Commit 05c32fb1 authored by Nicolas Giard's avatar Nicolas Giard Committed by GitHub

Merge branch 'dev' into master

parents 14c716b2 9684d0c9
{
"comments": false,
"presets": [
["env", {
"targets": {
"browsers": [
"last 6 Chrome major versions",
"last 6 Firefox major versions",
"last 4 Safari major versions",
"last 4 Edge major versions",
"last 3 iOS major versions",
"last 3 Android major versions",
"last 2 ChromeAndroid major versions",
"Explorer 11"
]
}
}],
"stage-2"
]
}
{ {
"extends": "standard", "extends": "requarks",
"env": { "env": {
"node": true, "node": true,
"es6": true, "es6": true,
"jest": true "jest": true
}, },
"globals": { "globals": {
// Client
"document": false, "document": false,
"navigator": false, "navigator": false,
"window": false, "window": false,
"siteLang": false, "FuseBox": false
"socket": true,
"wikijs": true,
"FuseBox": false,
// Server
"appconfig": true,
"appdata": true,
"ROOTPATH": true,
"SERVERPATH": true,
"IS_DEBUG": true
},
"rules": {
"space-before-function-paren": 0
} }
} }
save-prefix = "~" save-exact = true
save-prefix = ""
save-prefix "~" save-exact true
save-prefix ""
...@@ -237,11 +237,6 @@ This project adheres to [Semantic Versioning](http://semver.org/). ...@@ -237,11 +237,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Changed ### Changed
- Updated dependencies + snyk policy - Updated dependencies + snyk policy
[v1.0.11]: https://github.com/Requarks/wiki/releases/tag/v1.0.11
[v1.0.10]: https://github.com/Requarks/wiki/releases/tag/v1.0.10
[v1.0.9]: https://github.com/Requarks/wiki/releases/tag/v1.0.9
[v1.0.8]: https://github.com/Requarks/wiki/releases/tag/v1.0.8
[v1.0.7]: https://github.com/Requarks/wiki/releases/tag/v1.0.7
[v1.0.6]: https://github.com/Requarks/wiki/releases/tag/v1.0.6 [v1.0.6]: https://github.com/Requarks/wiki/releases/tag/v1.0.6
[v1.0.5]: https://github.com/Requarks/wiki/releases/tag/v1.0.5 [v1.0.5]: https://github.com/Requarks/wiki/releases/tag/v1.0.5
[v1.0.4]: https://github.com/Requarks/wiki/releases/tag/v1.0.4 [v1.0.4]: https://github.com/Requarks/wiki/releases/tag/v1.0.4
......
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
<svg width="100%" height="100%" viewBox="0 0 159 158" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<path fill="#51b5d7" d="M87.477,3.864c-4.583,-4.583 -12.024,-4.583 -16.608,0l-66.432,66.432c-4.583,4.583 -4.583,12.025 0,16.608l67.352,67.352c4.583,4.583 12.025,4.583 16.608,0l66.432,-66.432c4.583,-4.583 4.583,-12.025 0,-16.608l-67.352,-67.352Zm-14.477,44.282c-3.497,-2.176 -5.826,-6.054 -5.826,-10.472c0,-6.803 5.523,-12.326 12.326,-12.326c6.803,0 12.326,5.523 12.326,12.326c0,4.418 -2.329,8.296 -5.826,10.472l0,61.157c0.415,0.214 0.818,0.447 1.208,0.699l24.89,-24.89c-0.909,-1.718 -1.424,-3.676 -1.424,-5.753c0,-6.803 5.523,-12.326 12.326,-12.326c6.803,0 12.326,5.523 12.326,12.326c0,6.803 -5.523,12.326 -12.326,12.326c-1.493,0 -2.924,-0.266 -4.249,-0.753l-25.952,25.951c0.606,1.58 0.938,3.295 0.938,5.088c0,7.857 -6.38,14.236 -14.237,14.236c-7.857,0 -14.237,-6.379 -14.237,-14.236c0,-1.804 0.337,-3.531 0.95,-5.119l-26.466,-26.466c-1.182,0.377 -2.441,0.581 -3.747,0.581c-6.803,0 -12.326,-5.523 -12.326,-12.326c0,-6.803 5.523,-12.326 12.326,-12.326c6.803,0 12.326,5.523 12.326,12.326c0,2.254 -0.606,4.368 -1.665,6.187l25.157,25.157c0.382,-0.245 0.776,-0.473 1.182,-0.682l0,-61.157Z" />
<path fill="#97cbe1" d="M91.745,39.094l28.297,28.297c-4.527,1.116 -8.072,4.737 -9.079,9.306l-24.963,-24.963l0,-3.588c3.116,-1.939 5.306,-5.231 5.745,-9.052Zm-24.49,0c0.439,3.821 2.629,7.113 5.745,9.052l0,3.588l-24.846,24.846c-0.785,-4.655 -4.183,-8.427 -8.632,-9.753l27.733,-27.733Z" />
<circle fill="#97cbe1" cx="35.84" cy="78.701" r="6.701" />
<circle fill="#97cbe1" cx="123.16" cy="79.778" r="6.701" />
<circle fill="#97cbe1" cx="79.299" cy="37.243" r="6.701" />
<circle fill="#97cbe1" cx="79.633" cy="122.201" r="8.201" />
</svg>
<svg width="100%" height="100%" viewBox="0 0 500 500" preserveAspectRatio="xMinYMin meet">
<path fill="#3B5998" d="M288.714,500l0,-228.073l76.554,0l11.461,-88.885l-88.017,0l0,-56.749c0,-25.735 7.145,-43.271 44.049,-43.271l47.067,-0.022l0,-79.498c-8.141,-1.081 -36.082,-3.502 -68.584,-3.502c-67.862,0 -114.321,41.422 -114.321,117.492l0,65.55l-76.751,0l0,88.885l76.751,0l0,228.071l91.791,0l0,0.002Z" style="fill-rule:nonzero;"/>
</svg>
\ No newline at end of file
<svg width="2500" height="2432" viewBox="0 0 256 249" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet"><g fill="#161614"><path d="M127.505 0C57.095 0 0 57.085 0 127.505c0 56.336 36.534 104.13 87.196 120.99 6.372 1.18 8.712-2.766 8.712-6.134 0-3.04-.119-13.085-.173-23.739-35.473 7.713-42.958-15.044-42.958-15.044-5.8-14.738-14.157-18.656-14.157-18.656-11.568-7.914.872-7.752.872-7.752 12.804.9 19.546 13.14 19.546 13.14 11.372 19.493 29.828 13.857 37.104 10.6 1.144-8.242 4.449-13.866 8.095-17.05-28.32-3.225-58.092-14.158-58.092-63.014 0-13.92 4.981-25.295 13.138-34.224-1.324-3.212-5.688-16.18 1.235-33.743 0 0 10.707-3.427 35.073 13.07 10.17-2.826 21.078-4.242 31.914-4.29 10.836.048 21.752 1.464 31.942 4.29 24.337-16.497 35.029-13.07 35.029-13.07 6.94 17.563 2.574 30.531 1.25 33.743 8.175 8.929 13.122 20.303 13.122 34.224 0 48.972-29.828 59.756-58.22 62.912 4.573 3.957 8.648 11.717 8.648 23.612 0 17.06-.148 30.791-.148 34.991 0 3.393 2.295 7.369 8.759 6.117 50.634-16.879 87.122-64.656 87.122-120.973C255.009 57.085 197.922 0 127.505 0"/><path d="M47.755 181.634c-.28.633-1.278.823-2.185.389-.925-.416-1.445-1.28-1.145-1.916.275-.652 1.273-.834 2.196-.396.927.415 1.455 1.287 1.134 1.923M54.027 187.23c-.608.564-1.797.302-2.604-.589-.834-.889-.99-2.077-.373-2.65.627-.563 1.78-.3 2.616.59.834.899.996 2.08.36 2.65M58.33 194.39c-.782.543-2.06.034-2.849-1.1-.781-1.133-.781-2.493.017-3.038.792-.545 2.05-.055 2.85 1.07.78 1.153.78 2.513-.019 3.069M65.606 202.683c-.699.77-2.187.564-3.277-.488-1.114-1.028-1.425-2.487-.724-3.258.707-.772 2.204-.555 3.302.488 1.107 1.026 1.445 2.496.7 3.258M75.01 205.483c-.307.998-1.741 1.452-3.185 1.028-1.442-.437-2.386-1.607-2.095-2.616.3-1.005 1.74-1.478 3.195-1.024 1.44.435 2.386 1.596 2.086 2.612M85.714 206.67c.036 1.052-1.189 1.924-2.705 1.943-1.525.033-2.758-.818-2.774-1.852 0-1.062 1.197-1.926 2.721-1.951 1.516-.03 2.758.815 2.758 1.86M96.228 206.267c.182 1.026-.872 2.08-2.377 2.36-1.48.27-2.85-.363-3.039-1.38-.184-1.052.89-2.105 2.367-2.378 1.508-.262 2.857.355 3.049 1.398"/></g></svg>
\ No newline at end of file
<svg width="2443" height="2500" viewBox="0 0 256 262" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027" fill="#4285F4"/><path d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1" fill="#34A853"/><path d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782" fill="#FBBC05"/><path d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251" fill="#EB4335"/></svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="48px" height="48px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
<path fill="#458BC4" d="M44.804,30.404l-20-27C24.615,3.15,24.308,3.023,24,3.023V46c0.2,0,0.401-0.061,0.573-0.181l20-14
c0.222-0.155,0.37-0.393,0.414-0.659C45.03,30.895,44.964,30.622,44.804,30.404z"/>
<path fill="#43A6DD" d="M23.196,3.405l-20,27c-0.16,0.218-0.227,0.49-0.184,0.756c0.044,0.267,0.192,0.504,0.414,0.659l20,14
C23.599,45.939,23.8,46,24,46V3.023C23.692,3.023,23.385,3.15,23.196,3.405z"/>
</svg>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
<path d="M32,19c13.089,0,27-3.154,27-9S45.089,1,32,1S5,4.154,5,10S18.911,19,32,19z"/>
<path d="M32,41c13.089,0,27-3.154,27-9V14.436c-1.481,1.493-3.963,2.968-8.022,4.174C45.864,20.128,38.946,21,32,21
s-13.864-0.872-18.978-2.391C8.963,17.403,6.481,15.929,5,14.436V32C5,37.846,18.911,41,32,41z"/>
<path d="M32,63c13.089,0,27-3.154,27-9V36.436c-1.481,1.493-3.963,2.968-8.022,4.174C45.864,42.128,38.946,43,32,43
s-13.864-0.872-18.978-2.391C8.963,39.403,6.481,37.929,5,36.436V54C5,59.846,18.911,63,32,63z"/>
</svg>
<svg width="2490" height="2500" viewBox="0 0 256 257" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M0 36.357L104.62 22.11l.045 100.914-104.57.595L0 36.358zm104.57 98.293l.08 101.002L.081 221.275l-.006-87.302 104.494.677zm12.682-114.405L255.968 0v121.74l-138.716 1.1V20.246zM256 135.6l-.033 121.191-138.716-19.578-.194-101.84L256 135.6z" fill="#00ADEF"/></svg>
\ No newline at end of file
<svg width="2500" height="2500" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M165.964 15.838c-3.89-11.975-16.752-18.528-28.725-14.636-11.975 3.89-18.528 16.752-14.636 28.725l58.947 181.365c4.048 11.187 16.132 17.473 27.732 14.135 12.1-3.483 19.475-16.334 15.614-28.217L165.964 15.838" fill="#DFA22F"/><path d="M74.626 45.516C70.734 33.542 57.873 26.989 45.9 30.879 33.924 34.77 27.37 47.631 31.263 59.606l58.948 181.366c4.047 11.186 16.132 17.473 27.732 14.132 12.099-3.481 19.474-16.332 15.613-28.217L74.626 45.516" fill="#3CB187"/><path d="M240.162 166.045c11.975-3.89 18.526-16.75 14.636-28.726-3.89-11.973-16.752-18.527-28.725-14.636L44.708 181.632c-11.187 4.046-17.473 16.13-14.135 27.73 3.483 12.099 16.334 19.475 28.217 15.614l181.372-58.93" fill="#CE1E5B"/><path d="M82.508 217.27l43.347-14.084-14.086-43.352-43.35 14.09 14.089 43.347" fill="#392538"/><path d="M173.847 187.591c16.388-5.323 31.62-10.273 43.348-14.084l-14.088-43.36-43.35 14.09 14.09 43.354" fill="#BB242A"/><path d="M210.484 74.706c11.974-3.89 18.527-16.751 14.637-28.727-3.89-11.973-16.752-18.526-28.727-14.636L15.028 90.293C3.842 94.337-2.445 106.422.896 118.022c3.481 12.098 16.332 19.474 28.217 15.613l181.371-58.93" fill="#72C5CD"/><path d="M52.822 125.933c11.805-3.836 27.025-8.782 43.354-14.086-5.323-16.39-10.273-31.622-14.084-43.352l-43.36 14.092 14.09 43.346" fill="#248C73"/><path d="M144.16 96.256l43.356-14.088a546179.21 546179.21 0 0 0-14.089-43.36L130.07 52.9l14.09 43.356" fill="#62803A"/></svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1500 1000" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<path d="M1500,543c0,0 -135.753,54.677 -252.197,101.577c-106.023,42.702 -223.596,47.2 -332.571,12.724c-56.088,-17.745 -118.404,-37.46 -173.999,-55.048c-103.773,-32.831 -216.376,-22.709 -312.631,28.103c0,0 0,0.001 0,0.001c-94.821,50.055 -206.441,57.476 -307.053,20.415c-63.317,-23.322 -121.549,-44.772 -121.549,-44.772l0,417l1500,0l0,-480Z" style="fill:#2d2d2d;fill-opacity:0.282609;"/>
<path d="M1510,580c0,0 -144.155,47.882 -252.311,83.806c-74.651,24.796 -156.199,17.958 -225.679,-18.923c0,0 0,0 0,0c-62.207,-33.021 -133.629,-44.415 -203.023,-32.389c-98.381,17.051 -244.859,42.438 -352.664,61.121c-92.259,15.99 -187.076,8.079 -275.41,-22.977c-93.342,-32.818 -200.913,-70.638 -200.913,-70.638l0,466l1500,0l10,-466Z" style="fill:#2d2d2d;fill-opacity:0.550725;"/>
<path d="M1500,650c0,0 -143.367,28.581 -239.425,47.731c-56.087,11.181 -113.694,12.508 -170.237,3.922c-74.75,-11.351 -183.318,-27.838 -261.719,-39.743c-65.252,-9.909 -131.707,-8.759 -196.577,3.4c-49.655,9.308 -109.704,20.564 -158.992,29.803c-63.125,11.833 -127.839,12.479 -191.188,1.911c-111.875,-18.665 -281.862,-47.024 -281.862,-47.024l0,430l1500,0l0,-430Z" style="fill:#2d2d2d;"/>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1500 1000" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<path d="M1500,543c0,0 -135.753,54.677 -252.197,101.577c-106.023,42.702 -223.596,47.2 -332.571,12.724c-56.088,-17.745 -118.404,-37.46 -173.999,-55.048c-103.773,-32.831 -216.376,-22.709 -312.631,28.103c0,0 0,0.001 0,0.001c-94.821,50.055 -206.441,57.476 -307.053,20.415c-63.317,-23.322 -121.549,-44.772 -121.549,-44.772l0,417l1500,0l0,-480Z" style="fill:#fff;fill-opacity:0.282609;"/>
<path d="M1510,580c0,0 -144.155,47.882 -252.311,83.806c-74.651,24.796 -156.199,17.958 -225.679,-18.923c0,0 0,0 0,0c-62.207,-33.021 -133.629,-44.415 -203.023,-32.389c-98.381,17.051 -244.859,42.438 -352.664,61.121c-92.259,15.99 -187.076,8.079 -275.41,-22.977c-93.342,-32.818 -200.913,-70.638 -200.913,-70.638l0,466l1500,0l10,-466Z" style="fill:#fff;fill-opacity:0.550725;"/>
<path d="M1500,650c0,0 -143.367,28.581 -239.425,47.731c-56.087,11.181 -113.694,12.508 -170.237,3.922c-74.75,-11.351 -183.318,-27.838 -261.719,-39.743c-65.252,-9.909 -131.707,-8.759 -196.577,3.4c-49.655,9.308 -109.704,20.564 -158.992,29.803c-63.125,11.833 -127.839,12.479 -191.188,1.911c-111.875,-18.665 -281.862,-47.024 -281.862,-47.024l0,430l1500,0l0,-430Z" style="fill:#fff;"/>
</svg>
'use strict'
require('./scss/configure.scss')
require('./js/configure.js')
'use strict' 'use strict'
let logic = document.documentElement.dataset.logic require('./scss/app.scss')
require('./js/app.js')
switch (logic) {
case 'error':
require('./scss/error.scss')
break
case 'login':
require('./scss/login.scss')
require('./js/login.js')
break
default:
require('./scss/app.scss')
require('./js/app.js')
break
}
'use strict' 'use strict'
/* global $, siteRoot */ /* global siteConfig */
/* eslint-disable no-new */ /* eslint-disable no-new */
import CONSTANTS from './constants'
import Vue from 'vue' import Vue from 'vue'
import VueResource from 'vue-resource' import VueResource from 'vue-resource'
import VueClipboards from 'vue-clipboards' import VueClipboards from 'vue-clipboards'
import VueLodash from 'vue-lodash' import VeeValidate from 'vee-validate'
import { ApolloClient, createBatchingNetworkInterface } from 'apollo-client'
import store from './store' import store from './store'
import io from 'socket-io-client'
import i18next from 'i18next' // ====================================
import i18nextXHR from 'i18next-xhr-backend' // Load Modules
import VueI18Next from '@panter/vue-i18next' // ====================================
import 'jquery-contextmenu'
import 'jquery-simple-upload' import localization from './modules/localization'
import 'jquery-smooth-scroll'
import 'jquery-sticky'
// ==================================== // ====================================
// Load Helpers // Load Helpers
// ==================================== // ====================================
import helpers from './helpers' import helpers from './helpers'
import _ from './helpers/lodash'
// ==================================== // ====================================
// Load Vue Components // Load Vue Components
...@@ -36,6 +36,7 @@ import editorFileComponent from './components/editor-file.vue' ...@@ -36,6 +36,7 @@ import editorFileComponent from './components/editor-file.vue'
import editorVideoComponent from './components/editor-video.vue' import editorVideoComponent from './components/editor-video.vue'
import historyComponent from './components/history.vue' import historyComponent from './components/history.vue'
import loadingSpinnerComponent from './components/loading-spinner.vue' import loadingSpinnerComponent from './components/loading-spinner.vue'
import loginComponent from './components/login.vue'
import modalCreatePageComponent from './components/modal-create-page.vue' import modalCreatePageComponent from './components/modal-create-page.vue'
import modalCreateUserComponent from './components/modal-create-user.vue' import modalCreateUserComponent from './components/modal-create-user.vue'
import modalDeletePageComponent from './components/modal-delete-page.vue' import modalDeletePageComponent from './components/modal-delete-page.vue'
...@@ -53,19 +54,48 @@ import adminEditUserComponent from './pages/admin-edit-user.component.js' ...@@ -53,19 +54,48 @@ import adminEditUserComponent from './pages/admin-edit-user.component.js'
import adminProfileComponent from './pages/admin-profile.component.js' import adminProfileComponent from './pages/admin-profile.component.js'
import adminSettingsComponent from './pages/admin-settings.component.js' import adminSettingsComponent from './pages/admin-settings.component.js'
import adminThemeComponent from './pages/admin-theme.component.js' import adminThemeComponent from './pages/admin-theme.component.js'
import configManagerComponent from './components/config-manager.component.js'
import contentViewComponent from './pages/content-view.component.js' import contentViewComponent from './pages/content-view.component.js'
import editorComponent from './components/editor.component.js' import editorComponent from './components/editor.component.js'
import sourceViewComponent from './pages/source-view.component.js' import sourceViewComponent from './pages/source-view.component.js'
// ==================================== // ====================================
// Initialize Global Vars
// ====================================
window.wiki = null
window.CONSTANTS = CONSTANTS
// ====================================
// Initialize Apollo Client (GraphQL)
// ====================================
window.graphQL = new ApolloClient({
networkInterface: createBatchingNetworkInterface({
uri: window.location.protocol + '//' + window.location.host + siteConfig.path + '/graphql'
}),
connectToDevTools: true
})
// ====================================
// Initialize Vue Modules // Initialize Vue Modules
// ==================================== // ====================================
Vue.use(VueResource) Vue.use(VueResource)
Vue.use(VueClipboards) Vue.use(VueClipboards)
Vue.use(VueI18Next) Vue.use(localization.VueI18Next)
Vue.use(VueLodash, _)
Vue.use(helpers) Vue.use(helpers)
Vue.use(VeeValidate, {
enableAutoClasses: true,
classNames: {
touched: 'is-touched', // the control has been blurred
untouched: 'is-untouched', // the control hasn't been blurred
valid: 'is-valid', // model is valid
invalid: 'is-invalid', // model is invalid
pristine: 'is-pristine', // control has not been interacted with
dirty: 'is-dirty' // control has been interacted with
}
})
// ==================================== // ====================================
// Register Vue Components // Register Vue Components
...@@ -78,6 +108,7 @@ Vue.component('adminSettings', adminSettingsComponent) ...@@ -78,6 +108,7 @@ Vue.component('adminSettings', adminSettingsComponent)
Vue.component('adminTheme', adminThemeComponent) Vue.component('adminTheme', adminThemeComponent)
Vue.component('anchor', anchorComponent) Vue.component('anchor', anchorComponent)
Vue.component('colorPicker', colorPickerComponent) Vue.component('colorPicker', colorPickerComponent)
Vue.component('configManager', configManagerComponent)
Vue.component('contentView', contentViewComponent) Vue.component('contentView', contentViewComponent)
Vue.component('editor', editorComponent) Vue.component('editor', editorComponent)
Vue.component('editorCodeblock', editorCodeblockComponent) Vue.component('editorCodeblock', editorCodeblockComponent)
...@@ -85,6 +116,7 @@ Vue.component('editorFile', editorFileComponent) ...@@ -85,6 +116,7 @@ Vue.component('editorFile', editorFileComponent)
Vue.component('editorVideo', editorVideoComponent) Vue.component('editorVideo', editorVideoComponent)
Vue.component('history', historyComponent) Vue.component('history', historyComponent)
Vue.component('loadingSpinner', loadingSpinnerComponent) Vue.component('loadingSpinner', loadingSpinnerComponent)
Vue.component('login', loginComponent)
Vue.component('modalCreatePage', modalCreatePageComponent) Vue.component('modalCreatePage', modalCreatePageComponent)
Vue.component('modalCreateUser', modalCreateUserComponent) Vue.component('modalCreateUser', modalCreateUserComponent)
Vue.component('modalDeletePage', modalDeletePageComponent) Vue.component('modalDeletePage', modalDeletePageComponent)
...@@ -99,52 +131,26 @@ Vue.component('sourceView', sourceViewComponent) ...@@ -99,52 +131,26 @@ Vue.component('sourceView', sourceViewComponent)
Vue.component('toggle', toggleComponent) Vue.component('toggle', toggleComponent)
Vue.component('tree', treeComponent) Vue.component('tree', treeComponent)
// ==================================== document.addEventListener('DOMContentLoaded', ev => {
// Load Localization strings
// ====================================
i18next
.use(i18nextXHR)
.init({
backend: {
loadPath: siteRoot + '/js/i18n/{{lng}}.json'
},
lng: siteLang,
fallbackLng: siteLang
})
$(() => {
// ==================================== // ====================================
// Notifications // Notifications
// ==================================== // ====================================
$(window).bind('beforeunload', () => { window.addEventListener('beforeunload', () => {
store.dispatch('startLoading') store.dispatch('startLoading')
}) })
$(document).ajaxSend(() => {
store.dispatch('startLoading')
}).ajaxComplete(() => {
store.dispatch('stopLoading')
})
// ====================================
// Establish WebSocket connection
// ====================================
let socket = io(window.location.origin)
window.socket = socket
// ==================================== // ====================================
// Bootstrap Vue // Bootstrap Vue
// ==================================== // ====================================
const i18n = new VueI18Next(i18next) const i18n = localization.init()
window.wikijs = new Vue({ window.wiki = new Vue({
mixins: [helpers], mixins: [helpers],
components: {}, components: {},
store, store,
i18n, i18n,
el: '#root', el: '#app',
methods: { methods: {
changeTheme(opts) { changeTheme(opts) {
this.$el.className = `has-stickynav is-primary-${opts.primary} is-alternate-${opts.alt}` this.$el.className = `has-stickynav is-primary-${opts.primary} is-alternate-${opts.alt}`
...@@ -153,9 +159,7 @@ $(() => { ...@@ -153,9 +159,7 @@ $(() => {
} }
}, },
mounted() { mounted() {
$('a:not(.toc-anchor)').smoothScroll({ speed: 500, offset: -50 })
$('#header').sticky({ topSpacing: 0 })
$('.sidebar-pagecontents').sticky({ topSpacing: 15, bottomSpacing: 75 })
} }
}) })
}) })
'use strict'
/* global $, siteConfig */
/* eslint-disable no-new */
import Vue from 'vue'
import VueResource from 'vue-resource'
import VueClipboards from 'vue-clipboards'
import VueLodash from 'vue-lodash'
import store from './store'
import i18next from 'i18next'
import i18nextXHR from 'i18next-xhr-backend'
import VueI18Next from '@panter/vue-i18next'
import 'jquery-contextmenu'
import 'jquery-simple-upload'
import 'jquery-smooth-scroll'
import 'jquery-sticky'
// ====================================
// Load Helpers
// ====================================
import helpers from './helpers'
import _ from './helpers/lodash'
// ====================================
// Load Vue Components
// ====================================
import alertComponent from './components/alert.vue'
import anchorComponent from './components/anchor.vue'
import colorPickerComponent from './components/color-picker.vue'
import editorCodeblockComponent from './components/editor-codeblock.vue'
import editorFileComponent from './components/editor-file.vue'
import editorVideoComponent from './components/editor-video.vue'
import historyComponent from './components/history.vue'
import loadingSpinnerComponent from './components/loading-spinner.vue'
import modalCreatePageComponent from './components/modal-create-page.vue'
import modalCreateUserComponent from './components/modal-create-user.vue'
import modalDeleteUserComponent from './components/modal-delete-user.vue'
import modalDiscardPageComponent from './components/modal-discard-page.vue'
import modalMovePageComponent from './components/modal-move-page.vue'
import modalProfile2faComponent from './components/modal-profile-2fa.vue'
import modalUpgradeSystemComponent from './components/modal-upgrade-system.vue'
import pageLoaderComponent from './components/page-loader.vue'
import searchComponent from './components/search.vue'
import toggleComponent from './components/toggle.vue'
import treeComponent from './components/tree.vue'
import adminEditUserComponent from './pages/admin-edit-user.component.js'
import adminProfileComponent from './pages/admin-profile.component.js'
import adminSettingsComponent from './pages/admin-settings.component.js'
import adminThemeComponent from './pages/admin-theme.component.js'
import contentViewComponent from './pages/content-view.component.js'
import editorComponent from './components/editor.component.js'
import sourceViewComponent from './pages/source-view.component.js'
// ====================================
// Initialize Vue Modules
// ====================================
Vue.use(VueResource)
Vue.use(VueClipboards)
Vue.use(VueI18Next)
Vue.use(VueLodash, _)
Vue.use(helpers)
// ====================================
// Register Vue Components
// ====================================
Vue.component('alert', alertComponent)
Vue.component('adminEditUser', adminEditUserComponent)
Vue.component('adminProfile', adminProfileComponent)
Vue.component('adminSettings', adminSettingsComponent)
Vue.component('adminTheme', adminThemeComponent)
Vue.component('anchor', anchorComponent)
Vue.component('colorPicker', colorPickerComponent)
Vue.component('contentView', contentViewComponent)
Vue.component('editor', editorComponent)
Vue.component('editorCodeblock', editorCodeblockComponent)
Vue.component('editorFile', editorFileComponent)
Vue.component('editorVideo', editorVideoComponent)
Vue.component('history', historyComponent)
Vue.component('loadingSpinner', loadingSpinnerComponent)
Vue.component('modalCreatePage', modalCreatePageComponent)
Vue.component('modalCreateUser', modalCreateUserComponent)
Vue.component('modalDeleteUser', modalDeleteUserComponent)
Vue.component('modalDiscardPage', modalDiscardPageComponent)
Vue.component('modalMovePage', modalMovePageComponent)
Vue.component('modalProfile2fa', modalProfile2faComponent)
Vue.component('modalUpgradeSystem', modalUpgradeSystemComponent)
Vue.component('pageLoader', pageLoaderComponent)
Vue.component('search', searchComponent)
Vue.component('sourceView', sourceViewComponent)
Vue.component('toggle', toggleComponent)
Vue.component('tree', treeComponent)
// ====================================
// Load Localization strings
// ====================================
i18next
.use(i18nextXHR)
.init({
backend: {
loadPath: siteConfig.path + '/js/i18n/{{lng}}.json'
},
lng: siteConfig.lang,
fallbackLng: siteConfig.lang
})
$(() => {
// ====================================
// Notifications
// ====================================
$(window).bind('beforeunload', () => {
store.dispatch('startLoading')
})
$(document).ajaxSend(() => {
store.dispatch('startLoading')
}).ajaxComplete(() => {
store.dispatch('stopLoading')
})
// ====================================
// Bootstrap Vue
// ====================================
const i18n = new VueI18Next(i18next)
if (document.querySelector('#root')) {
window.wikijs = new Vue({
mixins: [helpers],
components: {},
store,
i18n,
el: '#root',
methods: {
changeTheme(opts) {
this.$el.className = `has-stickynav is-primary-${opts.primary} is-alternate-${opts.alt}`
this.$refs.header.className = `nav is-${opts.primary}`
this.$refs.footer.className = `footer is-${opts.footer}`
}
},
mounted() {
$('a:not(.toc-anchor)').smoothScroll({ speed: 500, offset: -50 })
$('#header').sticky({ topSpacing: 0 })
$('.sidebar-pagecontents').sticky({ topSpacing: 15, bottomSpacing: 75 })
}
})
}
})
/* global siteConfig */
import axios from 'axios'
export default {
name: 'configManager',
data() {
return {
loading: false,
state: 'welcome',
syscheck: {
ok: false,
error: '',
results: []
},
dbcheck: {
ok: false,
error: ''
},
gitcheck: {
ok: false,
error: ''
},
final: {
ok: false,
error: '',
results: []
},
conf: {
telemetry: true,
upgrade: false,
title: siteConfig.title || 'Wiki',
host: siteConfig.host || 'http://',
port: siteConfig.port || 80,
lang: siteConfig.lang || 'en',
public: (siteConfig.public === true),
pathData: './data',
pathRepo: './repo',
gitUseRemote: (siteConfig.git !== false),
gitUrl: '',
gitBranch: 'master',
gitAuthType: 'ssh',
gitAuthSSHKey: '',
gitAuthUser: '',
gitAuthPass: '',
gitAuthSSL: true,
gitShowUserEmail: true,
gitServerEmail: '',
adminEmail: '',
adminPassword: '',
adminPasswordConfirm: ''
},
considerations: {
https: false,
port: false,
localhost: false
}
}
},
computed: {
currentProgress: function () {
let perc = '0%'
switch (this.state) {
case 'welcome':
perc = '0%'
break
case 'syscheck':
perc = (this.syscheck.ok) ? '15%' : '5%'
break
case 'general':
perc = '25%'
break
case 'considerations':
perc = '30%'
break
case 'git':
perc = '50%'
break
case 'gitcheck':
perc = (this.gitcheck.ok) ? '70%' : '55%'
break
case 'admin':
perc = '75%'
break
}
return perc
}
},
mounted: function () {
/* if (appconfig.paths) {
this.conf.pathData = appconfig.paths.data || './data'
this.conf.pathRepo = appconfig.paths.repo || './repo'
}
if (appconfig.git !== false && _.isPlainObject(appconfig.git)) {
this.conf.gitUrl = appconfig.git.url || ''
this.conf.gitBranch = appconfig.git.branch || 'master'
this.conf.gitShowUserEmail = (appconfig.git.showUserEmail !== false)
this.conf.gitServerEmail = appconfig.git.serverEmail || ''
if (_.isPlainObject(appconfig.git.auth)) {
this.conf.gitAuthType = appconfig.git.auth.type || 'ssh'
this.conf.gitAuthSSHKey = appconfig.git.auth.privateKey || ''
this.conf.gitAuthUser = appconfig.git.auth.username || ''
this.conf.gitAuthPass = appconfig.git.auth.password || ''
this.conf.gitAuthSSL = (appconfig.git.auth.sslVerify !== false)
}
} */
},
methods: {
proceedToWelcome: function (ev) {
this.state = 'welcome'
this.loading = false
},
proceedToSyscheck: function (ev) {
let self = this
this.state = 'syscheck'
this.loading = true
self.syscheck = {
ok: false,
error: '',
results: []
}
this.$helpers._.delay(() => {
axios.post('/syscheck').then(resp => {
if (resp.data.ok === true) {
self.syscheck.ok = true
self.syscheck.results = resp.data.results
} else {
self.syscheck.ok = false
self.syscheck.error = resp.data.error
}
self.loading = false
self.$nextTick()
}).catch(err => {
window.alert(err.message)
})
}, 1000)
},
proceedToGeneral: function (ev) {
let self = this
self.state = 'general'
self.loading = false
self.$nextTick(() => {
self.$validator.validateAll('general')
})
},
proceedToConsiderations: function (ev) {
this.considerations = {
https: !this.$helpers._.startsWith(this.conf.host, 'https'),
port: false, // TODO
localhost: this.$helpers._.includes(this.conf.host, 'localhost')
}
this.state = 'considerations'
this.loading = false
},
proceedToGit: function (ev) {
let self = this
self.state = 'git'
self.loading = false
self.$nextTick(() => {
self.$validator.validateAll('git')
})
},
proceedToGitCheck: function (ev) {
let self = this
this.state = 'gitcheck'
this.loading = true
self.gitcheck = {
ok: false,
results: [],
error: ''
}
this.$helpers._.delay(() => {
axios.post('/gitcheck', self.conf).then(resp => {
if (resp.data.ok === true) {
self.gitcheck.ok = true
self.gitcheck.results = resp.data.results
} else {
self.gitcheck.ok = false
self.gitcheck.error = resp.data.error
}
self.loading = false
self.$nextTick()
}).catch(err => {
window.alert(err.message)
})
}, 1000)
},
proceedToAdmin: function (ev) {
let self = this
self.state = 'admin'
self.loading = false
self.$nextTick(() => {
self.$validator.validateAll('admin')
})
},
proceedToFinal: function (ev) {
let self = this
self.state = 'final'
self.loading = true
self.final = {
ok: false,
error: '',
results: []
}
this.$helpers._.delay(() => {
axios.post('/finalize', self.conf).then(resp => {
if (resp.data.ok === true) {
self.final.ok = true
self.final.results = resp.data.results
} else {
self.final.ok = false
self.final.error = resp.data.error
}
self.loading = false
self.$nextTick()
}).catch(err => {
window.alert(err.message)
})
}, 1000)
},
finish: function (ev) {
let self = this
self.state = 'restart'
this.$helpers._.delay(() => {
axios.post('/restart', {}).then(resp => {
this.$helpers._.delay(() => {
window.location.assign(self.conf.host)
}, 30000)
}).catch(err => {
window.alert(err.message)
})
}, 1000)
}
}
}
...@@ -34,9 +34,9 @@ ...@@ -34,9 +34,9 @@
<script> <script>
const videoRules = { const videoRules = {
'youtube': new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|&v(?:i)?=))([^#&?]*).*/, 'i'), 'youtube': new RegExp('/(?:(?:youtu\\.be\\/|v\\/|vi\\/|u\\/\\w\\/|embed\\/)|(?:(?:watch)?\\?v(?:i)?=|&v(?:i)?=))([^#&?]*).*/', 'i'),
'vimeo': new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/, 'i'), 'vimeo': new RegExp('/vimeo.com\\/(?:channels\\/(?:\\w+\\/)?|groups\\/(?:[^/]*)\\/videos\\/|album\\/(?:\\d+)\\/video\\/|)(\\d+)(?:$|\\/|\\?)/', 'i'),
'dailymotion': new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/, 'i') 'dailymotion': new RegExp('/(?:dailymotion\\.com(?:\\/embed)?(?:\\/video|\\/hub)|dai\\.ly)\\/([0-9a-z]+)(?:[-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/', 'i')
} }
export default { export default {
......
<template lang="pug">
.login(:class='{ "is-error": error }')
.login-container(:class='{ "is-expanded": strategies.length > 1 }')
.login-error(v-if='error')
strong
i.icon-warning-outline
| {{ error.title }}
span {{ error.message }}
.login-providers(v-show='strategies.length > 1')
button(v-for='strategy in strategies', :class='{ "is-active": strategy.key === selectedStrategy }', @click='selectStrategy(strategy.key, strategy.useForm)', :title='strategy.title')
em(v-html='strategy.icon')
span {{ strategy.title }}
.login-frame
h1 {{ siteTitle }}
h2 {{ $t('auth:loginrequired') }}
form(method='post', action='/login')
input#login-user(type='text', name='email', :placeholder='$t("auth:fields.emailuser")')
input#login-pass(type='password', name='password', :placeholder='$t("auth:fields.password")')
button.button.is-light-blue.is-fullwidth(type='submit')
span {{ $t('auth:actions.login') }}
.login-copyright
span {{ $t('footer.poweredby') }}
a(href='https://wiki.js.org', rel='external', title='Wiki.js') Wiki.js
</template>
<script>
export default {
name: 'login',
data() {
return {
error: false,
strategies: [],
selectedStrategy: 'local'
}
},
computed: {
siteTitle() {
return siteConfig.title
}
},
methods: {
selectStrategy(key, useForm) {
this.selectedStrategy = key
if (!useForm) {
window.location.assign(siteConfig.path + '/login/' + key)
}
},
refreshStrategies() {
graphQL.query({
query: CONSTANTS.GRAPHQL.GQL_QUERY_AUTHENTICATION,
variables: {
mode: 'active'
}
}).then(resp => {
if (resp.data.authentication) {
this.strategies = resp.data.authentication
} else {
throw new Error('No authentication providers available!')
}
}).catch(err => {
console.error(err)
})
}
},
mounted() {
this.refreshStrategies()
}
}
</script>
...@@ -5,20 +5,19 @@ ...@@ -5,20 +5,19 @@
span {{ msg }} span {{ msg }}
</template> </template>
<script> <script type='js'>
export default { export default {
name: 'page-loader', name: 'page-loader',
props: ['text'], props: ['text'],
data () { data () {
return {} return {}
}, },
computed: { computed: {
msg () { return this.$store.state.pageLoader.msg }, msg () { return this.$store.state.pageLoader.msg },
isShown () { return this.$store.state.pageLoader.shown } isShown () { return this.$store.state.pageLoader.shown }
}, },
mounted() { mounted() {
this.$store.commit('pageLoader/msgChange', this.text) this.$store.commit('pageLoader/msgChange', this.text)
}
} }
} }
</script> </script>
import gql from 'graphql-tag'
export default {
GQL_QUERY_AUTHENTICATION: gql`
query($mode: String!) {
authentication(mode:$mode) {
key
useForm
title
icon
}
}
`,
GQL_QUERY_TRANSLATIONS: gql`
query($locale: String!, $namespace: String!) {
translations(locale:$locale, namespace:$namespace) {
key
value
}
}
`
}
import GRAPHQL from './graphql'
export default {
GRAPHQL
}
'use strict' 'use strict'
const helpers = { const helpers = {
_: require('./lodash'),
common: require('./common'), common: require('./common'),
form: require('./form'), form: require('./form'),
pages: require('./pages') pages: require('./pages')
......
'use strict'
/* global $ */
$(() => {
$('#login-user').focus()
})
import i18next from 'i18next'
import i18nextXHR from 'i18next-xhr-backend'
import i18nextCache from 'i18next-localstorage-cache'
import VueI18Next from '@panter/vue-i18next'
import loSet from 'lodash/set'
/* global siteConfig, graphQL, CONSTANTS */
module.exports = {
VueI18Next,
init() {
i18next
.use(i18nextXHR)
.use(i18nextCache)
.init({
backend: {
loadPath: '{{lng}}/{{ns}}',
parse: (data) => data,
ajax: (url, opts, cb, data) => {
let langParams = url.split('/')
graphQL.query({
query: CONSTANTS.GRAPHQL.GQL_QUERY_TRANSLATIONS,
variables: {
locale: langParams[0],
namespace: langParams[1]
}
}).then(resp => {
let ns = {}
if (resp.data.translations.length > 0) {
resp.data.translations.forEach(entry => {
loSet(ns, entry.key, entry.value)
})
}
return cb(ns, {status: '200'})
}).catch(err => {
console.error(err)
return cb(null, {status: '404'})
})
}
},
cache: {
enabled: true,
expiration: 60 * 60 * 1000
},
defaultNS: 'common',
lng: siteConfig.lang,
fallbackLng: siteConfig.lang,
ns: ['common', 'admin', 'auth']
})
return new VueI18Next(i18next)
}
}
...@@ -15,6 +15,7 @@ $primary: 'indigo'; ...@@ -15,6 +15,7 @@ $primary: 'indigo';
@import 'components/button'; @import 'components/button';
@import 'components/collapsable-nav'; @import 'components/collapsable-nav';
@import 'components/color-picker'; @import 'components/color-picker';
@import 'components/config-manager';
@import 'components/footer'; @import 'components/footer';
@import 'components/form'; @import 'components/form';
@import 'components/grid'; @import 'components/grid';
...@@ -43,6 +44,7 @@ $primary: 'indigo'; ...@@ -43,6 +44,7 @@ $primary: 'indigo';
@import 'layout/_loader'; @import 'layout/_loader';
@import 'layout/_rtl'; @import 'layout/_rtl';
@import 'pages/_welcome'; @import 'pages/login';
@import 'pages/welcome';
@import 'base/print'; @import 'base/print';
...@@ -11,10 +11,15 @@ html { ...@@ -11,10 +11,15 @@ html {
display: none; display: none;
} }
#root { #app {
padding-bottom: 67px; padding-bottom: 67px;
position: relative; position: relative;
min-height: 100%; min-height: 100%;
&.is-fullscreen {
width: 100vw;
height: 100vh;
}
} }
body { body {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
border: 1px solid mc('orange','700'); border: 1px solid mc('orange','700');
border-radius: 3px; border-radius: 3px;
display: inline-flex; display: inline-flex;
height: 30px; height: 40px;
align-items: center; align-items: center;
padding: 0 15px; padding: 0 15px;
font-size: 13px; font-size: 13px;
...@@ -61,7 +61,11 @@ ...@@ -61,7 +61,11 @@
background-color: mc($color,'800'); background-color: mc($color,'800');
color: #FFF; color: #FFF;
animation: none; animation: none;
} }
&:focus {
box-shadow: inset 0 0 0 3px rgba(255,255,255, .4);
}
} }
} }
...@@ -74,7 +78,13 @@ ...@@ -74,7 +78,13 @@
&.is-featured { &.is-featured {
animation: btnInvertedPulse .6s ease alternate infinite; animation: btnInvertedPulse .6s ease alternate infinite;
} }
&.is-fullwidth {
width: 100%;
text-align: center;
justify-content: center;
}
&.is-disabled, &:disabled { &.is-disabled, &:disabled {
background-color: mc('grey', '300'); background-color: mc('grey', '300');
...@@ -87,7 +97,11 @@ ...@@ -87,7 +97,11 @@
background-color: mc('grey', '300') !important; background-color: mc('grey', '300') !important;
color: mc('grey', '500') !important; color: mc('grey', '500') !important;
} }
} }
&.is-small {
height: 30px;
}
} }
......
.config-manager {
.welcome {
text-align: center;
padding: 50px 0 15px 0;
color: mc('grey', '700');
h2 {
margin: 0;
}
}
i.icon-loader {
display: inline-block;
color: mc('indigo', '500')
}
i.icon-check {
color: mc('green', '500')
}
i.icon-square-cross {
color: mc('red', '500')
}
i.icon-warning-outline {
color: mc('orange', '500')
}
.tst-welcome-leave-active, .tst-welcome-enter-active {
transition: all .5s;
overflow-y: hidden;
}
.tst-welcome-leave, .tst-welcome-enter-to {
opacity: 1;
max-height: 200px;
}
.tst-welcome-leave-to, .tst-welcome-enter {
opacity: 0;
max-height: 0;
padding-top: 0;
}
.progress-bar {
width: 150px;
height: 10px;
background-color: mc('indigo', '50');
border:1px solid mc('indigo', '100');
border-radius: 3px;
position: absolute;
left: 15px;
top: 21px;
padding: 1px;
> div {
width: 5px;
height: 6px;
background-color: mc('indigo', '200');
border-radius: 2px;
transition: all 1s ease;
}
}
}
@charset "utf-8";
$primary: 'indigo';
@import "base/variables";
@import "base/colors";
@import "base/reset";
@import "base/mixins";
@import "base/fonts";
@import "base/base";
@import "libs/animate";
@import 'pages/login';
.login {
body { background-image: linear-gradient(to right, mc('blue', '400'), mc('blue', '600'));
padding: 0; width: 100%;
margin: 0; height: 100%;
font-family: $core-font-standard; display: flex;
font-size: 14px; align-items: center;
} justify-content: center;
a { &.is-error {
color: #FFF; background-image: linear-gradient(to right, mc('red', '400'), mc('red', '600'));
transition: color 0.4s ease; }
text-decoration: none;
&::before {
&:hover { content: '';
color: mc('orange','600'); position: absolute;
text-decoration: underline; background-image: url('../svg/login-bg.svg');
} background-position: center bottom;
background-size: cover;
} top: 0;
left: 0;
#bg { width: 100vw;
position: fixed; height: 100vh;
top: 0; }
left: 0;
width: 100%; &-container {
height: 100%; position: relative;
z-index: 1; display: flex;
background-color: #000; width: 400px;
align-items: stretch;
> div { box-shadow: 0 14px 28px rgba(0,0,0,0.2);
background-size: cover; border-radius: 6px;
background-position: center center;
width: 100%; &.is-expanded {
height: 100%; width: 650px;
position: absolute;
top: 0; .login-frame {
left: 0; border-radius: 0 6px 6px 0;
opacity: 0; border-left: none;
visibility: hidden; }
transition: opacity 3s ease, visibility 3s; }
animation: bg 30s linear infinite;
@include until($tablet) {
&:nth-child(1) { width: 350px;
animation-delay: 10s;
} &.is-expanded {
width: 400px;
&:nth-child(2) { }
animation-delay: 20s; }
} }
} &-error {
position: absolute;
} bottom: 105%;
width: 100%;
#root { min-height: 50px;
position: fixed; background-image: radial-gradient(ellipse at top left, rgba(255,255,255,.9) 0%,rgba(255,255,255,.8) 100%);
top: 15vh; box-shadow: 0 5px 10px rgba(0,0,0,0.2);
left: 10vw; border-radius: 6px;
z-index: 2; color: mc('red', '800');
color: #FFF; display: flex;
display: flex; justify-content: center;
flex-direction: column; align-items: center;
padding: 1rem;
h1 {
font-size: 4rem; strong {
font-weight: bold; font-weight: 600;
color: #FFF; text-transform: uppercase;
padding: 0; display: block;
margin: 0; padding: 0 1rem 0 0;
animation: headerIntro 3s ease; border-right: 1px solid mc('red', '200');
} }
span {
h2 { padding: 0 0 0 1rem;
font-size: 1.5rem; display: block;
font-weight: normal; }
color: rgba(255,255,255,0.7); }
padding: 0;
margin: 0 0 25px 0; &-providers {
animation: headerIntro 3s ease; display: flex;
} flex-direction: column;
width: 250px;
h3 {
font-size: 1.25rem; border-right: none;
font-weight: normal; border-radius: 6px 0 0 6px;
color: #FB8C00; z-index: 1;
padding: 0; overflow: hidden;
margin: 0;
animation: shake 1s ease; @include until($tablet) {
width: 50px;
> .fa { }
margin-right: 7px;
} button {
flex: 1 1;
} padding: 5px 15px;
border: none;
h4 { color: #FFF;
font-size: 0.8rem; background: linear-gradient(to right, rgba(mc('light-blue', '800'), .7), rgba(mc('light-blue', '800'), 1));
font-weight: normal; border-top: 1px solid rgba(mc('light-blue', '900'), .5);
color: rgba(255,255,255,0.7); font-family: $core-font-standard;
padding: 0; font-weight: 600;
margin: 0 0 15px 0; text-align: left;
animation: fadeIn 3s ease; min-height: 40px;
} display: flex;
justify-content: flex-start;
form { align-items: center;
display: flex; transition: all .4s ease;
flex-direction: column;
} &:focus {
outline: none;
input[type=text], input[type=password] { }
width: 350px;
max-width: 80vw; @include until($tablet) {
border: 1px solid rgba(255,255,255,0.3); justify-content: center;
border-radius: 3px; }
background-color: rgba(0,0,0,0.2);
padding: 0 15px; &:hover {
height: 40px; background-color: mc('light-blue', '900');
margin: 0 0 10px 0; }
color: #FFF;
font-weight: bold; &:first-child {
font-size: 14px; border-top: none;
transition: all 0.4s ease;
&.is-active {
&:focus { border-top: 1px solid rgba(255,255,255, .5);
outline: none; }
border-color: mc('orange','600'); }
}
&.is-active {
} background-image: linear-gradient(to right, rgba(255,255,255,1) 0%,rgba(255,255,255,.77) 100%);
color: mc('light-blue', '700');
button { cursor: default;
background-color: mc('orange','600');
color: #FFF; &:hover {
border: 1px solid lighten(mc('orange','600'), 10%); background-color: transparent;
border-radius: 3px; }
height: 40px;
width: 125px; svg path {
padding: 0; fill: mc('light-blue', '800');
font-weight: bold; }
margin: 15px 0 0 0; }
transition: all 0.4s ease;
cursor: pointer; i {
margin-right: 10px;
span { font-size: 16px;
font-weight: bold;
} @include until($tablet) {
margin-right: 0;
&:focus { font-size: 20px;
outline: none; }
border-color: #FFF; }
}
svg {
&:hover { margin-right: 10px;
background-color: darken(mc('orange','600'), 10%); width: auto;
} height: 20px;
max-width: 18px;
} max-height: 20px;
#social { path {
margin-top: 25px; fill: #FFF;
}
> span {
display: block; @include until($tablet) {
font-weight: bold; margin-right: 0;
color: rgba(255,255,255,0.7); font-size: 20px;
} }
}
button {
margin-right: 5px; span {
width: auto; font-weight: 600;
padding: 0 15px;
@include until($tablet) {
> i { display: none;
margin-right: 10px; }
font-size: 16px; }
} }
}
&.ms {
background-color: mc('blue','600'); &-frame {
border-color: lighten(mc('blue','600'), 10%); background-image: radial-gradient(circle at top center, rgba(255,255,255,1) 5%,rgba(255,255,255,.6) 100%);
border: 1px solid rgba(255,255,255, .5);
&:focus { border-radius: 6px;
border-color: #FFF; width: 400px;
} padding: 1rem;
color: mc('grey', '700');
&:hover { display: flex;
background-color: darken(mc('blue','600'), 10%); justify-content: center;
} flex-direction: column;
text-align: center;
}
@include until($tablet) {
&.google { width: 350px;
background-color: mc('light-blue','600'); }
border-color: lighten(mc('light-blue','600'), 10%);
h1 {
&:focus { font-size: 2rem;
border-color: #FFF; font-weight: 600;
} color: mc('light-blue', '700');
text-shadow: 1px 1px 0 #FFF;
&:hover { padding: 0;
background-color: darken(mc('light-blue','600'), 10%); margin: 0;
} }
} h2 {
font-size: 1.5rem;
&.facebook { font-weight: 300;
background-color: mc('indigo','600'); color: mc('grey', '700');
border-color: lighten(mc('indigo','600'), 10%); text-shadow: 1px 1px 0 #FFF;
padding: 0;
&:focus { margin: 0 0 25px 0;
border-color: #FFF; }
}
form {
&:hover { display: flex;
background-color: darken(mc('indigo','600'), 10%); flex-direction: column;
} }
} input[type=text], input[type=password] {
width: 100%;
&.github { border: 1px solid #FFF;
background-color: mc('blue-grey','700'); border-radius: 3px;
border-color: lighten(mc('blue-grey','700'), 10%); background-color: rgba(255,255,255,.9);
box-shadow: inset 0 0 0 3px rgba(255,255,255, .25);
&:focus { padding: 0 15px;
border-color: #FFF; height: 40px;
} margin: 0 0 10px 0;
color: mc('grey', '700');
&:hover { font-weight: 600;
background-color: darken(mc('blue-grey','700'), 10%); font-size: .8rem;
} transition: all 0.4s ease;
} text-align: center;
&.slack { &:focus {
background-color: mc('purple','700'); outline: none;
border-color: lighten(mc('purple','700'), 10%); border-color: mc('light-blue','500');
background-color: rgba(255,255,255,1);
&:focus { box-shadow: inset 0 0 0 3px rgba(mc('light-blue','500'), .25);
border-color: #FFF; color: mc('light-blue', '800');
} }
&:hover { }
background-color: darken(mc('purple','700'), 10%);
} }
}
&-copyright {
} display: flex;
align-items: center;
} justify-content: center;
position: absolute;
} left: 0;
bottom: 10vh;
#copyright { width: 100%;
display: flex; z-index: 2;
align-items: center; color: mc('grey', '500');
justify-content: flex-start; font-weight: 400;
position: absolute;
left: 10vw; a {
bottom: 10vh; font-weight: 600;
z-index: 2; color: mc('light-blue', '500');
color: rgba(255,255,255,0.5); margin-left: .25rem;
font-weight: bold; }
.icon { }
font-size: 1.2rem;
margin: 0 8px;
}
a {
opacity: 0.75;
}
}
@include keyframes(bg) {
0% {
@include prefix(transform, scale(1,1));
visibility: visible;
opacity: 0;
},
5% {
opacity: 0.5;
},
33% {
opacity: 0.5;
},
38% {
@include prefix(transform, scale(1.2, 1.2));
opacity: 0;
},
39% {
visibility: hidden;
}
100% {
visibility: hidden;
opacity: 0;
}
}
@include keyframes(headerIntro) {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
} }
...@@ -5,23 +5,9 @@ ...@@ -5,23 +5,9 @@
# https://docs.requarks.io/wiki/install # https://docs.requarks.io/wiki/install
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# Title of this site # Port the main server should listen to
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
title: Wiki
# ---------------------------------------------------------------------
# Full public path to the site, without the trailing slash
# ---------------------------------------------------------------------
# INCLUDE CLIENT PORT IF NOT 80/443!
host: http://localhost
# ---------------------------------------------------------------------
# Port the main server should listen to (80 by default)
# ---------------------------------------------------------------------
# To use process.env.PORT, comment the line below:
port: 80 port: 80
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
...@@ -33,140 +19,38 @@ paths: ...@@ -33,140 +19,38 @@ paths:
data: ./data data: ./data
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# Upload Limits # Database
# ---------------------------------------------------------------------
# In megabytes (MB)
uploads:
maxImageFileSize: 3
maxOtherFileSize: 100
# ---------------------------------------------------------------------
# Site Language
# ---------------------------------------------------------------------
# Possible values: en, de, es, fa, fr, ja, ko, nl, pt, ru, sr, tr or zh
lang: en
# Enable for right to left languages (e.g. arabic):
langRtl: false
# ---------------------------------------------------------------------
# Site Authentication
# ---------------------------------------------------------------------
public: false
auth:
defaultReadAccess: false
local:
enabled: true
google:
enabled: true
clientId: GOOGLE_CLIENT_ID
clientSecret: GOOGLE_CLIENT_SECRET
microsoft:
enabled: true
clientId: MS_APP_ID
clientSecret: MS_APP_SECRET
facebook:
enabled: false
clientId: FACEBOOK_APP_ID
clientSecret: FACEBOOK_APP_SECRET
github:
enabled: false
clientId: GITHUB_CLIENT_ID
clientSecret: GITHUB_CLIENT_SECRET
slack:
enabled: false
clientId: 'SLACK_CLIENT_ID'
clientSecret: 'SLACK_CLIENT_SECRET'
ldap:
enabled: false
url: ldap://serverhost:389
bindDn: cn='root'
bindCredentials: BIND_PASSWORD
searchBase: o=users,o=example.com
searchFilter: (uid={{username}})
tlsEnabled: false
tlsCertPath: C:\example\root_ca_cert.crt
azure:
enabled: false
clientId: APP_ID
clientSecret: APP_SECRET_KEY
resource: '00000002-0000-0000-c000-000000000000'
tenant: 'YOUR_TENANT.onmicrosoft.com'
# ---------------------------------------------------------------------
# Secret key to use when encrypting sessions
# ---------------------------------------------------------------------
# Use a long and unique random string (256-bit keys are perfect!)
sessionSecret: 1234567890abcdefghijklmnopqrstuvxyz
# ---------------------------------------------------------------------
# Database Connection String
# ---------------------------------------------------------------------
# You can also use an ENV variable by using $ENV_VAR_NAME as the value
db: mongodb://localhost:27017/wiki
# ---------------------------------------------------------------------
# Git Connection Info
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
git: db:
url: https://github.com/Organization/Repo host: localhost
branch: master port: 5432
auth: user: wikijs
pass: wikijsrocks
# Type: basic or ssh db: wiki
type: ssh
# Only for Basic authentication:
username: marty
password: MartyMcFly88
# Only for SSH authentication:
privateKey: /etc/wiki/keys/git.pem
sslVerify: true
# Default email to use as commit author
serverEmail: marty@example.com
# Whether to use user email as author in commits
showUserEmail: true
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# Features # Redis
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# You can enable / disable specific features below
features: redis:
linebreaks: true host: localhost
mathjax: true port: 6379
db: 0
password: null
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# External Logging # Background Workers
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# Leave 0 for auto based on CPU cores
externalLogging: workers: 0
bugsnag: false
loggly: false
papertrail: false
rollbar: false
sentry: false
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# Color Theme # High Availability
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# Read the docs BEFORE changing these settings!
theme: ha:
primary: indigo nodeuid: primary
alt: blue-grey readonly: false
viewSource: all # all | write | false
footer: blue-grey
code:
dark: true
colorize: true
![Wiki.js](https://raw.githubusercontent.com/Requarks/wiki-site/1.0/assets/images/logo.png)
# Wiki.js
[![npm](https://img.shields.io/npm/v/wiki.js.svg?style=flat-square)](https://github.com/Requarks)
[![Release](https://img.shields.io/github/release/Requarks/wiki.svg?style=flat-square&maxAge=3600)](https://github.com/Requarks/wiki/releases)
[![License](https://img.shields.io/badge/license-AGPLv3-blue.svg?style=flat-square)](https://github.com/requarks/wiki/blob/master/LICENSE)
This npm package is an installer for Wiki.js.
For information about Wiki.js, including detailed installation steps, read the following links:
- [Official Website](https://wiki.js.org/)
- [Installation Guide](https://wiki.js.org/get-started.html)
- [GitHub Repository](https://github.com/Requarks/wiki)
'use strict'
// =====================================================
// Wiki.js
// Installation Script
// =====================================================
const path = require('path')
const spawn = require('child_process').spawn
const installDir = path.resolve(__dirname, '../..')
const cmd = (process.platform !== 'win32')
? 'curl -s -S -o- https://wiki.js.org/install.sh | bash'
: `PowerShell.exe -NoProfile -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://wiki.js.org/install.ps1'))"`
console.info(`Executing installation script for ${process.platform} platform...`)
let inst = spawn(cmd, [], {
cwd: installDir,
env: process.env,
shell: true,
stdio: 'inherit',
detached: true
})
inst.unref()
{
"name": "wiki.js",
"version": "1.0.5-rev.1",
"description": "A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown",
"main": "install.js",
"scripts": {
"test": "exit 1",
"postinstall": "node install.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Requarks/wiki.git"
},
"keywords": [
"wiki",
"wikis",
"wikijs",
"wiki.js",
"wiki-js",
"docs",
"documentation",
"markdown",
"guides"
],
"author": "Nicolas Giard",
"license": "AGPL-3.0",
"bugs": {
"url": "https://github.com/Requarks/wiki/issues"
},
"homepage": "https://github.com/Requarks/wiki#readme",
"dependencies": {}
}
{ {
"name": "wiki", "name": "wiki",
"version": "1.0.11", "version": "2.0.0",
"description": "A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown", "description": "A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown",
"main": "wiki.js", "main": "wiki.js",
"scripts": { "scripts": {
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
"restart": "node wiki restart", "restart": "node wiki restart",
"build": "node tools/fuse", "build": "node tools/fuse",
"dev": "node tools/fuse -d", "dev": "node tools/fuse -d",
"dev-configure": "node tools/fuse -c",
"test": "jest", "test": "jest",
"postinstall": "opencollective postinstall" "postinstall": "opencollective postinstall"
}, },
...@@ -38,135 +37,143 @@ ...@@ -38,135 +37,143 @@
"node": ">=6.11.1" "node": ">=6.11.1"
}, },
"dependencies": { "dependencies": {
"auto-load": "~3.0.0", "apollo-server-express": "1.1.3",
"axios": "~0.16.2", "auto-load": "3.0.0",
"bcryptjs-then": "~1.0.1", "axios": "0.16.2",
"bluebird": "~3.5.0", "bcryptjs-then": "1.0.1",
"body-parser": "~1.17.2", "bluebird": "3.5.1",
"bunyan": "~1.8.12", "body-parser": "1.18.2",
"cheerio": "~1.0.0-rc.2", "bull": "3.3.0",
"child-process-promise": "~2.2.1", "bunyan": "1.8.12",
"chokidar": "~1.7.0", "cheerio": "1.0.0-rc.2",
"compression": "~1.7.0", "child-process-promise": "2.2.1",
"connect-flash": "~0.1.1", "chokidar": "1.7.0",
"connect-mongo": "~1.3.2", "compression": "1.7.1",
"cookie-parser": "~1.4.3", "connect-flash": "0.1.1",
"cron": "~1.2.1", "connect-redis": "3.3.2",
"diff2html": "~2.3.0", "cookie-parser": "1.4.3",
"execa": "~0.8.0", "diff2html": "2.3.1",
"express": "~4.15.4", "dotize": "^0.2.0",
"execa": "0.8.0",
"express": "4.16.1",
"express-brute": "1.0.1", "express-brute": "1.0.1",
"express-brute-mongoose": "~0.0.9", "express-brute-redis": "0.0.1",
"express-session": "~1.15.5", "express-session": "1.15.6",
"file-type": "~6.1.0", "file-type": "6.2.0",
"filesize.js": "~1.0.2", "filesize.js": "1.0.2",
"follow-redirects": "~1.2.4", "follow-redirects": "1.2.5",
"fs-extra": "~4.0.1", "fs-extra": "4.0.2",
"git-wrapper2-promise": "~0.2.9", "git-wrapper2-promise": "0.2.9",
"highlight.js": "~9.12.0", "graphql": "0.10.5",
"i18next": "~9.0.0", "graphql-tools": "2.2.1",
"i18next-express-middleware": "~1.0.5", "highlight.js": "9.12.0",
"i18next-node-fs-backend": "~1.0.0", "i18next": "9.1.0",
"image-size": "~0.6.0", "i18next-express-middleware": "1.0.7",
"jimp": "~0.2.28", "i18next-localstorage-cache": "1.1.1",
"js-yaml": "~3.9.1", "i18next-node-fs-backend": "1.0.0",
"jsonwebtoken": "~7.4.3", "image-size": "0.6.1",
"klaw": "~2.1.0", "ioredis": "3.1.4",
"levelup": "~1.3.9", "jimp": "0.2.28",
"lodash": "~4.17.4", "js-yaml": "3.10.0",
"markdown-it": "~8.4.0", "jsonwebtoken": "8.0.1",
"markdown-it-abbr": "~1.0.4", "klaw": "2.1.0",
"markdown-it-anchor": "~4.0.0", "lodash": "4.17.4",
"markdown-it-attrs": "~1.1.0", "markdown-it": "8.4.0",
"markdown-it-emoji": "~1.4.0", "markdown-it-abbr": "1.0.4",
"markdown-it-expand-tabs": "~1.0.12", "markdown-it-anchor": "4.0.0",
"markdown-it-attrs": "1.2.0",
"markdown-it-emoji": "1.4.0",
"markdown-it-expand-tabs": "1.0.12",
"markdown-it-external-links": "0.0.6", "markdown-it-external-links": "0.0.6",
"markdown-it-footnote": "~3.0.1", "markdown-it-footnote": "3.0.1",
"markdown-it-mathjax": "~2.0.0", "markdown-it-mathjax": "2.0.0",
"markdown-it-task-lists": "~2.0.1", "markdown-it-task-lists": "2.0.1",
"mathjax-node": "~1.2.0", "mathjax-node": "1.2.1",
"memdown": "~1.2.4", "mime-types": "2.1.17",
"mime-types": "~2.1.16", "moment": "2.18.1",
"moment": "~2.18.1", "moment-timezone": "0.5.13",
"moment-timezone": "~0.5.13", "multer": "1.3.0",
"mongodb": "~2.2.31", "node-2fa": "1.1.2",
"mongoose": "~4.11.9", "node-graceful": "0.2.3",
"multer": "~1.3.0", "ora": "1.3.0",
"node-2fa": "~1.1.2", "passport": "0.4.0",
"node-graceful": "~0.2.3",
"opencollective": "~1.0.3",
"ora": "~1.3.0",
"passport": "~0.4.0",
"passport-azure-ad-oauth2": "0.0.4", "passport-azure-ad-oauth2": "0.0.4",
"passport-facebook": "~2.1.1", "passport-facebook": "2.1.1",
"passport-github2": "~0.1.10", "passport-github2": "0.1.11",
"passport-google-oauth20": "~1.0.0", "passport-google-oauth20": "1.0.0",
"passport-ldapauth": "~2.0.0", "passport-ldapauth": "2.0.0",
"passport-local": "~1.0.0", "passport-local": "1.0.0",
"passport-slack": "0.0.7", "passport-slack": "0.0.7",
"passport-windowslive": "~1.0.2", "passport-windowslive": "1.0.2",
"passport.socketio": "~3.7.0", "pg": "6.4.2",
"pm2": "~2.6.1", "pg-hstore": "2.3.2",
"pug": "~2.0.0-rc.3", "pg-promise": "6.10.3",
"read-chunk": "~2.1.0", "pm2": "2.7.1",
"remove-markdown": "~0.2.2", "pug": "2.0.0-rc.4",
"request": "~2.81.0", "qr-image": "3.2.0",
"search-index-adder": "~0.3.9", "read-chunk": "2.1.0",
"search-index-searcher": "~0.2.10", "remove-markdown": "0.2.2",
"semver": "~5.4.1", "request": "2.83.0",
"serve-favicon": "~2.4.3", "semver": "5.4.1",
"simplemde": "~1.11.2", "sequelize": "4.13.5",
"socket.io": "~2.0.2", "serve-favicon": "2.4.5",
"stopword": "~0.1.6", "simplemde": "1.11.2",
"stream-to-promise": "~2.2.0", "stream-to-promise": "2.2.0",
"tar": "~4.0.1", "tar": "4.0.1",
"through2": "~2.0.3", "through2": "2.0.3",
"validator": "~8.1.0", "validator": "9.0.0",
"validator-as-promised": "~1.0.2", "validator-as-promised": "1.0.2",
"winston": "~2.3.1", "winston": "2.4.0",
"yargs": "~8.0.1" "yargs": "9.0.1"
}, },
"devDependencies": { "devDependencies": {
"@glimpse/glimpse": "~0.22.15", "@glimpse/glimpse": "0.22.15",
"@panter/vue-i18next": "~0.5.0", "@panter/vue-i18next": "0.6.1",
"babel-cli": "~6.26.0", "apollo-client": "^1.9.3",
"babel-jest": "~20.0.3", "autoprefixer": "7.1.5",
"babel-plugin-transform-object-assign": "~6.22.0", "babel-cli": "6.26.0",
"babel-preset-es2015": "~6.24.1", "babel-core": "6.26.0",
"brace": "~0.10.0", "babel-jest": "21.2.0",
"colors": "~1.1.2", "babel-preset-env": "1.6.0",
"consolidate": "~0.14.5", "babel-preset-es2015": "6.24.1",
"eslint": "~4.5.0", "babel-preset-stage-2": "6.24.1",
"eslint-config-standard": "~10.2.1", "brace": "0.10.0",
"eslint-plugin-import": "~2.7.0", "colors": "1.1.2",
"eslint-plugin-node": "~5.1.0", "consolidate": "0.14.5",
"eslint-plugin-promise": "~3.5.0", "eslint": "4.8.0",
"eslint-plugin-standard": "~3.0.1", "eslint-config-requarks": "1.0.7",
"fuse-box": "~2.2.2", "eslint-config-standard": "10.2.1",
"i18next-xhr-backend": "~1.4.2", "eslint-plugin-import": "2.7.0",
"jest": "~20.0.4", "eslint-plugin-node": "5.2.0",
"jest-junit": "~3.1.0", "eslint-plugin-promise": "3.5.0",
"jquery": "~3.2.1", "eslint-plugin-standard": "3.0.1",
"jquery-contextmenu": "~2.5.0", "fuse-box": "2.3.3",
"jquery-simple-upload": "~1.0.0", "graphql-tag": "^2.4.2",
"jquery-smooth-scroll": "~2.2.0", "i18next-xhr-backend": "1.4.3",
"jquery-sticky": "~1.0.4", "jest": "21.2.1",
"lodash-cli": "~4.17.4", "jquery": "3.2.1",
"lodash-es": "~4.17.4", "jquery-contextmenu": "2.6.2",
"node-sass": "~4.5.3", "jquery-simple-upload": "1.0.0",
"nodemon": "~1.11.0", "js-cookie": "2.1.4",
"pug-lint": "~2.4.0", "node-sass": "4.5.3",
"twemoji-awesome": "~1.0.6", "nodemon": "1.12.1",
"typescript": "~2.5.2", "postcss-selector-parser": "2.2.3",
"uglify-es": "~3.0.28", "pug-lint": "2.5.0",
"vee-validate": "~2.0.0-rc.14", "twemoji-awesome": "1.0.6",
"vue": "~2.4.2", "typescript": "2.5.3",
"vue-clipboards": "~1.1.0", "uglify-es": "3.1.3",
"vue-lodash": "~1.0.3", "vee-validate": "2.0.0-rc.18",
"vue-resource": "~1.3.4", "vue": "2.4.4",
"vue-template-compiler": "~2.4.2", "vue-clipboards": "1.1.0",
"vue-template-es2015-compiler": "~1.5.3", "vue-hot-reload-api": "2.1.1",
"vuex": "~2.4.0" "vue-lodash": "1.0.4",
"vue-material": "^0.7.5",
"vue-resource": "1.3.4",
"vue-simple-breakpoints": "1.0.2",
"vue-template-compiler": "2.4.4",
"vue-template-es2015-compiler": "1.5.3",
"vuex": "2.4.1",
"vuex-persistedstate": "2.0.0"
}, },
"jest": { "jest": {
"testResultsProcessor": "./node_modules/jest-junit", "testResultsProcessor": "./node_modules/jest-junit",
......
// ===========================================
// Wiki.js - Background Agent
// 1.0.0
// Licensed under AGPLv3
// ===========================================
const path = require('path')
const ROOTPATH = process.cwd()
const SERVERPATH = path.join(ROOTPATH, 'server')
global.ROOTPATH = ROOTPATH
global.SERVERPATH = SERVERPATH
const IS_DEBUG = process.env.NODE_ENV === 'development'
let appconf = require('./libs/config')()
global.appconfig = appconf.config
global.appdata = appconf.data
// ----------------------------------------
// Load Winston
// ----------------------------------------
global.winston = require('./libs/logger')(IS_DEBUG, 'AGENT')
// ----------------------------------------
// Load global modules
// ----------------------------------------
global.winston.info('Background Agent is initializing...')
global.db = require('./libs/db').init()
global.upl = require('./libs/uploads-agent').init()
global.git = require('./libs/git').init()
global.entries = require('./libs/entries').init()
global.lang = require('i18next')
global.mark = require('./libs/markdown')
// ----------------------------------------
// Load modules
// ----------------------------------------
const moment = require('moment')
const Promise = require('bluebird')
const fs = Promise.promisifyAll(require('fs-extra'))
const klaw = require('klaw')
const Cron = require('cron').CronJob
const i18nBackend = require('i18next-node-fs-backend')
const entryHelper = require('./helpers/entry')
// ----------------------------------------
// Localization Engine
// ----------------------------------------
global.lang
.use(i18nBackend)
.init({
load: 'languageOnly',
ns: ['common', 'admin', 'auth', 'errors', 'git'],
defaultNS: 'common',
saveMissing: false,
preload: [appconfig.lang],
lng: appconfig.lang,
fallbackLng: 'en',
backend: {
loadPath: path.join(SERVERPATH, 'locales/{{lng}}/{{ns}}.json')
}
})
// ----------------------------------------
// Start Cron
// ----------------------------------------
let job
let jobIsBusy = false
let jobUplWatchStarted = false
global.db.onReady.then(() => {
return global.db.Entry.remove({})
}).then(() => {
job = new Cron({
cronTime: '0 */5 * * * *',
onTick: () => {
// Make sure we don't start two concurrent jobs
if (jobIsBusy) {
global.winston.warn('Previous job has not completed gracefully or is still running! Skipping for now. (This is not normal, you should investigate)')
return
}
global.winston.info('Running all jobs...')
jobIsBusy = true
// Prepare async job collector
let jobs = []
let repoPath = path.resolve(ROOTPATH, appconfig.paths.repo)
let dataPath = path.resolve(ROOTPATH, appconfig.paths.data)
let uploadsTempPath = path.join(dataPath, 'temp-upload')
// ----------------------------------------
// REGULAR JOBS
// ----------------------------------------
//* ****************************************
// -> Sync with Git remote
//* ****************************************
jobs.push(global.git.resync().then(() => {
// -> Stream all documents
let cacheJobs = []
let jobCbStreamDocsResolve = null
let jobCbStreamDocs = new Promise((resolve, reject) => {
jobCbStreamDocsResolve = resolve
})
klaw(repoPath).on('data', function (item) {
if (path.extname(item.path) === '.md' && path.basename(item.path) !== 'README.md') {
let entryPath = entryHelper.parsePath(entryHelper.getEntryPathFromFullPath(item.path))
let cachePath = entryHelper.getCachePath(entryPath)
// -> Purge outdated cache
cacheJobs.push(
fs.statAsync(cachePath).then((st) => {
return moment(st.mtime).isBefore(item.stats.mtime) ? 'expired' : 'active'
}).catch((err) => {
return (err.code !== 'EEXIST') ? err : 'new'
}).then((fileStatus) => {
// -> Delete expired cache file
if (fileStatus === 'expired') {
return fs.unlinkAsync(cachePath).return(fileStatus)
}
return fileStatus
}).then((fileStatus) => {
// -> Update cache and search index
if (fileStatus !== 'active') {
return global.entries.updateCache(entryPath).then(entry => {
process.send({
action: 'searchAdd',
content: entry
})
return true
})
}
return true
})
)
}
}).on('end', () => {
jobCbStreamDocsResolve(Promise.all(cacheJobs))
})
return jobCbStreamDocs
}))
//* ****************************************
// -> Clear failed temporary upload files
//* ****************************************
jobs.push(
fs.readdirAsync(uploadsTempPath).then((ls) => {
let fifteenAgo = moment().subtract(15, 'minutes')
return Promise.map(ls, (f) => {
return fs.statAsync(path.join(uploadsTempPath, f)).then((s) => { return { filename: f, stat: s } })
}).filter((s) => { return s.stat.isFile() }).then((arrFiles) => {
return Promise.map(arrFiles, (f) => {
if (moment(f.stat.ctime).isBefore(fifteenAgo, 'minute')) {
return fs.unlinkAsync(path.join(uploadsTempPath, f.filename))
} else {
return true
}
})
})
})
)
// ----------------------------------------
// Run
// ----------------------------------------
Promise.all(jobs).then(() => {
global.winston.info('All jobs completed successfully! Going to sleep for now.')
if (!jobUplWatchStarted) {
jobUplWatchStarted = true
global.upl.initialScan().then(() => {
job.start()
})
}
return true
}).catch((err) => {
global.winston.error('One or more jobs have failed: ', err)
}).finally(() => {
jobIsBusy = false
})
},
start: false,
timeZone: 'UTC',
runOnInit: true
})
})
// ----------------------------------------
// Shutdown gracefully
// ----------------------------------------
process.on('disconnect', () => {
global.winston.warn('Lost connection to main server. Exiting...')
job.stop()
process.exit()
})
process.on('exit', () => {
job.stop()
})
...@@ -5,67 +5,49 @@ ...@@ -5,67 +5,49 @@
name: Wiki.js name: Wiki.js
defaults: defaults:
config: config:
title: Wiki
host: http://localhost
port: 80 port: 80
paths: paths:
repo: ./repo repo: ./repo
data: ./data data: ./data
uploads: db:
maxImageFileSize: 3, host: localhost
maxOtherFileSize: 100 port: 5432
lang: en user: wikijs
langRtl: false pass: wikijsrocks
public: false db: wiki
auth: redis:
defaultReadAccess: false host: localhost
local: port: 6379
enabled: true db: 0
microsoft: password: null
enabled: false workers: 0
google: ha:
enabled: false nodeuid: primary
facebook: readonly: false
enabled: false site:
github: path: ''
enabled: false lang: en
slack: title: Wiki.js
enabled: false configNamespaces:
ldap: - auth
enabled: false - features
azure: - git
enabled: false - logging
db: mongodb://localhost/wiki - site
sessionSecret: null - theme
admin: null - uploads
git: queues:
url: null - gitSync
branch: master - uplClearTemp
auth: authProviders:
type: basic - local
username: null - microsoft
password: null - google
privateKey: null - facebook
sslVerify: true - github
serverEmail: wiki@example.com - slack
showUserEmail: true - ldap
features: - azure
linebreaks: true
mathjax: true
externalLogging:
bugsnap: false
loggly: false
papertrail: false
rollbar: false
sentry: false
theme:
primary: indigo
alt: blue-grey
footer: blue-grey
viewSource: false
code:
dark: true
colorize: true
colors: colors:
- red - red
- pink - pink
...@@ -109,6 +91,12 @@ langs: ...@@ -109,6 +91,12 @@ langs:
id: ko id: ko
name: Korean - 한국어 name: Korean - 한국어
- -
id: fa
name: Persian (Fārsi) - فارسی
-
id: pt
name: Portuguese - Português
-
id: ru id: ru
name: Russian - Русский name: Russian - Русский
- -
......
'use strict'
/* global wiki */
// ------------------------------------
// Azure AD Account
// ------------------------------------
const AzureAdOAuth2Strategy = require('passport-azure-ad-oauth2').Strategy
module.exports = {
key: 'azure',
title: 'Azure Active Directory',
useForm: false,
props: ['clientId', 'clientSecret', 'callbackURL', 'resource', 'tenant'],
init (passport, conf) {
const jwt = require('jsonwebtoken')
passport.use('azure_ad_oauth2',
new AzureAdOAuth2Strategy({
clientID: conf.clientId,
clientSecret: conf.clientSecret,
callbackURL: conf.callbackURL,
resource: conf.resource,
tenant: conf.tenant
}, (accessToken, refreshToken, params, profile, cb) => {
let waadProfile = jwt.decode(params.id_token)
waadProfile.id = waadProfile.oid
waadProfile.provider = 'azure'
wiki.db.User.processProfile(waadProfile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
}
'use strict'
/* global wiki */
// ------------------------------------
// Facebook Account
// ------------------------------------
const FacebookStrategy = require('passport-facebook').Strategy
module.exports = {
key: 'facebook',
title: 'Facebook',
useForm: false,
props: ['clientId', 'clientSecret', 'callbackURL'],
init (passport, conf) {
passport.use('facebook',
new FacebookStrategy({
clientID: conf.clientId,
clientSecret: conf.clientSecret,
callbackURL: conf.callbackURL,
profileFields: ['id', 'displayName', 'email']
}, function (accessToken, refreshToken, profile, cb) {
wiki.db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
}
'use strict'
/* global wiki */
// ------------------------------------
// GitHub Account
// ------------------------------------
const GitHubStrategy = require('passport-github2').Strategy
module.exports = {
key: 'github',
title: 'GitHub',
useForm: false,
props: ['clientId', 'clientSecret', 'callbackURL'],
init (passport, conf) {
passport.use('github',
new GitHubStrategy({
clientID: conf.clientId,
clientSecret: conf.clientSecret,
callbackURL: conf.callbackURL,
scope: ['user:email']
}, (accessToken, refreshToken, profile, cb) => {
wiki.db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
}
'use strict'
/* global wiki */
// ------------------------------------
// Google ID Account
// ------------------------------------
const GoogleStrategy = require('passport-google-oauth20').Strategy
module.exports = {
key: 'google',
title: 'Google ID',
useForm: false,
props: ['clientId', 'clientSecret', 'callbackURL'],
init (passport, conf) {
passport.use('google',
new GoogleStrategy({
clientID: conf.clientId,
clientSecret: conf.clientSecret,
callbackURL: conf.callbackURL
}, (accessToken, refreshToken, profile, cb) => {
wiki.db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
}
'use strict'
/* global wiki */
// ------------------------------------
// LDAP Account
// ------------------------------------
const LdapStrategy = require('passport-ldapauth').Strategy
const fs = require('fs')
module.exports = {
key: 'ldap',
title: 'LDAP / Active Directory',
useForm: true,
props: ['url', 'bindDn', 'bindCredentials', 'searchBase', 'searchFilter', 'tlsEnabled', 'tlsCertPath'],
init (passport, conf) {
passport.use('ldapauth',
new LdapStrategy({
server: {
url: conf.url,
bindDn: conf.bindDn,
bindCredentials: conf.bindCredentials,
searchBase: conf.searchBase,
searchFilter: conf.searchFilter,
searchAttributes: ['displayName', 'name', 'cn', 'mail'],
tlsOptions: (conf.tlsEnabled) ? {
ca: [
fs.readFileSync(conf.tlsCertPath)
]
} : {}
},
usernameField: 'email',
passReqToCallback: false
}, (profile, cb) => {
profile.provider = 'ldap'
profile.id = profile.dn
wiki.db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
}
'use strict'
/* global wiki */
// ------------------------------------
// Local Account
// ------------------------------------
const LocalStrategy = require('passport-local').Strategy
module.exports = {
key: 'local',
title: 'Local',
useForm: true,
props: [],
init (passport, conf) {
passport.use('local',
new LocalStrategy({
usernameField: 'email',
passwordField: 'password'
}, (uEmail, uPassword, done) => {
wiki.db.User.findOne({ email: uEmail, provider: 'local' }).then((user) => {
if (user) {
return user.validatePassword(uPassword).then(() => {
return done(null, user) || true
}).catch((err) => {
return done(err, null)
})
} else {
return done(new Error('INVALID_LOGIN'), null)
}
}).catch((err) => {
done(err, null)
})
}
))
}
}
'use strict'
/* global wiki */
// ------------------------------------
// Microsoft Account
// ------------------------------------
const WindowsLiveStrategy = require('passport-windowslive').Strategy
module.exports = {
key: 'microsoft',
title: 'Microsoft Account',
useForm: false,
props: ['clientId', 'clientSecret', 'callbackURL'],
init (passport, conf) {
passport.use('microsoft',
new WindowsLiveStrategy({
clientID: conf.clientId,
clientSecret: conf.clientSecret,
callbackURL: conf.callbackURL
}, function (accessToken, refreshToken, profile, cb) {
wiki.db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
}
'use strict'
/* global wiki */
// ------------------------------------
// Slack Account
// ------------------------------------
const SlackStrategy = require('passport-slack').Strategy
module.exports = {
key: 'slack',
title: 'Slack',
useForm: false,
props: ['clientId', 'clientSecret', 'callbackURL'],
init (passport, conf) {
passport.use('slack',
new SlackStrategy({
clientID: conf.clientId,
clientSecret: conf.clientSecret,
callbackURL: conf.callbackURL
}, (accessToken, refreshToken, profile, cb) => {
wiki.db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
}
'use strict' const path = require('path')
module.exports = (port, spinner) => { /* global wiki */
const path = require('path')
const ROOTPATH = process.cwd() module.exports = () => {
const SERVERPATH = path.join(ROOTPATH, 'server') wiki.config.site = {
const IS_DEBUG = process.env.NODE_ENV === 'development' path: '',
title: 'Wiki.js'
}
// ---------------------------------------- // ----------------------------------------
// Load modules // Load modules
...@@ -26,28 +27,30 @@ module.exports = (port, spinner) => { ...@@ -26,28 +27,30 @@ module.exports = (port, spinner) => {
// Define Express App // Define Express App
// ---------------------------------------- // ----------------------------------------
var app = express() let app = express()
app.use(compression()) app.use(compression())
var server let server
// ---------------------------------------- // ----------------------------------------
// Public Assets // Public Assets
// ---------------------------------------- // ----------------------------------------
app.use(favicon(path.join(ROOTPATH, 'assets', 'favicon.ico'))) app.use(favicon(path.join(wiki.ROOTPATH, 'assets', 'favicon.ico')))
app.use(express.static(path.join(ROOTPATH, 'assets'))) app.use(express.static(path.join(wiki.ROOTPATH, 'assets')))
// ---------------------------------------- // ----------------------------------------
// View Engine Setup // View Engine Setup
// ---------------------------------------- // ----------------------------------------
app.set('views', path.join(SERVERPATH, 'views')) app.set('views', path.join(wiki.SERVERPATH, 'views'))
app.set('view engine', 'pug') app.set('view engine', 'pug')
app.use(bodyParser.json()) app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false })) app.use(bodyParser.urlencoded({ extended: false }))
app.locals.config = wiki.config
app.locals.data = wiki.data
app.locals._ = require('lodash') app.locals._ = require('lodash')
// ---------------------------------------- // ----------------------------------------
...@@ -55,21 +58,8 @@ module.exports = (port, spinner) => { ...@@ -55,21 +58,8 @@ module.exports = (port, spinner) => {
// ---------------------------------------- // ----------------------------------------
app.get('*', (req, res) => { app.get('*', (req, res) => {
let langs = [] fs.readJsonAsync(path.join(wiki.ROOTPATH, 'package.json')).then(packageObj => {
let conf = {} res.render('configure/index', { packageObj })
try {
langs = yaml.safeLoad(fs.readFileSync(path.join(SERVERPATH, 'app/data.yml'), 'utf8')).langs
conf = yaml.safeLoad(fs.readFileSync(path.join(ROOTPATH, 'config.yml'), 'utf8'))
} catch (err) {
console.error(err)
}
res.render('configure/index', {
langs,
conf,
runmode: {
staticPort: (process.env.WIKI_JS_HEROKU || process.env.WIKI_JS_DOCKER),
staticMongo: (!_.isNil(process.env.WIKI_JS_HEROKU))
}
}) })
}) })
...@@ -80,15 +70,15 @@ module.exports = (port, spinner) => { ...@@ -80,15 +70,15 @@ module.exports = (port, spinner) => {
Promise.mapSeries([ Promise.mapSeries([
() => { () => {
const semver = require('semver') const semver = require('semver')
if (!semver.satisfies(semver.clean(process.version), '>=6.9.0')) { if (!semver.satisfies(semver.clean(process.version), '>=6.11.1')) {
throw new Error('Node.js version is too old. Minimum is v6.6.0.') throw new Error('Node.js version is too old. Minimum is 6.11.1.')
} }
return 'Node.js ' + process.version + ' detected. Minimum is v6.9.0.' return 'Node.js ' + process.version + ' detected. Minimum is 6.11.1.'
}, },
() => { () => {
return Promise.try(() => { return Promise.try(() => {
require('crypto') require('crypto')
}).catch(err => { // eslint-disable-line handle-callback-err }).catch(err => {
throw new Error('Crypto Node.js module is not available.') throw new Error('Crypto Node.js module is not available.')
}).return('Node.js Crypto module is available.') }).return('Node.js Crypto module is available.')
}, },
...@@ -102,24 +92,24 @@ module.exports = (port, spinner) => { ...@@ -102,24 +92,24 @@ module.exports = (port, spinner) => {
} }
let gitver = _.head(stdout.match(/[\d]+\.[\d]+(\.[\d]+)?/gi)) let gitver = _.head(stdout.match(/[\d]+\.[\d]+(\.[\d]+)?/gi))
if (!gitver || !semver.satisfies(semver.clean(gitver), '>=2.7.4')) { if (!gitver || !semver.satisfies(semver.clean(gitver), '>=2.7.4')) {
reject(new Error('Git version is too old. Minimum is v2.7.4.')) reject(new Error('Git version is too old. Minimum is 2.7.4.'))
} }
resolve('Git v' + gitver + ' detected. Minimum is v2.7.4.') resolve('Git ' + gitver + ' detected. Minimum is 2.7.4.')
}) })
}) })
}, },
() => { () => {
const os = require('os') const os = require('os')
if (os.totalmem() < 1000 * 1000 * 768) { if (os.totalmem() < 1000 * 1000 * 512) {
throw new Error('Not enough memory. Minimum is 768 MB.') throw new Error('Not enough memory. Minimum is 512 MB.')
} }
return _.round(os.totalmem() / (1024 * 1024)) + ' MB of system memory available. Minimum is 768 MB.' return _.round(os.totalmem() / (1024 * 1024)) + ' MB of system memory available. Minimum is 512 MB.'
}, },
() => { () => {
let fs = require('fs') let fs = require('fs')
return Promise.try(() => { return Promise.try(() => {
fs.accessSync(path.join(ROOTPATH, 'config.yml'), (fs.constants || fs).W_OK) fs.accessSync(path.join(wiki.ROOTPATH, 'config.yml'), (fs.constants || fs).W_OK)
}).catch(err => { // eslint-disable-line handle-callback-err }).catch(err => {
throw new Error('config.yml file is not writable by Node.js process or was not created properly.') throw new Error('config.yml file is not writable by Node.js process or was not created properly.')
}).return('config.yml is writable by the setup process.') }).return('config.yml is writable by the setup process.')
} }
...@@ -131,51 +121,14 @@ module.exports = (port, spinner) => { ...@@ -131,51 +121,14 @@ module.exports = (port, spinner) => {
}) })
/** /**
* Check the DB connection
*/
app.post('/dbcheck', (req, res) => {
let mongo = require('mongodb').MongoClient
let mongoURI = cfgHelper.parseConfigValue(req.body.db)
mongo.connect(mongoURI, {
autoReconnect: false,
reconnectTries: 2,
reconnectInterval: 1000,
connectTimeoutMS: 5000,
socketTimeoutMS: 5000
}, (err, db) => {
if (err === null) {
// Try to create a test collection
db.createCollection('test', (err, results) => {
if (err === null) {
// Try to drop test collection
db.dropCollection('test', (err, results) => {
if (err === null) {
res.json({ ok: true })
} else {
res.json({ ok: false, error: 'Unable to delete test collection. Verify permissions. ' + err.message })
}
db.close()
})
} else {
res.json({ ok: false, error: 'Unable to create test collection. Verify permissions. ' + err.message })
db.close()
}
})
} else {
res.json({ ok: false, error: err.message })
}
})
})
/**
* Check the Git connection * Check the Git connection
*/ */
app.post('/gitcheck', (req, res) => { app.post('/gitcheck', (req, res) => {
const exec = require('execa') const exec = require('execa')
const url = require('url') const url = require('url')
const dataDir = path.resolve(ROOTPATH, cfgHelper.parseConfigValue(req.body.pathData)) const dataDir = path.resolve(wiki.ROOTPATH, cfgHelper.parseConfigValue(req.body.pathData))
const gitDir = path.resolve(ROOTPATH, cfgHelper.parseConfigValue(req.body.pathRepo)) const gitDir = path.resolve(wiki.ROOTPATH, cfgHelper.parseConfigValue(req.body.pathRepo))
let gitRemoteUrl = '' let gitRemoteUrl = ''
...@@ -315,7 +268,7 @@ module.exports = (port, spinner) => { ...@@ -315,7 +268,7 @@ module.exports = (port, spinner) => {
} }
}) })
}), }),
fs.readFileAsync(path.join(ROOTPATH, 'config.yml'), 'utf8').then(confRaw => { fs.readFileAsync(path.join(wiki.ROOTPATH, 'config.yml'), 'utf8').then(confRaw => {
let conf = yaml.safeLoad(confRaw) let conf = yaml.safeLoad(confRaw)
conf.title = req.body.title conf.title = req.body.title
conf.host = req.body.host conf.host = req.body.host
...@@ -356,12 +309,12 @@ module.exports = (port, spinner) => { ...@@ -356,12 +309,12 @@ module.exports = (port, spinner) => {
return crypto.randomBytesAsync(32).then(buf => { return crypto.randomBytesAsync(32).then(buf => {
conf.sessionSecret = buf.toString('hex') conf.sessionSecret = buf.toString('hex')
confRaw = yaml.safeDump(conf) confRaw = yaml.safeDump(conf)
return fs.writeFileAsync(path.join(ROOTPATH, 'config.yml'), confRaw) return fs.writeFileAsync(path.join(wiki.ROOTPATH, 'config.yml'), confRaw)
}) })
}) })
).then(() => { ).then(() => {
if (process.env.IS_HEROKU) { if (process.env.IS_HEROKU) {
return fs.outputJsonAsync(path.join(SERVERPATH, 'app/heroku.json'), { configured: true }) return fs.outputJsonAsync(path.join(wiki.SERVERPATH, 'app/heroku.json'), { configured: true })
} else { } else {
return true return true
} }
...@@ -377,7 +330,7 @@ module.exports = (port, spinner) => { ...@@ -377,7 +330,7 @@ module.exports = (port, spinner) => {
*/ */
app.post('/restart', (req, res) => { app.post('/restart', (req, res) => {
res.status(204).end() res.status(204).end()
server.destroy(() => { /* server.destroy(() => {
spinner.text = 'Setup wizard terminated. Restarting in normal mode...' spinner.text = 'Setup wizard terminated. Restarting in normal mode...'
_.delay(() => { _.delay(() => {
const exec = require('execa') const exec = require('execa')
...@@ -386,7 +339,7 @@ module.exports = (port, spinner) => { ...@@ -386,7 +339,7 @@ module.exports = (port, spinner) => {
process.exit(0) process.exit(0)
}) })
}, 1000) }, 1000)
}) }) */
}) })
// ---------------------------------------- // ----------------------------------------
...@@ -403,21 +356,20 @@ module.exports = (port, spinner) => { ...@@ -403,21 +356,20 @@ module.exports = (port, spinner) => {
res.status(err.status || 500) res.status(err.status || 500)
res.send({ res.send({
message: err.message, message: err.message,
error: IS_DEBUG ? err : {} error: wiki.IS_DEBUG ? err : {}
}) })
spinner.fail(err.message) wiki.logger.error(err.message)
process.exit(1)
}) })
// ---------------------------------------- // ----------------------------------------
// Start HTTP server // Start HTTP server
// ---------------------------------------- // ----------------------------------------
spinner.text = 'Starting HTTP server...' wiki.logger.info(`HTTP Server on port: ${wiki.config.port}`)
app.set('port', port) app.set('port', wiki.config.port)
server = http.createServer(app) server = http.createServer(app)
server.listen(port) server.listen(wiki.config.port)
var openConnections = [] var openConnections = []
...@@ -443,10 +395,10 @@ module.exports = (port, spinner) => { ...@@ -443,10 +395,10 @@ module.exports = (port, spinner) => {
switch (error.code) { switch (error.code) {
case 'EACCES': case 'EACCES':
spinner.fail('Listening on port ' + port + ' requires elevated privileges!') wiki.logger.error('Listening on port ' + wiki.config.port + ' requires elevated privileges!')
return process.exit(1) return process.exit(1)
case 'EADDRINUSE': case 'EADDRINUSE':
spinner.fail('Port ' + port + ' is already in use!') wiki.logger.error('Port ' + wiki.config.port + ' is already in use!')
return process.exit(1) return process.exit(1)
default: default:
throw error throw error
...@@ -454,6 +406,6 @@ module.exports = (port, spinner) => { ...@@ -454,6 +406,6 @@ module.exports = (port, spinner) => {
}) })
server.on('listening', () => { server.on('listening', () => {
spinner.text = 'Browse to http://localhost:' + port + ' to configure Wiki.js!' wiki.logger.info('HTTP Server: RUNNING')
}) })
} }
'use strict' 'use strict'
/* global db, lang, rights, winston */ /* global wiki */
var express = require('express') var express = require('express')
var router = express.Router() var router = express.Router()
...@@ -33,14 +33,14 @@ router.post('/profile', (req, res) => { ...@@ -33,14 +33,14 @@ router.post('/profile', (req, res) => {
return res.render('error-forbidden') return res.render('error-forbidden')
} }
return db.User.findById(req.user.id).then((usr) => { return wiki.db.User.findById(req.user.id).then((usr) => {
usr.name = _.trim(req.body.name) usr.name = _.trim(req.body.name)
if (usr.provider === 'local' && req.body.password !== '********') { if (usr.provider === 'local' && req.body.password !== '********') {
let nPwd = _.trim(req.body.password) let nPwd = _.trim(req.body.password)
if (nPwd.length < 6) { if (nPwd.length < 6) {
return Promise.reject(new Error('New Password too short!')) return Promise.reject(new Error('New Password too short!'))
} else { } else {
return db.User.hashPassword(nPwd).then((pwd) => { return wiki.db.User.hashPassword(nPwd).then((pwd) => {
usr.password = pwd usr.password = pwd
return usr.save() return usr.save()
}) })
...@@ -61,9 +61,9 @@ router.get('/stats', (req, res) => { ...@@ -61,9 +61,9 @@ router.get('/stats', (req, res) => {
} }
Promise.all([ Promise.all([
db.Entry.count(), wiki.db.Entry.count(),
db.UplFile.count(), wiki.db.UplFile.count(),
db.User.count() wiki.db.User.count()
]).spread((totalEntries, totalUploads, totalUsers) => { ]).spread((totalEntries, totalUploads, totalUsers) => {
return res.render('pages/admin/stats', { return res.render('pages/admin/stats', {
totalEntries, totalUploads, totalUsers, adminTab: 'stats' totalEntries, totalUploads, totalUsers, adminTab: 'stats'
...@@ -78,7 +78,7 @@ router.get('/users', (req, res) => { ...@@ -78,7 +78,7 @@ router.get('/users', (req, res) => {
return res.render('error-forbidden') return res.render('error-forbidden')
} }
db.User.find({}) wiki.db.User.find({})
.select('-password -rights') .select('-password -rights')
.sort('name email') .sort('name email')
.exec().then((usrs) => { .exec().then((usrs) => {
...@@ -95,7 +95,7 @@ router.get('/users/:id', (req, res) => { ...@@ -95,7 +95,7 @@ router.get('/users/:id', (req, res) => {
return res.render('error-forbidden') return res.render('error-forbidden')
} }
db.User.findById(req.params.id) wiki.db.User.findById(req.params.id)
.select('-password -providerId') .select('-password -providerId')
.exec().then((usr) => { .exec().then((usr) => {
let usrOpts = { let usrOpts = {
...@@ -137,12 +137,12 @@ router.post('/users/create', (req, res) => { ...@@ -137,12 +137,12 @@ router.post('/users/create', (req, res) => {
return res.status(400).json({ msg: 'Name is missing' }) return res.status(400).json({ msg: 'Name is missing' })
} }
db.User.findOne({ email: nUsr.email, provider: nUsr.provider }).then(exUsr => { wiki.db.User.findOne({ email: nUsr.email, provider: nUsr.provider }).then(exUsr => {
if (exUsr) { if (exUsr) {
return res.status(400).json({ msg: 'User already exists!' }) || true return res.status(400).json({ msg: 'User already exists!' }) || true
} }
let pwdGen = (nUsr.provider === 'local') ? db.User.hashPassword(nUsr.password) : Promise.resolve(true) let pwdGen = (nUsr.provider === 'local') ? wiki.db.User.hashPassword(nUsr.password) : Promise.resolve(true)
return pwdGen.then(nPwd => { return pwdGen.then(nPwd => {
if (nUsr.provider !== 'local') { if (nUsr.provider !== 'local') {
nUsr.password = '' nUsr.password = ''
...@@ -158,37 +158,37 @@ router.post('/users/create', (req, res) => { ...@@ -158,37 +158,37 @@ router.post('/users/create', (req, res) => {
deny: false deny: false
}] }]
return db.User.create(nUsr).then(() => { return wiki.db.User.create(nUsr).then(() => {
return res.json({ ok: true }) return res.json({ ok: true })
}) })
}).catch(err => { }).catch(err => {
winston.warn(err) wiki.logger.warn(err)
return res.status(500).json({ msg: err }) return res.status(500).json({ msg: err })
}) })
}).catch(err => { }).catch(err => {
winston.warn(err) wiki.logger.warn(err)
return res.status(500).json({ msg: err }) return res.status(500).json({ msg: err })
}) })
}) })
router.post('/users/:id', (req, res) => { router.post('/users/:id', (req, res) => {
if (!res.locals.rights.manage) { if (!res.locals.rights.manage) {
return res.status(401).json({ msg: lang.t('errors:unauthorized') }) return res.status(401).json({ msg: wiki.lang.t('errors:unauthorized') })
} }
if (!validator.isMongoId(req.params.id)) { if (!validator.isMongoId(req.params.id)) {
return res.status(400).json({ msg: lang.t('errors:invaliduserid') }) return res.status(400).json({ msg: wiki.lang.t('errors:invaliduserid') })
} }
return db.User.findById(req.params.id).then((usr) => { return wiki.db.User.findById(req.params.id).then((usr) => {
usr.name = _.trim(req.body.name) usr.name = _.trim(req.body.name)
usr.rights = JSON.parse(req.body.rights) usr.rights = JSON.parse(req.body.rights)
if (usr.provider === 'local' && req.body.password !== '********') { if (usr.provider === 'local' && req.body.password !== '********') {
let nPwd = _.trim(req.body.password) let nPwd = _.trim(req.body.password)
if (nPwd.length < 6) { if (nPwd.length < 6) {
return Promise.reject(new Error(lang.t('errors:newpasswordtooshort'))) return Promise.reject(new Error(wiki.lang.t('errors:newpasswordtooshort')))
} else { } else {
return db.User.hashPassword(nPwd).then((pwd) => { return wiki.db.User.hashPassword(nPwd).then((pwd) => {
usr.password = pwd usr.password = pwd
return usr.save() return usr.save()
}) })
...@@ -199,7 +199,7 @@ router.post('/users/:id', (req, res) => { ...@@ -199,7 +199,7 @@ router.post('/users/:id', (req, res) => {
}).then((usr) => { }).then((usr) => {
// Update guest rights for future requests // Update guest rights for future requests
if (usr.provider === 'local' && usr.email === 'guest') { if (usr.provider === 'local' && usr.email === 'guest') {
rights.guest = usr wiki.rights.guest = usr
} }
return usr return usr
}).then(() => { }).then(() => {
...@@ -214,14 +214,14 @@ router.post('/users/:id', (req, res) => { ...@@ -214,14 +214,14 @@ router.post('/users/:id', (req, res) => {
*/ */
router.delete('/users/:id', (req, res) => { router.delete('/users/:id', (req, res) => {
if (!res.locals.rights.manage) { if (!res.locals.rights.manage) {
return res.status(401).json({ msg: lang.t('errors:unauthorized') }) return res.status(401).json({ msg: wiki.lang.t('errors:unauthorized') })
} }
if (!validator.isMongoId(req.params.id)) { if (!validator.isMongoId(req.params.id)) {
return res.status(400).json({ msg: lang.t('errors:invaliduserid') }) return res.status(400).json({ msg: wiki.lang.t('errors:invaliduserid') })
} }
return db.User.findByIdAndRemove(req.params.id).then(() => { return wiki.db.User.findByIdAndRemove(req.params.id).then(() => {
return res.json({ ok: true }) return res.json({ ok: true })
}).catch((err) => { }).catch((err) => {
res.status(500).json({ ok: false, msg: err.message }) res.status(500).json({ ok: false, msg: err.message })
...@@ -249,7 +249,7 @@ router.get('/system', (req, res) => { ...@@ -249,7 +249,7 @@ router.get('/system', (req, res) => {
cwd: process.cwd() cwd: process.cwd()
} }
fs.readJsonAsync(path.join(ROOTPATH, 'package.json')).then(packageObj => { fs.readJsonAsync(path.join(wiki.ROOTPATH, 'package.json')).then(packageObj => {
axios.get('https://api.github.com/repos/Requarks/wiki/releases/latest').then(resp => { axios.get('https://api.github.com/repos/Requarks/wiki/releases/latest').then(resp => {
let sysversion = { let sysversion = {
current: 'v' + packageObj.version, current: 'v' + packageObj.version,
...@@ -259,7 +259,7 @@ router.get('/system', (req, res) => { ...@@ -259,7 +259,7 @@ router.get('/system', (req, res) => {
res.render('pages/admin/system', { adminTab: 'system', hostInfo, sysversion }) res.render('pages/admin/system', { adminTab: 'system', hostInfo, sysversion })
}).catch(err => { }).catch(err => {
winston.warn(err) wiki.logger.warn(err)
res.render('pages/admin/system', { adminTab: 'system', hostInfo, sysversion: { current: 'v' + packageObj.version } }) res.render('pages/admin/system', { adminTab: 'system', hostInfo, sysversion: { current: 'v' + packageObj.version } })
}) })
}) })
...@@ -287,19 +287,19 @@ router.post('/theme', (req, res) => { ...@@ -287,19 +287,19 @@ router.post('/theme', (req, res) => {
return res.render('error-forbidden') return res.render('error-forbidden')
} }
if (!validator.isIn(req.body.primary, appdata.colors)) { if (!validator.isIn(req.body.primary, wiki.data.colors)) {
return res.status(406).json({ msg: 'Primary color is invalid.' }) return res.status(406).json({ msg: 'Primary color is invalid.' })
} else if (!validator.isIn(req.body.alt, appdata.colors)) { } else if (!validator.isIn(req.body.alt, wiki.data.colors)) {
return res.status(406).json({ msg: 'Alternate color is invalid.' }) return res.status(406).json({ msg: 'Alternate color is invalid.' })
} else if (!validator.isIn(req.body.footer, appdata.colors)) { } else if (!validator.isIn(req.body.footer, wiki.data.colors)) {
return res.status(406).json({ msg: 'Footer color is invalid.' }) return res.status(406).json({ msg: 'Footer color is invalid.' })
} }
appconfig.theme.primary = req.body.primary wiki.config.theme.primary = req.body.primary
appconfig.theme.alt = req.body.alt wiki.config.theme.alt = req.body.alt
appconfig.theme.footer = req.body.footer wiki.config.theme.footer = req.body.footer
appconfig.theme.code.dark = req.body.codedark === 'true' wiki.config.theme.code.dark = req.body.codedark === 'true'
appconfig.theme.code.colorize = req.body.codecolorize === 'true' wiki.config.theme.code.colorize = req.body.codecolorize === 'true'
return res.json({ msg: 'OK' }) return res.json({ msg: 'OK' })
}) })
......
'use strict' /* global wiki */
/* global db, lang */
const Promise = require('bluebird') const Promise = require('bluebird')
const express = require('express') const express = require('express')
const router = express.Router() const router = express.Router()
const passport = require('passport')
const ExpressBrute = require('express-brute') const ExpressBrute = require('express-brute')
const ExpressBruteMongooseStore = require('express-brute-mongoose') const ExpressBruteRedisStore = require('express-brute-redis')
const moment = require('moment') const moment = require('moment')
const _ = require('lodash')
/** /**
* Setup Express-Brute * Setup Express-Brute
*/ */
const EBstore = new ExpressBruteMongooseStore(db.Bruteforce) const EBstore = new ExpressBruteRedisStore({
client: wiki.redis
})
const bruteforce = new ExpressBrute(EBstore, { const bruteforce = new ExpressBrute(EBstore, {
freeRetries: 5, freeRetries: 5,
minWait: 60 * 1000, minWait: 60 * 1000,
...@@ -22,8 +22,8 @@ const bruteforce = new ExpressBrute(EBstore, { ...@@ -22,8 +22,8 @@ const bruteforce = new ExpressBrute(EBstore, {
failCallback (req, res, next, nextValidRequestDate) { failCallback (req, res, next, nextValidRequestDate) {
req.flash('alert', { req.flash('alert', {
class: 'error', class: 'error',
title: lang.t('auth:errors.toomanyattempts'), title: wiki.lang.t('auth:errors.toomanyattempts'),
message: lang.t('auth:errors.toomanyattemptsmsg', { time: moment(nextValidRequestDate).fromNow() }), message: wiki.lang.t('auth:errors.toomanyattemptsmsg', { time: moment(nextValidRequestDate).fromNow() }),
iconClass: 'fa-times' iconClass: 'fa-times'
}) })
res.redirect('/login') res.redirect('/login')
...@@ -35,23 +35,24 @@ const bruteforce = new ExpressBrute(EBstore, { ...@@ -35,23 +35,24 @@ const bruteforce = new ExpressBrute(EBstore, {
*/ */
router.get('/login', function (req, res, next) { router.get('/login', function (req, res, next) {
res.render('auth/login', { res.render('auth/login', {
usr: res.locals.usr authStrategies: _.reject(wiki.auth.strategies, { key: 'local' }),
hasMultipleStrategies: Object.keys(wiki.config.auth.strategies).length > 1
}) })
}) })
router.post('/login', bruteforce.prevent, function (req, res, next) { router.post('/login', bruteforce.prevent, function (req, res, next) {
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
// [1] LOCAL AUTHENTICATION // [1] LOCAL AUTHENTICATION
passport.authenticate('local', function (err, user, info) { wiki.auth.passport.authenticate('local', function (err, user, info) {
if (err) { return reject(err) } if (err) { return reject(err) }
if (!user) { return reject(new Error('INVALID_LOGIN')) } if (!user) { return reject(new Error('INVALID_LOGIN')) }
resolve(user) resolve(user)
})(req, res, next) })(req, res, next)
}).catch({ message: 'INVALID_LOGIN' }, err => { }).catch({ message: 'INVALID_LOGIN' }, err => {
if (appconfig.auth.ldap && appconfig.auth.ldap.enabled) { if (_.has(wiki.config.auth.strategy, 'ldap')) {
// [2] LDAP AUTHENTICATION // [2] LDAP AUTHENTICATION
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
passport.authenticate('ldapauth', function (err, user, info) { wiki.auth.passport.authenticate('ldapauth', function (err, user, info) {
if (err) { return reject(err) } if (err) { return reject(err) }
if (info && info.message) { return reject(new Error(info.message)) } if (info && info.message) { return reject(new Error(info.message)) }
if (!user) { return reject(new Error('INVALID_LOGIN')) } if (!user) { return reject(new Error('INVALID_LOGIN')) }
...@@ -73,13 +74,13 @@ router.post('/login', bruteforce.prevent, function (req, res, next) { ...@@ -73,13 +74,13 @@ router.post('/login', bruteforce.prevent, function (req, res, next) {
// LOGIN FAIL // LOGIN FAIL
if (err.message === 'INVALID_LOGIN') { if (err.message === 'INVALID_LOGIN') {
req.flash('alert', { req.flash('alert', {
title: lang.t('auth:errors.invalidlogin'), title: wiki.lang.t('auth:errors.invalidlogin'),
message: lang.t('auth:errors.invalidloginmsg') message: wiki.lang.t('auth:errors.invalidloginmsg')
}) })
return res.redirect('/login') return res.redirect('/login')
} else { } else {
req.flash('alert', { req.flash('alert', {
title: lang.t('auth:errors.loginerror'), title: wiki.lang.t('auth:errors.loginerror'),
message: err.message message: err.message
}) })
return res.redirect('/login') return res.redirect('/login')
...@@ -91,19 +92,19 @@ router.post('/login', bruteforce.prevent, function (req, res, next) { ...@@ -91,19 +92,19 @@ router.post('/login', bruteforce.prevent, function (req, res, next) {
* Social Login * Social Login
*/ */
router.get('/login/ms', passport.authenticate('windowslive', { scope: ['wl.signin', 'wl.basic', 'wl.emails'] })) router.get('/login/ms', wiki.auth.passport.authenticate('windowslive', { scope: ['wl.signin', 'wl.basic', 'wl.emails'] }))
router.get('/login/google', passport.authenticate('google', { scope: ['profile', 'email'] })) router.get('/login/google', wiki.auth.passport.authenticate('google', { scope: ['profile', 'email'] }))
router.get('/login/facebook', passport.authenticate('facebook', { scope: ['public_profile', 'email'] })) router.get('/login/facebook', wiki.auth.passport.authenticate('facebook', { scope: ['public_profile', 'email'] }))
router.get('/login/github', passport.authenticate('github', { scope: ['user:email'] })) router.get('/login/github', wiki.auth.passport.authenticate('github', { scope: ['user:email'] }))
router.get('/login/slack', passport.authenticate('slack', { scope: ['identity.basic', 'identity.email'] })) router.get('/login/slack', wiki.auth.passport.authenticate('slack', { scope: ['identity.basic', 'identity.email'] }))
router.get('/login/azure', passport.authenticate('azure_ad_oauth2')) router.get('/login/azure', wiki.auth.passport.authenticate('azure_ad_oauth2'))
router.get('/login/ms/callback', passport.authenticate('windowslive', { failureRedirect: '/login', successRedirect: '/' })) router.get('/login/ms/callback', wiki.auth.passport.authenticate('windowslive', { failureRedirect: '/login', successRedirect: '/' }))
router.get('/login/google/callback', passport.authenticate('google', { failureRedirect: '/login', successRedirect: '/' })) router.get('/login/google/callback', wiki.auth.passport.authenticate('google', { failureRedirect: '/login', successRedirect: '/' }))
router.get('/login/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login', successRedirect: '/' })) router.get('/login/facebook/callback', wiki.auth.passport.authenticate('facebook', { failureRedirect: '/login', successRedirect: '/' }))
router.get('/login/github/callback', passport.authenticate('github', { failureRedirect: '/login', successRedirect: '/' })) router.get('/login/github/callback', wiki.auth.passport.authenticate('github', { failureRedirect: '/login', successRedirect: '/' }))
router.get('/login/slack/callback', passport.authenticate('slack', { failureRedirect: '/login', successRedirect: '/' })) router.get('/login/slack/callback', wiki.auth.passport.authenticate('slack', { failureRedirect: '/login', successRedirect: '/' }))
router.get('/login/azure/callback', passport.authenticate('azure_ad_oauth2', { failureRedirect: '/login', successRedirect: '/' })) router.get('/login/azure/callback', wiki.auth.passport.authenticate('azure_ad_oauth2', { failureRedirect: '/login', successRedirect: '/' }))
/** /**
* Logout * Logout
......
'use strict' 'use strict'
/* global git, lang, lcdata, upl */ /* global wiki */
module.exports = false
return
const express = require('express') const express = require('express')
const router = express.Router() const router = express.Router()
...@@ -12,7 +15,7 @@ const fs = Promise.promisifyAll(require('fs-extra')) ...@@ -12,7 +15,7 @@ const fs = Promise.promisifyAll(require('fs-extra'))
const path = require('path') const path = require('path')
const _ = require('lodash') const _ = require('lodash')
const validPathRe = new RegExp('^([a-z0-9/-' + appdata.regex.cjk + appdata.regex.arabic + ']+\\.[a-z0-9]+)$') const validPathRe = new RegExp('^([a-z0-9/-' + wiki.data.regex.cjk + wiki.data.regex.arabic + ']+\\.[a-z0-9]+)$')
const validPathThumbsRe = new RegExp('^([a-z0-9]+\\.png)$') const validPathThumbsRe = new RegExp('^([a-z0-9]+\\.png)$')
// ========================================== // ==========================================
...@@ -28,7 +31,7 @@ router.get('/t/*', (req, res, next) => { ...@@ -28,7 +31,7 @@ router.get('/t/*', (req, res, next) => {
// todo: Authentication-based access // todo: Authentication-based access
res.sendFile(fileName, { res.sendFile(fileName, {
root: lcdata.getThumbsPath(), root: wiki.disk.getThumbsPath(),
dotfiles: 'deny' dotfiles: 'deny'
}, (err) => { }, (err) => {
if (err) { if (err) {
...@@ -37,12 +40,12 @@ router.get('/t/*', (req, res, next) => { ...@@ -37,12 +40,12 @@ router.get('/t/*', (req, res, next) => {
}) })
}) })
router.post('/img', lcdata.uploadImgHandler, (req, res, next) => { router.post('/img', wiki.disk.uploadImgHandler, (req, res, next) => {
let destFolder = _.chain(req.body.folder).trim().toLower().value() let destFolder = _.chain(req.body.folder).trim().toLower().value()
upl.validateUploadsFolder(destFolder).then((destFolderPath) => { wiki.upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
if (!destFolderPath) { if (!destFolderPath) {
res.json({ ok: false, msg: lang.t('errors:invalidfolder') }) res.json({ ok: false, msg: wiki.lang.t('errors:invalidfolder') })
return true return true
} }
...@@ -50,7 +53,7 @@ router.post('/img', lcdata.uploadImgHandler, (req, res, next) => { ...@@ -50,7 +53,7 @@ router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
let destFilename = '' let destFilename = ''
let destFilePath = '' let destFilePath = ''
return lcdata.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => { return wiki.disk.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
destFilename = fname destFilename = fname
destFilePath = path.resolve(destFolderPath, destFilename) destFilePath = path.resolve(destFolderPath, destFilename)
...@@ -60,7 +63,7 @@ router.post('/img', lcdata.uploadImgHandler, (req, res, next) => { ...@@ -60,7 +63,7 @@ router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
let mimeInfo = fileType(buf) let mimeInfo = fileType(buf)
if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) { if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
return Promise.reject(new Error(lang.t('errors:invalidfiletype'))) return Promise.reject(new Error(wiki.lang.t('errors:invalidfiletype')))
} }
return true return true
}).then(() => { }).then(() => {
...@@ -94,12 +97,12 @@ router.post('/img', lcdata.uploadImgHandler, (req, res, next) => { ...@@ -94,12 +97,12 @@ router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
}) })
}) })
router.post('/file', lcdata.uploadFileHandler, (req, res, next) => { router.post('/file', wiki.disk.uploadFileHandler, (req, res, next) => {
let destFolder = _.chain(req.body.folder).trim().toLower().value() let destFolder = _.chain(req.body.folder).trim().toLower().value()
upl.validateUploadsFolder(destFolder).then((destFolderPath) => { wiki.upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
if (!destFolderPath) { if (!destFolderPath) {
res.json({ ok: false, msg: lang.t('errors:invalidfolder') }) res.json({ ok: false, msg: wiki.lang.t('errors:invalidfolder') })
return true return true
} }
...@@ -107,7 +110,7 @@ router.post('/file', lcdata.uploadFileHandler, (req, res, next) => { ...@@ -107,7 +110,7 @@ router.post('/file', lcdata.uploadFileHandler, (req, res, next) => {
let destFilename = '' let destFilename = ''
let destFilePath = '' let destFilePath = ''
return lcdata.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => { return wiki.disk.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
destFilename = fname destFilename = fname
destFilePath = path.resolve(destFolderPath, destFilename) destFilePath = path.resolve(destFolderPath, destFilename)
...@@ -150,7 +153,7 @@ router.get('/*', (req, res, next) => { ...@@ -150,7 +153,7 @@ router.get('/*', (req, res, next) => {
// todo: Authentication-based access // todo: Authentication-based access
res.sendFile(fileName, { res.sendFile(fileName, {
root: git.getRepoPath() + '/uploads/', root: wiki.git.getRepoPath() + '/uploads/',
dotfiles: 'deny' dotfiles: 'deny'
}, (err) => { }, (err) => {
if (err) { if (err) {
......
'use strict'
// =========================================== // ===========================================
// Wiki.js // Wiki.js
// 1.0.0
// Licensed under AGPLv3 // Licensed under AGPLv3
// =========================================== // ===========================================
const path = require('path') const path = require('path')
const ROOTPATH = process.cwd() const cluster = require('cluster')
const SERVERPATH = path.join(ROOTPATH, 'server')
let wiki = {
global.ROOTPATH = ROOTPATH IS_DEBUG: process.env.NODE_ENV === 'development',
global.SERVERPATH = SERVERPATH IS_MASTER: cluster.isMaster,
const IS_DEBUG = process.env.NODE_ENV === 'development' ROOTPATH: process.cwd(),
SERVERPATH: path.join(process.cwd(), 'server'),
configSvc: require('./modules/config'),
kernel: require('./modules/kernel')
}
global.wiki = wiki
process.env.VIPS_WARNING = false process.env.VIPS_WARNING = false
// if (IS_DEBUG) { // if (wiki.IS_DEBUG) {
// require('@glimpse/glimpse').init() // require('@glimpse/glimpse').init()
// } // }
let appconf = require('./libs/config')() wiki.configSvc.init()
global.appconfig = appconf.config
global.appdata = appconf.data
// ----------------------------------------
// Load Winston
// ----------------------------------------
global.winston = require('./libs/logger')(IS_DEBUG, 'SERVER')
global.winston.info('Wiki.js is initializing...')
// ----------------------------------------
// Load global modules
// ----------------------------------------
global.lcdata = require('./libs/local').init()
global.db = require('./libs/db').init()
global.entries = require('./libs/entries').init()
global.git = require('./libs/git').init(false)
global.lang = require('i18next')
global.mark = require('./libs/markdown')
global.search = require('./libs/search').init()
global.upl = require('./libs/uploads').init()
// ----------------------------------------
// Load modules
// ----------------------------------------
const autoload = require('auto-load')
const bodyParser = require('body-parser')
const compression = require('compression')
const cookieParser = require('cookie-parser')
const express = require('express')
const favicon = require('serve-favicon')
const flash = require('connect-flash')
const fork = require('child_process').fork
const http = require('http')
const i18nBackend = require('i18next-node-fs-backend')
const passport = require('passport')
const passportSocketIo = require('passport.socketio')
const session = require('express-session')
const SessionMongoStore = require('connect-mongo')(session)
const graceful = require('node-graceful')
const socketio = require('socket.io')
var mw = autoload(path.join(SERVERPATH, '/middlewares'))
var ctrl = autoload(path.join(SERVERPATH, '/controllers'))
// ----------------------------------------
// Define Express App
// ----------------------------------------
const app = express()
global.app = app
app.use(compression())
// ----------------------------------------
// Security
// ----------------------------------------
app.use(mw.security)
// ----------------------------------------
// Public Assets
// ----------------------------------------
app.use(favicon(path.join(ROOTPATH, 'assets', 'favicon.ico')))
app.use(express.static(path.join(ROOTPATH, 'assets'), {
index: false,
maxAge: '7d'
}))
// ---------------------------------------- // ----------------------------------------
// Passport Authentication // Init Logger
// ---------------------------------------- // ----------------------------------------
require('./libs/auth')(passport) wiki.logger = require('./modules/logger').init()
global.rights = require('./libs/rights')
global.rights.init()
let sessionStore = new SessionMongoStore({
mongooseConnection: global.db.connection,
touchAfter: 15
})
app.use(cookieParser())
app.use(session({
name: 'wikijs.sid',
store: sessionStore,
secret: appconfig.sessionSecret,
resave: false,
saveUninitialized: false
}))
app.use(flash())
app.use(passport.initialize())
app.use(passport.session())
// ----------------------------------------
// SEO
// ---------------------------------------- // ----------------------------------------
// Init DB
app.use(mw.seo)
// ----------------------------------------
// Localization Engine
// ----------------------------------------
global.lang
.use(i18nBackend)
.init({
load: 'languageOnly',
ns: ['common', 'admin', 'auth', 'errors', 'git'],
defaultNS: 'common',
saveMissing: false,
preload: [appconfig.lang],
lng: appconfig.lang,
fallbackLng: 'en',
backend: {
loadPath: path.join(SERVERPATH, 'locales/{{lng}}/{{ns}}.json')
}
})
// ---------------------------------------- // ----------------------------------------
// View Engine Setup
// ----------------------------------------
app.set('views', path.join(SERVERPATH, 'views'))
app.set('view engine', 'pug')
app.use(bodyParser.json({ limit: '1mb' }))
app.use(bodyParser.urlencoded({ extended: false, limit: '1mb' }))
// ----------------------------------------
// View accessible data
// ----------------------------------------
app.locals._ = require('lodash')
app.locals.t = global.lang.t.bind(global.lang)
app.locals.moment = require('moment')
app.locals.moment.locale(appconfig.lang)
app.locals.appconfig = appconfig
app.use(mw.flash)
// ----------------------------------------
// Controllers
// ----------------------------------------
app.use('/', ctrl.auth)
app.use('/uploads', mw.auth, ctrl.uploads)
app.use('/admin', mw.auth, ctrl.admin)
app.use('/', mw.auth, ctrl.pages)
// ----------------------------------------
// Error handling
// ----------------------------------------
app.use(function (req, res, next) {
var err = new Error('Not Found')
err.status = 404
next(err)
})
app.use(function (err, req, res, next) {
res.status(err.status || 500)
res.render('error', {
message: err.message,
error: IS_DEBUG ? err : {}
})
})
// ----------------------------------------
// Start HTTP server
// ----------------------------------------
global.winston.info('Starting HTTP/WS server on port ' + appconfig.port + '...')
app.set('port', appconfig.port)
var server = http.createServer(app)
var io = socketio(server)
server.listen(appconfig.port)
server.on('error', (error) => {
if (error.syscall !== 'listen') {
throw error
}
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
global.winston.error('Listening on port ' + appconfig.port + ' requires elevated privileges!')
return process.exit(1)
case 'EADDRINUSE':
global.winston.error('Port ' + appconfig.port + ' is already in use!')
return process.exit(1)
default:
throw error
}
})
server.on('listening', () => {
global.winston.info('HTTP/WS server started successfully! [RUNNING]')
})
// ----------------------------------------
// WebSocket
// ----------------------------------------
io.use(passportSocketIo.authorize({
key: 'wikijs.sid',
store: sessionStore,
secret: appconfig.sessionSecret,
cookieParser,
success: (data, accept) => {
accept()
},
fail: (data, message, error, accept) => {
accept()
}
}))
io.on('connection', ctrl.ws)
// ----------------------------------------
// Start child processes
// ----------------------------------------
let bgAgent = fork(path.join(SERVERPATH, 'agent.js'))
bgAgent.on('message', m => {
if (!m.action) {
return
}
switch (m.action) { wiki.db = require('./modules/db').init()
case 'searchAdd':
global.search.add(m.content)
break
}
})
// ---------------------------------------- // ----------------------------------------
// Graceful shutdown // Start Kernel
// ---------------------------------------- // ----------------------------------------
graceful.on('exit', () => { wiki.kernel.init()
global.winston.info('- SHUTTING DOWN - Terminating Background Agent...')
bgAgent.kill()
global.winston.info('- SHUTTING DOWN - Performing git sync...')
return global.git.resync().then(() => {
global.winston.info('- SHUTTING DOWN - Git sync successful. Now safe to exit.')
process.exit()
})
})
'use strict'
const Promise = require('bluebird')
const fs = Promise.promisifyAll(require('fs-extra'))
const pm2 = Promise.promisifyAll(require('pm2'))
const ora = require('ora')
const path = require('path')
const ROOTPATH = process.cwd()
module.exports = {
/**
* Detect the most appropriate start mode
*/
startDetect: function () {
if (process.env.WIKI_JS_HEROKU) {
return this.startInHerokuMode()
} else {
return this.startInBackgroundMode()
}
},
/**
* Start in background mode
*/
startInBackgroundMode: function () {
let spinner = ora('Initializing...').start()
return fs.emptyDirAsync(path.join(ROOTPATH, './logs')).then(() => {
return pm2.connectAsync().then(() => {
return pm2.startAsync({
name: 'wiki',
script: 'server',
cwd: ROOTPATH,
output: path.join(ROOTPATH, './logs/wiki-output.log'),
error: path.join(ROOTPATH, './logs/wiki-error.log'),
minUptime: 5000,
maxRestarts: 5
}).then(() => {
spinner.succeed('Wiki.js has started successfully.')
}).finally(() => {
pm2.disconnect()
})
})
}).catch(err => {
spinner.fail(err)
process.exit(1)
})
},
/**
* Start in Heroku mode
*/
startInHerokuMode: function () {
console.warn('Incorrect command on Heroku, use instead: node server')
process.exit(1)
},
/**
* Stop Wiki.js process(es)
*/
stop () {
let spinner = ora('Shutting down Wiki.js...').start()
return pm2.connectAsync().then(() => {
return pm2.stopAsync('wiki').then(() => {
spinner.succeed('Wiki.js has stopped successfully.')
}).finally(() => {
pm2.disconnect()
})
}).catch(err => {
spinner.fail(err)
process.exit(1)
})
},
/**
* Restart Wiki.js process(es)
*/
restart: function () {
let self = this
return self.stop().delay(1000).then(() => {
self.startDetect()
})
},
/**
* Start the web-based configuration wizard
*
* @param {Number} port Port to bind the HTTP server on
*/
configure (port) {
port = port || 3000
let spinner = ora('Initializing interactive setup...').start()
require('./configure')(port, spinner)
}
}
'use strict'
/* global appconfig, appdata, db, lang, winston */
const fs = require('fs')
module.exports = function (passport) {
// Serialization user methods
passport.serializeUser(function (user, done) {
done(null, user._id)
})
passport.deserializeUser(function (id, done) {
db.User.findById(id).then((user) => {
if (user) {
done(null, user)
} else {
done(new Error(lang.t('auth:errors:usernotfound')), null)
}
return true
}).catch((err) => {
done(err, null)
})
})
// Local Account
if (appconfig.auth.local && appconfig.auth.local.enabled) {
const LocalStrategy = require('passport-local').Strategy
passport.use('local',
new LocalStrategy({
usernameField: 'email',
passwordField: 'password'
}, (uEmail, uPassword, done) => {
db.User.findOne({ email: uEmail, provider: 'local' }).then((user) => {
if (user) {
return user.validatePassword(uPassword).then(() => {
return done(null, user) || true
}).catch((err) => {
return done(err, null)
})
} else {
return done(new Error('INVALID_LOGIN'), null)
}
}).catch((err) => {
done(err, null)
})
}
))
}
// Google ID
if (appconfig.auth.google && appconfig.auth.google.enabled) {
const GoogleStrategy = require('passport-google-oauth20').Strategy
passport.use('google',
new GoogleStrategy({
clientID: appconfig.auth.google.clientId,
clientSecret: appconfig.auth.google.clientSecret,
callbackURL: appconfig.host + '/login/google/callback'
}, (accessToken, refreshToken, profile, cb) => {
db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
// Microsoft Accounts
if (appconfig.auth.microsoft && appconfig.auth.microsoft.enabled) {
const WindowsLiveStrategy = require('passport-windowslive').Strategy
passport.use('windowslive',
new WindowsLiveStrategy({
clientID: appconfig.auth.microsoft.clientId,
clientSecret: appconfig.auth.microsoft.clientSecret,
callbackURL: appconfig.host + '/login/ms/callback'
}, function (accessToken, refreshToken, profile, cb) {
db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
// Facebook
if (appconfig.auth.facebook && appconfig.auth.facebook.enabled) {
const FacebookStrategy = require('passport-facebook').Strategy
passport.use('facebook',
new FacebookStrategy({
clientID: appconfig.auth.facebook.clientId,
clientSecret: appconfig.auth.facebook.clientSecret,
callbackURL: appconfig.host + '/login/facebook/callback',
profileFields: ['id', 'displayName', 'email']
}, function (accessToken, refreshToken, profile, cb) {
db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
// GitHub
if (appconfig.auth.github && appconfig.auth.github.enabled) {
const GitHubStrategy = require('passport-github2').Strategy
passport.use('github',
new GitHubStrategy({
clientID: appconfig.auth.github.clientId,
clientSecret: appconfig.auth.github.clientSecret,
callbackURL: appconfig.host + '/login/github/callback',
scope: ['user:email']
}, (accessToken, refreshToken, profile, cb) => {
db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
// Slack
if (appconfig.auth.slack && appconfig.auth.slack.enabled) {
const SlackStrategy = require('passport-slack').Strategy
passport.use('slack',
new SlackStrategy({
clientID: appconfig.auth.slack.clientId,
clientSecret: appconfig.auth.slack.clientSecret,
callbackURL: appconfig.host + '/login/slack/callback'
}, (accessToken, refreshToken, profile, cb) => {
db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
// LDAP
if (appconfig.auth.ldap && appconfig.auth.ldap.enabled) {
const LdapStrategy = require('passport-ldapauth').Strategy
passport.use('ldapauth',
new LdapStrategy({
server: {
url: appconfig.auth.ldap.url,
bindDn: appconfig.auth.ldap.bindDn,
bindCredentials: appconfig.auth.ldap.bindCredentials,
searchBase: appconfig.auth.ldap.searchBase,
searchFilter: appconfig.auth.ldap.searchFilter,
searchAttributes: ['displayName', 'name', 'cn', 'mail'],
tlsOptions: (appconfig.auth.ldap.tlsEnabled) ? {
ca: [
fs.readFileSync(appconfig.auth.ldap.tlsCertPath)
]
} : {}
},
usernameField: 'email',
passReqToCallback: false
}, (profile, cb) => {
profile.provider = 'ldap'
profile.id = profile.dn
db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
// AZURE AD
if (appconfig.auth.azure && appconfig.auth.azure.enabled) {
const AzureAdOAuth2Strategy = require('passport-azure-ad-oauth2').Strategy
const jwt = require('jsonwebtoken')
passport.use('azure_ad_oauth2',
new AzureAdOAuth2Strategy({
clientID: appconfig.auth.azure.clientId,
clientSecret: appconfig.auth.azure.clientSecret,
callbackURL: appconfig.host + '/login/azure/callback',
resource: appconfig.auth.azure.resource,
tenant: appconfig.auth.azure.tenant
}, (accessToken, refreshToken, params, profile, cb) => {
let waadProfile = jwt.decode(params.id_token)
waadProfile.id = waadProfile.oid
waadProfile.provider = 'azure'
db.User.processProfile(waadProfile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
// Create users for first-time
db.onReady.then(() => {
return db.User.findOne({ provider: 'local', email: 'guest' }).then((c) => {
if (c < 1) {
// Create guest account
return db.User.create({
provider: 'local',
email: 'guest',
name: 'Guest',
password: '',
rights: [{
role: 'read',
path: '/',
exact: false,
deny: !appconfig.public
}]
}).then(() => {
winston.info('[AUTH] Guest account created successfully!')
}).catch((err) => {
winston.error('[AUTH] An error occured while creating guest account:')
winston.error(err)
})
}
}).then(() => {
if (process.env.WIKI_JS_HEROKU) {
return db.User.findOne({ provider: 'local', email: process.env.WIKI_ADMIN_EMAIL }).then((c) => {
if (c < 1) {
// Create root admin account (HEROKU ONLY)
return db.User.create({
provider: 'local',
email: process.env.WIKI_ADMIN_EMAIL,
name: 'Administrator',
password: '$2a$04$MAHRw785Xe/Jd5kcKzr3D.VRZDeomFZu2lius4gGpZZ9cJw7B7Mna', // admin123 (default)
rights: [{
role: 'admin',
path: '/',
exact: false,
deny: false
}]
}).then(() => {
winston.info('[AUTH] Root admin account created successfully!')
}).catch((err) => {
winston.error('[AUTH] An error occured while creating root admin account:')
winston.error(err)
})
} else { return true }
})
} else { return true }
})
})
}
'use strict'
/* global ROOTPATH, appconfig, winston */
const modb = require('mongoose')
const fs = require('fs')
const path = require('path')
const _ = require('lodash')
/**
* MongoDB module
*
* @return {Object} MongoDB wrapper instance
*/
module.exports = {
/**
* Initialize DB
*
* @return {Object} DB instance
*/
init() {
let self = this
let dbModelsPath = path.join(SERVERPATH, 'models')
modb.Promise = require('bluebird')
// Event handlers
modb.connection.on('error', err => {
winston.error('Failed to connect to MongoDB instance.')
return err
})
modb.connection.once('open', function () {
winston.log('Connected to MongoDB instance.')
})
// Store connection handle
self.connection = modb.connection
self.ObjectId = modb.Types.ObjectId
// Load DB Models
fs
.readdirSync(dbModelsPath)
.filter(function (file) {
return (file.indexOf('.') !== 0)
})
.forEach(function (file) {
let modelName = _.upperFirst(_.camelCase(_.split(file, '.')[0]))
self[modelName] = require(path.join(dbModelsPath, file))
})
// Connect
self.onReady = modb.connect(appconfig.db, { useMongoClient: true })
return self
}
}
'use strict'
module.exports = (isDebug, processName) => {
let winston = require('winston')
if (typeof processName === 'undefined') {
processName = 'SERVER'
}
// Console
let logger = new (winston.Logger)({
level: (isDebug) ? 'debug' : 'info',
transports: [
new (winston.transports.Console)({
level: (isDebug) ? 'debug' : 'info',
prettyPrint: true,
colorize: true,
silent: false,
timestamp: true
})
]
})
logger.filters.push((level, msg) => {
return '[' + processName + '] ' + msg
})
// External services
if (appconfig.externalLogging.bugsnag) {
const bugsnagTransport = require('./winston-transports/bugsnag')
logger.add(bugsnagTransport, {
level: 'warn',
key: appconfig.externalLogging.bugsnag
})
}
if (appconfig.externalLogging.loggly) {
require('winston-loggly-bulk')
logger.add(winston.transports.Loggly, {
token: appconfig.externalLogging.loggly.token,
subdomain: appconfig.externalLogging.loggly.subdomain,
tags: ['wiki-js'],
level: 'warn',
json: true
})
}
if (appconfig.externalLogging.papertrail) {
require('winston-papertrail').Papertrail // eslint-disable-line no-unused-expressions
logger.add(winston.transports.Papertrail, {
host: appconfig.externalLogging.papertrail.host,
port: appconfig.externalLogging.papertrail.port,
level: 'warn',
program: 'wiki.js'
})
}
if (appconfig.externalLogging.rollbar) {
const rollbarTransport = require('./winston-transports/rollbar')
logger.add(rollbarTransport, {
level: 'warn',
key: appconfig.externalLogging.rollbar
})
}
if (appconfig.externalLogging.sentry) {
const sentryTransport = require('./winston-transports/sentry')
logger.add(sentryTransport, {
level: 'warn',
key: appconfig.externalLogging.sentry
})
}
return logger
}
const bunyan = require('bunyan')
const level = require('levelup')
const down = require('memdown')
const SearchIndexAdder = require('search-index-adder')
const SearchIndexSearcher = require('search-index-searcher')
module.exports = function (givenOptions, moduleReady) {
const optionsLoaded = function (err, SearchIndex) {
const siUtil = require('./siUtil.js')(SearchIndex.options)
if (err) return moduleReady(err)
SearchIndex.close = siUtil.close
SearchIndex.countDocs = siUtil.countDocs
getAdder(SearchIndex, adderLoaded)
}
const adderLoaded = function (err, SearchIndex) {
if (err) return moduleReady(err)
getSearcher(SearchIndex, searcherLoaded)
}
const searcherLoaded = function (err, SearchIndex) {
if (err) return moduleReady(err)
return moduleReady(err, SearchIndex)
}
getOptions(givenOptions, optionsLoaded)
}
const getAdder = function (SearchIndex, done) {
SearchIndexAdder(SearchIndex.options, function (err, searchIndexAdder) {
SearchIndex.add = searchIndexAdder.add
SearchIndex.callbackyAdd = searchIndexAdder.concurrentAdd // deprecated
SearchIndex.concurrentAdd = searchIndexAdder.concurrentAdd
SearchIndex.createWriteStream = searchIndexAdder.createWriteStream
SearchIndex.dbWriteStream = searchIndexAdder.dbWriteStream
SearchIndex.defaultPipeline = searchIndexAdder.defaultPipeline
SearchIndex.del = searchIndexAdder.deleter
SearchIndex.deleteStream = searchIndexAdder.deleteStream
SearchIndex.flush = searchIndexAdder.flush
done(err, SearchIndex)
})
}
const getSearcher = function (SearchIndex, done) {
SearchIndexSearcher(SearchIndex.options, function (err, searchIndexSearcher) {
SearchIndex.availableFields = searchIndexSearcher.availableFields
SearchIndex.buckets = searchIndexSearcher.bucketStream
SearchIndex.categorize = searchIndexSearcher.categoryStream
SearchIndex.dbReadStream = searchIndexSearcher.dbReadStream
SearchIndex.get = searchIndexSearcher.get
SearchIndex.match = searchIndexSearcher.match
SearchIndex.scan = searchIndexSearcher.scan
SearchIndex.search = searchIndexSearcher.search
SearchIndex.totalHits = searchIndexSearcher.totalHits
done(err, SearchIndex)
})
}
const getOptions = function (options, done) {
var SearchIndex = {}
SearchIndex.options = Object.assign({}, {
indexPath: 'si',
keySeparator: '○',
logLevel: 'error'
}, options)
options.log = bunyan.createLogger({
name: 'search-index',
level: options.logLevel
})
if (!options.indexes) {
level(SearchIndex.options.indexPath || 'si', {
valueEncoding: 'json',
db: down
}, function (err, db) {
SearchIndex.options.indexes = db
return done(err, SearchIndex)
})
} else {
return done(null, SearchIndex)
}
}
'use strict'
module.exports = function (siOptions) {
var siUtil = {}
siUtil.countDocs = function (callback) {
var count = 0
const gte = 'DOCUMENT' + siOptions.keySeparator
const lte = 'DOCUMENT' + siOptions.keySeparator + siOptions.keySeparator
siOptions.indexes.createReadStream({gte: gte, lte: lte})
.on('data', function (data) {
count++
})
.on('error', function (err) {
return callback(err, null)
})
.on('end', function () {
return callback(null, count)
})
}
siUtil.close = function (callback) {
siOptions.indexes.close(function (err) {
while (!siOptions.indexes.isClosed()) {
// log not always working here- investigate
if (siOptions.log) siOptions.log.info('closing...')
}
if (siOptions.indexes.isClosed()) {
if (siOptions.log) siOptions.log.info('closed...')
callback(err)
}
})
}
return siUtil
}
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
"invalidlogin": "不正なログイン", "invalidlogin": "不正なログイン",
"invalidloginmsg": "Eメール又はパスワードが無効です。", "invalidloginmsg": "Eメール又はパスワードが無効です。",
"invaliduseremail": "無効なユーザーEメール", "invaliduseremail": "無効なユーザーEメール",
"lognerror": "ログインエラー", "loginerror": "ログインエラー",
"notyetauthorized": "まだこのサイトにログインする権限がありません。", "notyetauthorized": "まだこのサイトにログインする権限がありません。",
"toomanyattempts": "試行回数が多すぎます", "toomanyattempts": "試行回数が多すぎます",
"toomanyattemptsmsg": "短期間に失敗した試行回数が多すぎます。{{time}}にもう一度お試しください。", "toomanyattemptsmsg": "短期間に失敗した試行回数が多すぎます。{{time}}にもう一度お試しください。",
......
/* global wiki */
module.exports = () => {
// ----------------------------------------
// Load global modules
// ----------------------------------------
wiki.auth = require('./modules/auth').init()
wiki.disk = require('./modules/disk').init()
wiki.docs = require('./modules/documents').init()
wiki.git = require('./modules/git').init(false)
wiki.lang = require('./modules/localization').init()
wiki.mark = require('./modules/markdown')
wiki.search = require('./modules/search').init()
wiki.upl = require('./modules/uploads').init()
// ----------------------------------------
// Load modules
// ----------------------------------------
const autoload = require('auto-load')
const bodyParser = require('body-parser')
const compression = require('compression')
const cookieParser = require('cookie-parser')
const express = require('express')
const favicon = require('serve-favicon')
const flash = require('connect-flash')
const http = require('http')
const path = require('path')
const session = require('express-session')
const SessionRedisStore = require('connect-redis')(session)
const graceful = require('node-graceful')
const graphqlApollo = require('apollo-server-express')
const graphqlSchema = require('./modules/graphql')
var mw = autoload(path.join(wiki.SERVERPATH, '/middlewares'))
var ctrl = autoload(path.join(wiki.SERVERPATH, '/controllers'))
// ----------------------------------------
// Define Express App
// ----------------------------------------
const app = express()
wiki.app = app
app.use(compression())
// ----------------------------------------
// Security
// ----------------------------------------
app.use(mw.security)
// ----------------------------------------
// Public Assets
// ----------------------------------------
app.use(favicon(path.join(wiki.ROOTPATH, 'assets', 'favicon.ico')))
app.use(express.static(path.join(wiki.ROOTPATH, 'assets'), {
index: false,
maxAge: '7d'
}))
// ----------------------------------------
// Passport Authentication
// ----------------------------------------
let sessionStore = new SessionRedisStore({
client: wiki.redis
})
app.use(cookieParser())
app.use(session({
name: 'wikijs.sid',
store: sessionStore,
secret: wiki.config.site.sessionSecret,
resave: false,
saveUninitialized: false
}))
app.use(flash())
app.use(wiki.auth.passport.initialize())
app.use(wiki.auth.passport.session())
// ----------------------------------------
// SEO
// ----------------------------------------
app.use(mw.seo)
// ----------------------------------------
// View Engine Setup
// ----------------------------------------
app.set('views', path.join(wiki.SERVERPATH, 'views'))
app.set('view engine', 'pug')
app.use(bodyParser.json({ limit: '1mb' }))
app.use(bodyParser.urlencoded({ extended: false, limit: '1mb' }))
// ----------------------------------------
// View accessible data
// ----------------------------------------
app.locals.basedir = wiki.ROOTPATH
app.locals._ = require('lodash')
app.locals.t = wiki.lang.engine.t.bind(wiki.lang)
app.locals.moment = require('moment')
app.locals.moment.locale(wiki.config.site.lang)
app.locals.config = wiki.config
app.use(mw.flash)
// ----------------------------------------
// Controllers
// ----------------------------------------
app.use('/', ctrl.auth)
app.use('/graphql', graphqlApollo.graphqlExpress({ schema: graphqlSchema }))
app.use('/graphiql', graphqlApollo.graphiqlExpress({ endpointURL: '/graphql' }))
// app.use('/uploads', mw.auth, ctrl.uploads)
app.use('/admin', mw.auth, ctrl.admin)
app.use('/', mw.auth, ctrl.pages)
// ----------------------------------------
// Error handling
// ----------------------------------------
app.use(function (req, res, next) {
var err = new Error('Not Found')
err.status = 404
next(err)
})
app.use(function (err, req, res, next) {
res.status(err.status || 500)
res.render('error', {
message: err.message,
error: wiki.IS_DEBUG ? err : {}
})
})
// ----------------------------------------
// Start HTTP server
// ----------------------------------------
wiki.logger.info(`HTTP Server on port: ${wiki.config.port}`)
app.set('port', wiki.config.port)
let server = http.createServer(app)
server.listen(wiki.config.port)
server.on('error', (error) => {
if (error.syscall !== 'listen') {
throw error
}
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
wiki.logger.error('Listening on port ' + wiki.config.port + ' requires elevated privileges!')
return process.exit(1)
case 'EADDRINUSE':
wiki.logger.error('Port ' + wiki.config.port + ' is already in use!')
return process.exit(1)
default:
throw error
}
})
server.on('listening', () => {
wiki.logger.info('HTTP Server: RUNNING')
})
// ----------------------------------------
// Graceful shutdown
// ----------------------------------------
graceful.on('exit', () => {
wiki.logger.info('- SHUTTING DOWN - Performing git sync...')
return global.git.resync().then(() => {
wiki.logger.info('- SHUTTING DOWN - Git sync successful. Now safe to exit.')
process.exit()
})
})
return true
}
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* @return {any} void * @return {any} void
*/ */
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
res.locals.appflash = req.flash('alert') res.locals.flash = req.flash('alert')
next() next()
} }
'use strict' 'use strict'
/* global app */
/** /**
* Security Middleware * Security Middleware
* *
...@@ -12,7 +10,7 @@ ...@@ -12,7 +10,7 @@
*/ */
module.exports = function (req, res, next) { module.exports = function (req, res, next) {
// -> Disable X-Powered-By // -> Disable X-Powered-By
app.disable('x-powered-by') req.app.disable('x-powered-by')
// -> Disable Frame Embedding // -> Disable Frame Embedding
res.set('X-Frame-Options', 'deny') res.set('X-Frame-Options', 'deny')
......
/**
* Associate DB Model relations
*/
module.exports = db => {
db.User.belongsToMany(db.Group, { through: 'userGroups' })
db.Group.belongsToMany(db.User, { through: 'userGroups' })
db.Group.hasMany(db.Right)
db.Right.belongsTo(db.Group)
db.Document.belongsToMany(db.Tag, { through: 'documentTags' })
db.Document.hasMany(db.Comment)
db.Tag.belongsToMany(db.Document, { through: 'documentTags' })
db.File.belongsTo(db.Folder)
db.Folder.hasMany(db.File)
db.Comment.belongsTo(db.Document)
db.Comment.belongsTo(db.User, { as: 'author' })
}
'use strict'
const Mongoose = require('mongoose')
/**
* BruteForce schema
*
* @type {<Mongoose.Schema>}
*/
var bruteForceSchema = Mongoose.Schema({
_id: { type: String, index: 1 },
data: {
count: Number,
lastRequest: Date,
firstRequest: Date
},
expires: { type: Date, index: { expires: '1d' } }
})
module.exports = Mongoose.model('Bruteforce', bruteForceSchema)
/**
* Comment schema
*/
module.exports = (sequelize, DataTypes) => {
let commentSchema = sequelize.define('comment', {
content: {
type: DataTypes.STRING,
allowNull: false
}
}, {
timestamps: true,
version: true
})
return commentSchema
}
/**
* Document schema
*/
module.exports = (sequelize, DataTypes) => {
let documentSchema = sequelize.define('setting', {
path: {
type: DataTypes.STRING,
allowNull: false
},
title: {
type: DataTypes.STRING,
allowNull: false,
validate: {
len: [2, 255]
}
},
subtitle: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: ''
},
parentPath: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: ''
},
parentTitle: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: ''
},
isDirectory: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
isEntry: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
isDraft: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
searchContent: {
type: DataTypes.TEXT,
allowNull: true,
defaultValue: ''
}
}, {
timestamps: true,
version: true,
indexes: [
{
unique: true,
fields: ['path']
}
]
})
return documentSchema
}
'use strict'
const Mongoose = require('mongoose')
/**
* Entry schema
*
* @type {<Mongoose.Schema>}
*/
var entrySchema = Mongoose.Schema({
_id: String,
title: {
type: String,
required: true,
minlength: 2
},
subtitle: {
type: String,
default: ''
},
parentTitle: {
type: String,
default: ''
},
parentPath: {
type: String,
default: ''
},
isDirectory: {
type: Boolean,
default: false
},
isEntry: {
type: Boolean,
default: false
}
}, {
timestamps: {}
})
module.exports = Mongoose.model('Entry', entrySchema)
/**
* File schema
*/
module.exports = (sequelize, DataTypes) => {
let fileSchema = sequelize.define('file', {
category: {
type: DataTypes.ENUM('binary', 'image'),
allowNull: false,
defaultValue: 'binary'
},
mime: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: 'application/octet-stream'
},
extra: {
type: DataTypes.JSONB,
allowNull: true
},
filename: {
type: DataTypes.STRING,
allowNull: false
},
basename: {
type: DataTypes.STRING,
allowNull: false
},
filesize: {
type: DataTypes.INTEGER,
allowNull: false,
validate: {
isInt: true,
min: 0
}
}
}, {
timestamps: true,
version: true
})
return fileSchema
}
/**
* Folder schema
*/
module.exports = (sequelize, DataTypes) => {
let folderSchema = sequelize.define('folder', {
name: {
type: DataTypes.STRING,
allowNull: false
}
}, {
timestamps: true,
version: true,
indexes: [
{
unique: true,
fields: ['name']
}
]
})
return folderSchema
}
/**
* Group schema
*/
module.exports = (sequelize, DataTypes) => {
let groupSchema = sequelize.define('group', {
name: {
type: DataTypes.STRING,
allowNull: false
}
}, {
timestamps: true,
version: true
})
return groupSchema
}
/**
* Right schema
*/
module.exports = (sequelize, DataTypes) => {
let rightSchema = sequelize.define('right', {
path: {
type: DataTypes.STRING,
allowNull: false
},
role: {
type: DataTypes.ENUM('read', 'write', 'manage'),
allowNull: false,
defaultValue: 'read'
},
exact: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
allow: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
}
}, {
timestamps: true,
version: true,
indexes: [
{
fields: ['path']
}
]
})
return rightSchema
}
'use strict'
const Mongoose = require('mongoose')
/** /**
* Settings schema * Settings schema
*
* @type {<Mongoose.Schema>}
*/ */
var settingSchema = Mongoose.Schema({ module.exports = (sequelize, DataTypes) => {
key: { let settingSchema = sequelize.define('setting', {
type: String, key: {
required: true, type: DataTypes.STRING,
index: true allowNull: false
}, },
value: { config: {
type: String, type: DataTypes.JSONB,
required: true allowNull: false
} }
}, { timestamps: {} }) }, {
timestamps: true,
version: true,
indexes: [
{
unique: true,
fields: ['key']
}
]
})
module.exports = Mongoose.model('Setting', settingSchema) return settingSchema
}
/**
* Tags schema
*/
module.exports = (sequelize, DataTypes) => {
let tagSchema = sequelize.define('tag', {
key: {
type: DataTypes.STRING,
allowNull: false
}
}, {
timestamps: true,
version: true,
indexes: [
{
unique: true,
fields: ['key']
}
]
})
return tagSchema
}
'use strict'
const Mongoose = require('mongoose')
/**
* Upload File schema
*
* @type {<Mongoose.Schema>}
*/
var uplFileSchema = Mongoose.Schema({
_id: String,
category: {
type: String,
required: true,
default: 'binary'
},
mime: {
type: String,
required: true,
default: 'application/octet-stream'
},
extra: {
type: Object
},
folder: {
type: String,
ref: 'UplFolder'
},
filename: {
type: String,
required: true
},
basename: {
type: String,
required: true
},
filesize: {
type: Number,
required: true
}
}, { timestamps: {} })
module.exports = Mongoose.model('UplFile', uplFileSchema)
'use strict'
const Mongoose = require('mongoose')
/**
* Upload Folder schema
*
* @type {<Mongoose.Schema>}
*/
var uplFolderSchema = Mongoose.Schema({
_id: String,
name: {
type: String,
index: true
}
}, { timestamps: {} })
module.exports = Mongoose.model('UplFolder', uplFolderSchema)
'use strict' /* global wiki */
/* global db, lang */
const Mongoose = require('mongoose')
const Promise = require('bluebird') const Promise = require('bluebird')
const bcrypt = require('bcryptjs-then') const bcrypt = require('bcryptjs-then')
const _ = require('lodash') const _ = require('lodash')
/** /**
* Users schema * Users schema
*
* @type {<Mongoose.Schema>}
*/ */
var userSchema = Mongoose.Schema({ module.exports = (sequelize, DataTypes) => {
let userSchema = sequelize.define('user', {
email: { email: {
type: String, type: DataTypes.STRING,
required: true, allowNull: false,
index: true validate: {
}, isEmail: true
}
provider: { },
type: String, provider: {
required: true type: DataTypes.STRING,
}, allowNull: false
},
providerId: { providerId: {
type: String type: DataTypes.STRING,
}, allowNull: true
},
password: { password: {
type: String type: DataTypes.STRING,
}, allowNull: true
},
name: { name: {
type: String type: DataTypes.STRING,
}, allowNull: true
},
rights: [{ role: {
role: String, type: DataTypes.ENUM('admin', 'user', 'guest'),
path: String, allowNull: false
exact: Boolean, },
deny: Boolean tfaIsActive: {
}] type: DataTypes.BOOLEAN,
allowNull: false,
}, { timestamps: {} }) defaultValue: false
},
tfaSecret: {
type: DataTypes.STRING,
allowNull: true
}
}, {
timestamps: true,
version: true,
indexes: [
{
unique: true,
fields: ['provider', 'email']
}
]
})
userSchema.statics.processProfile = (profile) => { userSchema.prototype.validatePassword = function (rawPwd) {
let primaryEmail = '' return bcrypt.compare(rawPwd, this.password).then((isValid) => {
if (_.isArray(profile.emails)) { return (isValid) ? true : Promise.reject(new Error(wiki.lang.t('auth:errors:invalidlogin')))
let e = _.find(profile.emails, ['primary', true]) })
primaryEmail = (e) ? e.value : _.first(profile.emails).value
} else if (_.isString(profile.email) && profile.email.length > 5) {
primaryEmail = profile.email
} else if (_.isString(profile.mail) && profile.mail.length > 5) {
primaryEmail = profile.mail
} else if (profile.user && profile.user.email && profile.user.email.length > 5) {
primaryEmail = profile.user.email
} else {
return Promise.reject(new Error(lang.t('auth:errors.invaliduseremail')))
} }
profile.provider = _.lowerCase(profile.provider) userSchema.processProfile = (profile) => {
primaryEmail = _.toLower(primaryEmail) let primaryEmail = ''
if (_.isArray(profile.emails)) {
let e = _.find(profile.emails, ['primary', true])
primaryEmail = (e) ? e.value : _.first(profile.emails).value
} else if (_.isString(profile.email) && profile.email.length > 5) {
primaryEmail = profile.email
} else if (_.isString(profile.mail) && profile.mail.length > 5) {
primaryEmail = profile.mail
} else if (profile.user && profile.user.email && profile.user.email.length > 5) {
primaryEmail = profile.user.email
} else {
return Promise.reject(new Error(wiki.lang.t('auth:errors.invaliduseremail')))
}
return db.User.findOneAndUpdate({ profile.provider = _.lowerCase(profile.provider)
email: primaryEmail, primaryEmail = _.toLower(primaryEmail)
provider: profile.provider
}, { return wiki.db.User.findOneAndUpdate({
email: primaryEmail, email: primaryEmail,
provider: profile.provider, provider: profile.provider
providerId: profile.id, }, {
name: profile.displayName || profile.cn || _.split(primaryEmail, '@')[0] email: primaryEmail,
}, { provider: profile.provider,
new: true providerId: profile.id,
}).then((user) => { name: profile.displayName || _.split(primaryEmail, '@')[0]
// Handle unregistered accounts }, {
if (!user && profile.provider !== 'local' && (appconfig.auth.defaultReadAccess || profile.provider === 'ldap' || profile.provider === 'azure')) { new: true
let nUsr = { }).then((user) => {
email: primaryEmail, // Handle unregistered accounts
provider: profile.provider, if (!user && profile.provider !== 'local' && (appconfig.auth.defaultReadAccess || profile.provider === 'ldap' || profile.provider === 'azure')) {
providerId: profile.id, let nUsr = {
password: '', email: primaryEmail,
name: profile.displayName || profile.name || profile.cn, provider: profile.provider,
rights: [{ providerId: profile.id,
role: 'read', password: '',
path: '/', name: profile.displayName || profile.name || profile.cn,
exact: false, rights: [{
deny: false role: 'read',
}] path: '/',
exact: false,
deny: false
}]
}
return wiki.db.User.create(nUsr)
} }
return db.User.create(nUsr) return user || Promise.reject(new Error(wiki.lang.t('auth:errors:notyetauthorized')))
} })
return user || Promise.reject(new Error(lang.t('auth:errors:notyetauthorized'))) }
})
}
userSchema.statics.hashPassword = (rawPwd) => { userSchema.hashPassword = (rawPwd) => {
return bcrypt.hash(rawPwd) return bcrypt.hash(rawPwd)
} }
userSchema.methods.validatePassword = function (rawPwd) { return userSchema
return bcrypt.compare(rawPwd, this.password).then((isValid) => {
return (isValid) ? true : Promise.reject(new Error(lang.t('auth:errors:invalidlogin')))
})
} }
module.exports = Mongoose.model('User', userSchema)
/* global wiki */
const _ = require('lodash')
const passport = require('passport')
const fs = require('fs-extra')
const path = require('path')
module.exports = {
strategies: {},
init() {
this.passport = passport
// Serialization user methods
passport.serializeUser(function (user, done) {
done(null, user._id)
})
passport.deserializeUser(function (id, done) {
wiki.db.User.findById(id).then((user) => {
if (user) {
done(null, user)
} else {
done(new Error(wiki.lang.t('auth:errors:usernotfound')), null)
}
return true
}).catch((err) => {
done(err, null)
})
})
// Load authentication strategies
wiki.config.auth.strategies.local = {}
_.forOwn(wiki.config.auth.strategies, (strategyConfig, strategyKey) => {
strategyConfig.callbackURL = `${wiki.config.site.host}${wiki.config.site.path}/login/${strategyKey}/callback`
let strategy = require(`../authentication/${strategyKey}`)
strategy.init(passport, strategyConfig)
fs.readFile(path.join(wiki.ROOTPATH, `assets/svg/auth-icon-${strategyKey}.svg`), 'utf8').then(iconData => {
strategy.icon = iconData
}).catch(err => {
if (err.code === 'ENOENT') {
strategy.icon = '[missing icon]'
} else {
wiki.logger.error(err)
}
})
this.strategies[strategy.key] = strategy
wiki.logger.info(`Authentication Provider ${strategyKey}: OK`)
})
// Create Guest account for first-time
wiki.db.User.findOne({
where: {
provider: 'local',
email: 'guest@example.com'
}
}).then((c) => {
if (c < 1) {
return wiki.db.User.create({
provider: 'local',
email: 'guest@example.com',
name: 'Guest',
password: '',
role: 'guest'
}).then(() => {
wiki.logger.info('[AUTH] Guest account created successfully!')
return true
}).catch((err) => {
wiki.logger.error('[AUTH] An error occured while creating guest account:')
wiki.logger.error(err)
return err
})
}
})
// .then(() => {
// if (process.env.WIKI_JS_HEROKU) {
// return wiki.db.User.findOne({ provider: 'local', email: process.env.WIKI_ADMIN_EMAIL }).then((c) => {
// if (c < 1) {
// // Create root admin account (HEROKU ONLY)
// return wiki.db.User.create({
// provider: 'local',
// email: process.env.WIKI_ADMIN_EMAIL,
// name: 'Administrator',
// password: '$2a$04$MAHRw785Xe/Jd5kcKzr3D.VRZDeomFZu2lius4gGpZZ9cJw7B7Mna', // admin123 (default)
// role: 'admin'
// }).then(() => {
// wiki.logger.info('[AUTH] Root admin account created successfully!')
// return true
// }).catch((err) => {
// wiki.logger.error('[AUTH] An error occured while creating root admin account:')
// wiki.logger.error(err)
// return err
// })
// } else { return true }
// })
// } else { return true }
// })
return this
}
}
'use strict' /* global wiki */
const fs = require('fs') const fs = require('fs')
const yaml = require('js-yaml') const yaml = require('js-yaml')
...@@ -6,63 +6,80 @@ const _ = require('lodash') ...@@ -6,63 +6,80 @@ const _ = require('lodash')
const path = require('path') const path = require('path')
const cfgHelper = require('../helpers/config') const cfgHelper = require('../helpers/config')
/** module.exports = {
* Load Application Configuration /**
* * Load root config from disk
* @param {Object} confPaths Path to the configuration files */
* @return {Object} Application Configuration init() {
*/ let confPaths = {
module.exports = (confPaths) => { config: path.join(wiki.ROOTPATH, 'config.yml'),
confPaths = _.defaults(confPaths, { data: path.join(wiki.SERVERPATH, 'app/data.yml'),
config: path.join(ROOTPATH, 'config.yml'), dataRegex: path.join(wiki.SERVERPATH, 'app/regex.js')
data: path.join(SERVERPATH, 'app/data.yml'), }
dataRegex: path.join(SERVERPATH, 'app/regex.js')
})
let appconfig = {} let appconfig = {}
let appdata = {} let appdata = {}
try { try {
appconfig = yaml.safeLoad( appconfig = yaml.safeLoad(
cfgHelper.parseConfigValue( cfgHelper.parseConfigValue(
fs.readFileSync(confPaths.config, 'utf8') fs.readFileSync(confPaths.config, 'utf8')
)
) )
) appdata = yaml.safeLoad(fs.readFileSync(confPaths.data, 'utf8'))
appdata.regex = require(confPaths.dataRegex)
} catch (ex) {
console.error(ex)
process.exit(1)
}
appdata = yaml.safeLoad(fs.readFileSync(confPaths.data, 'utf8')) // Merge with defaults
appdata.regex = require(confPaths.dataRegex)
} catch (ex) {
console.error(ex)
process.exit(1)
}
// Merge with defaults
appconfig = _.defaultsDeep(appconfig, appdata.defaults.config) appconfig = _.defaultsDeep(appconfig, appdata.defaults.config)
// Check port // Check port
if (appconfig.port < 1) { if (appconfig.port < 1) {
appconfig.port = process.env.PORT || 80 appconfig.port = process.env.PORT || 80
} }
// Convert booleans // Convert booleans
appconfig.public = (appconfig.public === true || _.toLower(appconfig.public) === 'true') appconfig.public = (appconfig.public === true || _.toLower(appconfig.public) === 'true')
// List authentication strategies // List authentication strategies
wiki.config = appconfig
wiki.data = appdata
},
appconfig.authStrategies = { /**
list: _.filter(appconfig.auth, ['enabled', true]), * Load config from DB
socialEnabled: (_.chain(appconfig.auth).omit(['local', 'ldap']).filter(['enabled', true]).value().length > 0) *
} * @param {Array} subsets Array of subsets to load
if (appconfig.authStrategies.list.length < 1) { * @returns Promise
console.error(new Error('You must enable at least 1 authentication strategy!')) */
process.exit(1) loadFromDb(subsets) {
} if (!_.isArray(subsets) || subsets.length === 0) {
subsets = wiki.data.configNamespaces
}
return { return wiki.db.Setting.findAll({
config: appconfig, attributes: ['key', 'config'],
data: appdata where: {
key: {
$in: subsets
}
}
}).then(results => {
if (_.isArray(results) && results.length === subsets.length) {
results.forEach(result => {
wiki.config[result.key] = result.config
})
return true
} else {
wiki.logger.warn('DB Configuration is empty or incomplete.')
return false
}
})
} }
} }
'use strict'
/* global wiki */
const fs = require('fs')
const path = require('path')
const _ = require('lodash')
const Promise = require('bluebird')
const Sequelize = require('sequelize')
const Op = Sequelize.Op
const operatorsAliases = {
$eq: Op.eq,
$ne: Op.ne,
$gte: Op.gte,
$gt: Op.gt,
$lte: Op.lte,
$lt: Op.lt,
$not: Op.not,
$in: Op.in,
$notIn: Op.notIn,
$is: Op.is,
$like: Op.like,
$notLike: Op.notLike,
$iLike: Op.iLike,
$notILike: Op.notILike,
$regexp: Op.regexp,
$notRegexp: Op.notRegexp,
$iRegexp: Op.iRegexp,
$notIRegexp: Op.notIRegexp,
$between: Op.between,
$notBetween: Op.notBetween,
$overlap: Op.overlap,
$contains: Op.contains,
$contained: Op.contained,
$adjacent: Op.adjacent,
$strictLeft: Op.strictLeft,
$strictRight: Op.strictRight,
$noExtendRight: Op.noExtendRight,
$noExtendLeft: Op.noExtendLeft,
$and: Op.and,
$or: Op.or,
$any: Op.any,
$all: Op.all,
$values: Op.values,
$col: Op.col
}
/**
* PostgreSQL DB module
*/
module.exports = {
Sequelize,
Op: Sequelize.Op,
/**
* Initialize DB
*
* @return {Object} DB instance
*/
init() {
let self = this
let dbModelsPath = path.join(wiki.SERVERPATH, 'models')
// Define Sequelize instance
self.inst = new self.Sequelize(wiki.config.db.db, wiki.config.db.user, wiki.config.db.pass, {
host: wiki.config.db.host,
port: wiki.config.db.port,
dialect: 'postgres',
pool: {
max: 10,
min: 0,
idle: 10000
},
logging: log => { wiki.logger.log('verbose', log) },
operatorsAliases
})
// Attempt to connect and authenticate to DB
self.inst.authenticate().then(() => {
wiki.logger.info('Database (PostgreSQL) connection: OK')
}).catch(err => {
wiki.logger.error('Failed to connect to PostgreSQL instance.')
return err
})
// Load DB Models
fs
.readdirSync(dbModelsPath)
.filter(file => {
return (file.indexOf('.') !== 0 && file.indexOf('_') !== 0)
})
.forEach(file => {
let modelName = _.upperFirst(_.camelCase(_.split(file, '.')[0]))
self[modelName] = self.inst.import(path.join(dbModelsPath, file))
})
// Associate DB Models
require(path.join(dbModelsPath, '_relations.js'))(self)
// Set init tasks
let initTasks = {
// -> Sync DB Schemas
syncSchemas() {
return self.inst.sync({
force: false,
logging: log => { wiki.logger.log('verbose', log) }
})
},
// -> Set Connection App Name
setAppName() {
return self.inst.query(`set application_name = 'Wiki.js'`, { raw: true })
}
}
let initTasksQueue = (wiki.IS_MASTER) ? [
initTasks.syncSchemas,
initTasks.setAppName
] : [
initTasks.setAppName
]
// Perform init tasks
self.onReady = Promise.each(initTasksQueue, t => t()).return(true)
return self
}
}
'use strict' 'use strict'
/* global lang, winston */ /* global wiki */
const path = require('path') const path = require('path')
const Promise = require('bluebird') const Promise = require('bluebird')
...@@ -10,7 +10,7 @@ const os = require('os') ...@@ -10,7 +10,7 @@ const os = require('os')
const _ = require('lodash') const _ = require('lodash')
/** /**
* Local Data Storage * Local Disk Storage
*/ */
module.exports = { module.exports = {
...@@ -21,29 +21,26 @@ module.exports = { ...@@ -21,29 +21,26 @@ module.exports = {
/** /**
* Initialize Local Data Storage model * Initialize Local Data Storage model
*
* @return {Object} Local Data Storage model instance
*/ */
init () { init () {
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads') this._uploadsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, 'uploads')
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs') this._uploadsThumbsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'thumbs')
this.createBaseDirectories(appconfig) this.createBaseDirectories()
this.initMulter(appconfig) // this.initMulter()
return this return this
}, },
/** /**
* Init Multer upload handlers * Init Multer upload handlers
*
* @param {Object} appconfig The application config
* @return {boolean} Void
*/ */
initMulter (appconfig) { initMulter () {
let maxFileSizes = { let maxFileSizes = {
img: appconfig.uploads.maxImageFileSize * 1024 * 1024, // img: wiki.config.uploads.maxImageFileSize * 1024 * 1024,
file: appconfig.uploads.maxOtherFileSize * 1024 * 1024 // file: wiki.config.uploads.maxOtherFileSize * 1024 * 1024
img: 3 * 1024 * 1024,
file: 10 * 1024 * 1024
} }
// -> IMAGES // -> IMAGES
...@@ -51,7 +48,7 @@ module.exports = { ...@@ -51,7 +48,7 @@ module.exports = {
this.uploadImgHandler = multer({ this.uploadImgHandler = multer({
storage: multer.diskStorage({ storage: multer.diskStorage({
destination: (req, f, cb) => { destination: (req, f, cb) => {
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload')) cb(null, path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'temp-upload'))
} }
}), }),
fileFilter: (req, f, cb) => { fileFilter: (req, f, cb) => {
...@@ -76,7 +73,7 @@ module.exports = { ...@@ -76,7 +73,7 @@ module.exports = {
this.uploadFileHandler = multer({ this.uploadFileHandler = multer({
storage: multer.diskStorage({ storage: multer.diskStorage({
destination: (req, f, cb) => { destination: (req, f, cb) => {
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload')) cb(null, path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'temp-upload'))
} }
}), }),
fileFilter: (req, f, cb) => { fileFilter: (req, f, cb) => {
...@@ -95,35 +92,30 @@ module.exports = { ...@@ -95,35 +92,30 @@ module.exports = {
/** /**
* Creates a base directories (Synchronous). * Creates a base directories (Synchronous).
*
* @param {Object} appconfig The application config
* @return {Void} Void
*/ */
createBaseDirectories (appconfig) { createBaseDirectories () {
winston.info('Checking data directories...')
try { try {
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data)) fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data))
fs.emptyDirSync(path.resolve(ROOTPATH, appconfig.paths.data)) fs.emptyDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data))
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './cache')) fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data, './cache'))
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './thumbs')) fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data, './thumbs'))
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload')) fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data, './temp-upload'))
if (os.type() !== 'Windows_NT') { if (os.type() !== 'Windows_NT') {
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'), '755') fs.chmodSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data, './temp-upload'), '755')
} }
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo)) fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.repo))
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo, './uploads')) fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, './uploads'))
if (os.type() !== 'Windows_NT') { if (os.type() !== 'Windows_NT') {
fs.chmodSync(path.resolve(ROOTPATH, appconfig.paths.repo, './uploads'), '755') fs.chmodSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, './uploads'), '755')
} }
} catch (err) { } catch (err) {
winston.error(err) wiki.logger.error(err)
} }
winston.info('Data and Repository directories are OK.') wiki.logger.info('Disk Data Paths: OK')
}, },
/** /**
...@@ -154,7 +146,7 @@ module.exports = { ...@@ -154,7 +146,7 @@ module.exports = {
*/ */
validateUploadsFilename (f, fld, isImage) { validateUploadsFilename (f, fld, isImage) {
let fObj = path.parse(f) let fObj = path.parse(f)
let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(new RegExp('[^a-z0-9-' + appdata.regex.cjk + appdata.regex.arabic + ']', 'g'), '') let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(new RegExp('[^a-z0-9-' + wiki.data.regex.cjk + wiki.data.regex.arabic + ']', 'g'), '')
let fext = _.toLower(fObj.ext) let fext = _.toLower(fObj.ext)
if (isImage && !_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) { if (isImage && !_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) {
...@@ -165,7 +157,7 @@ module.exports = { ...@@ -165,7 +157,7 @@ module.exports = {
let fpath = path.resolve(this._uploadsPath, fld, f) let fpath = path.resolve(this._uploadsPath, fld, f)
return fs.statAsync(fpath).then((s) => { return fs.statAsync(fpath).then((s) => {
throw new Error(lang.t('errors:fileexists', { path: f })) throw new Error(wiki.lang.t('errors:fileexists', { path: f }))
}).catch((err) => { }).catch((err) => {
if (err.code === 'ENOENT') { if (err.code === 'ENOENT') {
return f return f
......
'use strict' 'use strict'
/* global db, git, lang, mark, rights, search, winston */ /* global wiki */
const Promise = require('bluebird') const Promise = require('bluebird')
const path = require('path') const path = require('path')
...@@ -10,7 +10,7 @@ const _ = require('lodash') ...@@ -10,7 +10,7 @@ const _ = require('lodash')
const entryHelper = require('../helpers/entry') const entryHelper = require('../helpers/entry')
/** /**
* Entries Model * Documents Model
*/ */
module.exports = { module.exports = {
...@@ -25,10 +25,10 @@ module.exports = { ...@@ -25,10 +25,10 @@ module.exports = {
init() { init() {
let self = this let self = this
self._repoPath = path.resolve(ROOTPATH, appconfig.paths.repo) self._repoPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.repo)
self._cachePath = path.resolve(ROOTPATH, appconfig.paths.data, 'cache') self._cachePath = path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'cache')
appdata.repoPath = self._repoPath wiki.data.repoPath = self._repoPath
appdata.cachePath = self._cachePath wiki.data.cachePath = self._cachePath
return self return self
}, },
......
'use strict' 'use strict'
/* global lang, winston */ /* global wiki */
const Git = require('git-wrapper2-promise') const Git = require('git-wrapper2-promise')
const Promise = require('bluebird') const Promise = require('bluebird')
...@@ -43,21 +43,19 @@ module.exports = { ...@@ -43,21 +43,19 @@ module.exports = {
// -> Build repository path // -> Build repository path
if (_.isEmpty(appconfig.paths.repo)) { if (_.isEmpty(wiki.config.paths.repo)) {
self._repo.path = path.join(ROOTPATH, 'repo') self._repo.path = path.join(wiki.ROOTPATH, 'repo')
} else { } else {
self._repo.path = appconfig.paths.repo self._repo.path = wiki.config.paths.repo
} }
// -> Initialize repository // -> Initialize repository
self.onReady = self._initRepo(appconfig) self.onReady = (wiki.IS_MASTER) ? self._initRepo() : Promise.resolve()
if (appconfig.git) { if (wiki.config.git) {
// Set repo branch self._repo.branch = wiki.config.git.branch || 'master'
self._repo.branch = appconfig.git.branch || 'master' self._signature.email = wiki.config.git.serverEmail || 'wiki@example.com'
// Define signature
self._signature.email = appconfig.git.serverEmail || 'wiki@example.com'
} }
return self return self
...@@ -66,19 +64,17 @@ module.exports = { ...@@ -66,19 +64,17 @@ module.exports = {
/** /**
* Initialize Git repository * Initialize Git repository
* *
* @param {Object} appconfig The application config * @param {Object} wiki.config The application config
* @return {Object} Promise * @return {Object} Promise
*/ */
_initRepo(appconfig) { _initRepo() {
let self = this let self = this
winston.info('Checking Git repository...')
// -> Check if path is accessible // -> Check if path is accessible
return fs.mkdirAsync(self._repo.path).catch((err) => { return fs.mkdirAsync(self._repo.path).catch((err) => {
if (err.code !== 'EEXIST') { if (err.code !== 'EEXIST') {
winston.error('Invalid Git repository path or missing permissions.') wiki.logger.error('Invalid Git repository path or missing permissions.')
} }
}).then(() => { }).then(() => {
self._git = new Git({ 'git-dir': self._repo.path }) self._git = new Git({ 'git-dir': self._repo.path })
...@@ -92,28 +88,28 @@ module.exports = { ...@@ -92,28 +88,28 @@ module.exports = {
self._repo.exists = false self._repo.exists = false
}) })
}).then(() => { }).then(() => {
if (appconfig.git === false) { if (wiki.config.git === false) {
winston.info('Remote Git syncing is disabled. Not recommended!') wiki.logger.warn('Remote Git syncing is disabled. Not recommended!')
return Promise.resolve(true) return Promise.resolve(true)
} }
// Initialize remote // Initialize remote
let urlObj = URL.parse(appconfig.git.url) let urlObj = URL.parse(wiki.config.git.url)
if (appconfig.git.auth.type !== 'ssh') { if (wiki.config.git.auth.type !== 'ssh') {
urlObj.auth = appconfig.git.auth.username + ':' + appconfig.git.auth.password urlObj.auth = wiki.config.git.auth.username + ':' + wiki.config.git.auth.password
} }
self._url = URL.format(urlObj) self._url = URL.format(urlObj)
let gitConfigs = [ let gitConfigs = [
() => { return self._git.exec('config', ['--local', 'user.name', 'Wiki']) }, () => { return self._git.exec('config', ['--local', 'user.name', 'Wiki']) },
() => { return self._git.exec('config', ['--local', 'user.email', self._signature.email]) }, () => { return self._git.exec('config', ['--local', 'user.email', self._signature.email]) },
() => { return self._git.exec('config', ['--local', '--bool', 'http.sslVerify', _.toString(appconfig.git.auth.sslVerify)]) } () => { return self._git.exec('config', ['--local', '--bool', 'http.sslVerify', _.toString(wiki.config.git.auth.sslVerify)]) }
] ]
if (appconfig.git.auth.type === 'ssh') { if (wiki.config.git.auth.type === 'ssh') {
gitConfigs.push(() => { gitConfigs.push(() => {
return self._git.exec('config', ['--local', 'core.sshCommand', 'ssh -i "' + appconfig.git.auth.privateKey + '" -o StrictHostKeyChecking=no']) return self._git.exec('config', ['--local', 'core.sshCommand', 'ssh -i "' + wiki.config.git.auth.privateKey + '" -o StrictHostKeyChecking=no'])
}) })
} }
...@@ -126,14 +122,14 @@ module.exports = { ...@@ -126,14 +122,14 @@ module.exports = {
return self._git.exec('remote', ['set-url', 'origin', self._url]) return self._git.exec('remote', ['set-url', 'origin', self._url])
} }
}).catch(err => { }).catch(err => {
winston.error(err) wiki.logger.error(err)
}) })
}) })
}).catch((err) => { }).catch((err) => {
winston.error('Git remote error!') wiki.logger.error('Git remote error!')
throw err throw err
}).then(() => { }).then(() => {
winston.info('Git repository is OK.') wiki.logger.info('Git Repository: OK')
return true return true
}) })
}, },
...@@ -144,7 +140,7 @@ module.exports = { ...@@ -144,7 +140,7 @@ module.exports = {
* @return {String} The repo path. * @return {String} The repo path.
*/ */
getRepoPath() { getRepoPath() {
return this._repo.path || path.join(ROOTPATH, 'repo') return this._repo.path || path.join(wiki.ROOTPATH, 'repo')
}, },
/** /**
...@@ -157,18 +153,18 @@ module.exports = { ...@@ -157,18 +153,18 @@ module.exports = {
// Is git remote disabled? // Is git remote disabled?
if (appconfig.git === false) { if (wiki.config.git === false) {
return Promise.resolve(true) return Promise.resolve(true)
} }
// Fetch // Fetch
winston.info('Performing pull from remote Git repository...') wiki.logger.info('Performing pull from remote Git repository...')
return self._git.pull('origin', self._repo.branch).then((cProc) => { return self._git.pull('origin', self._repo.branch).then((cProc) => {
winston.info('Git Pull completed.') wiki.logger.info('Git Pull completed.')
}) })
.catch((err) => { .catch((err) => {
winston.error('Unable to fetch from git origin!') wiki.logger.error('Unable to fetch from git origin!')
throw err throw err
}) })
.then(() => { .then(() => {
...@@ -178,19 +174,19 @@ module.exports = { ...@@ -178,19 +174,19 @@ module.exports = {
let out = cProc.stdout.toString() let out = cProc.stdout.toString()
if (_.includes(out, 'commit')) { if (_.includes(out, 'commit')) {
winston.info('Performing push to remote Git repository...') wiki.logger.info('Performing push to remote Git repository...')
return self._git.push('origin', self._repo.branch).then(() => { return self._git.push('origin', self._repo.branch).then(() => {
return winston.info('Git Push completed.') return wiki.logger.info('Git Push completed.')
}) })
} else { } else {
winston.info('Git Push skipped. Repository is already in sync.') wiki.logger.info('Git Push skipped. Repository is already in sync.')
} }
return true return true
}) })
}) })
.catch((err) => { .catch((err) => {
winston.error('Unable to push changes to remote Git repository!') wiki.logger.error('Unable to push changes to remote Git repository!')
throw err throw err
}) })
}, },
...@@ -210,7 +206,7 @@ module.exports = { ...@@ -210,7 +206,7 @@ module.exports = {
let out = cProc.stdout.toString() let out = cProc.stdout.toString()
return _.includes(out, gitFilePath) return _.includes(out, gitFilePath)
}).then((isTracked) => { }).then((isTracked) => {
commitMsg = (isTracked) ? lang.t('git:updated', { path: gitFilePath }) : lang.t('git:added', { path: gitFilePath }) commitMsg = (isTracked) ? wiki.lang.t('git:updated', { path: gitFilePath }) : wiki.lang.t('git:added', { path: gitFilePath })
return self._git.add(gitFilePath) return self._git.add(gitFilePath)
}).then(() => { }).then(() => {
let commitUsr = securityHelper.sanitizeCommitUser(author) let commitUsr = securityHelper.sanitizeCommitUser(author)
...@@ -246,29 +242,6 @@ module.exports = { ...@@ -246,29 +242,6 @@ module.exports = {
}, },
/** /**
* Delete a document.
*
* @param {String} entryPath The entry path
* @return {Promise<Boolean>} Resolve on success
*/
deleteDocument(entryPath, author) {
let self = this
let gitFilePath = entryPath + '.md'
return this._git.exec('rm', [gitFilePath]).then((cProc) => {
let out = cProc.stdout.toString()
if (_.includes(out, 'fatal')) {
let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ',')))
throw new Error(errorMsg)
}
let commitUsr = securityHelper.sanitizeCommitUser(author)
return self._git.exec('commit', ['-m', lang.t('git:deleted', { path: gitFilePath }), '--author="' + commitUsr.name + ' <' + commitUsr.email + '>"']).catch((err) => {
if (_.includes(err.stdout, 'nothing to commit')) { return true }
})
})
},
/**
* Commits uploads changes. * Commits uploads changes.
* *
* @param {String} msg The commit message * @param {String} msg The commit message
......
'use strict'
/* global wiki */
const gqlTools = require('graphql-tools')
const fs = require('fs')
const path = require('path')
const _ = require('lodash')
const typeDefs = fs.readFileSync(path.join(wiki.SERVERPATH, 'schemas/types.graphql'), 'utf8')
const DateScalar = require('../schemas/scalar-date')
const AuthenticationResolvers = require('../schemas/resolvers-authentication')
const CommentResolvers = require('../schemas/resolvers-comment')
const DocumentResolvers = require('../schemas/resolvers-document')
const FileResolvers = require('../schemas/resolvers-file')
const FolderResolvers = require('../schemas/resolvers-folder')
const GroupResolvers = require('../schemas/resolvers-group')
const SettingResolvers = require('../schemas/resolvers-setting')
const TagResolvers = require('../schemas/resolvers-tag')
const TranslationResolvers = require('../schemas/resolvers-translation')
const UserResolvers = require('../schemas/resolvers-user')
const resolvers = _.merge(
AuthenticationResolvers,
CommentResolvers,
DocumentResolvers,
FileResolvers,
FolderResolvers,
GroupResolvers,
SettingResolvers,
TagResolvers,
TranslationResolvers,
UserResolvers,
DateScalar
)
const Schema = gqlTools.makeExecutableSchema({
typeDefs,
resolvers
})
module.exports = Schema
const cluster = require('cluster')
const Promise = require('bluebird')
const _ = require('lodash')
/* global wiki */
module.exports = {
numWorkers: 1,
workers: [],
init() {
if (cluster.isMaster) {
wiki.logger.info('=======================================')
wiki.logger.info('= Wiki.js =============================')
wiki.logger.info('=======================================')
wiki.redis = require('./redis').init()
wiki.queue = require('./queue').init()
this.setWorkerLimit()
this.bootMaster()
} else {
this.bootWorker()
}
},
/**
* Pre-Master Boot Sequence
*/
preBootMaster() {
return Promise.mapSeries([
() => { return wiki.db.onReady },
() => { return wiki.configSvc.loadFromDb() },
() => { return wiki.queue.clean() }
], fn => { return fn() })
},
/**
* Boot Master Process
*/
bootMaster() {
this.preBootMaster().then(sequenceResults => {
if (_.every(sequenceResults, rs => rs === true)) {
this.postBootMaster()
} else {
wiki.logger.info('Starting configuration manager...')
require('../configure')()
}
return true
}).catch(err => {
wiki.logger.error(err)
process.exit(1)
})
},
/**
* Post-Master Boot Sequence
*/
postBootMaster() {
require('../master')().then(() => {
_.times(this.numWorker, this.spawnWorker)
wiki.queue.uplClearTemp.add({}, {
repeat: { cron: '*/15 * * * *' }
})
})
cluster.on('exit', (worker, code, signal) => {
wiki.logger.info(`Background Worker #${worker.id} was terminated.`)
})
},
/**
* Boot Worker Process
*/
bootWorker() {
wiki.logger.info(`Background Worker #${cluster.worker.id} is initializing...`)
require('../worker')
},
/**
* Spawn new Worker process
*/
spawnWorker() {
this.workers.push(cluster.fork())
},
/**
* Set Worker count based on config + system capabilities
*/
setWorkerLimit() {
const numCPUs = require('os').cpus().length
this.numWorkers = (wiki.config.workers > 0) ? wiki.config.workers : numCPUs
if (this.numWorkers > numCPUs) {
this.numWorkers = numCPUs
}
}
}
const _ = require('lodash')
const dotize = require('dotize')
const i18nBackend = require('i18next-node-fs-backend')
const i18next = require('i18next')
const path = require('path')
const Promise = require('bluebird')
/* global wiki */
module.exports = {
engine: null,
namespaces: ['common', 'admin', 'auth', 'errors', 'git'],
init() {
this.engine = i18next
this.engine.use(i18nBackend).init({
load: 'languageOnly',
ns: this.namespaces,
defaultNS: 'common',
saveMissing: false,
preload: [wiki.config.site.lang],
lng: wiki.config.site.lang,
fallbackLng: 'en',
backend: {
loadPath: path.join(wiki.SERVERPATH, 'locales/{{lng}}/{{ns}}.json')
}
})
return this
},
getByNamespace(locale, namespace) {
if (this.engine.hasResourceBundle(locale, namespace)) {
let data = this.engine.getResourceBundle(locale, namespace)
return _.map(dotize.convert(data), (value, key) => {
return {
key,
value
}
})
} else {
throw new Error('Invalid locale or namespace')
}
},
loadLocale(locale) {
return Promise.fromCallback(cb => {
return this.engine.loadLanguages(locale, cb)
})
},
setCurrentLocale(locale) {
return Promise.fromCallback(cb => {
return this.engine.changeLanguage(locale, cb)
})
}
}
'use strict'
/* global wiki */
const cluster = require('cluster')
module.exports = {
init() {
let winston = require('winston')
// Console
let logger = new (winston.Logger)({
level: (wiki.IS_DEBUG) ? 'debug' : 'info',
transports: [
new (winston.transports.Console)({
level: (wiki.IS_DEBUG) ? 'debug' : 'info',
prettyPrint: true,
colorize: true,
silent: false,
timestamp: true
})
]
})
logger.filters.push((level, msg) => {
let processName = (cluster.isMaster) ? 'MASTER' : `WORKER-${cluster.worker.id}`
return '[' + processName + '] ' + msg
})
// External services
// if (wiki.config.externalLogging.bugsnag) {
// const bugsnagTransport = require('./winston-transports/bugsnag')
// logger.add(bugsnagTransport, {
// level: 'warn',
// key: wiki.config.externalLogging.bugsnag
// })
// }
// if (wiki.config.externalLogging.loggly) {
// require('winston-loggly-bulk')
// logger.add(winston.transports.Loggly, {
// token: wiki.config.externalLogging.loggly.token,
// subdomain: wiki.config.externalLogging.loggly.subdomain,
// tags: ['wiki-js'],
// level: 'warn',
// json: true
// })
// }
// if (wiki.config.externalLogging.papertrail) {
// require('winston-papertrail').Papertrail // eslint-disable-line no-unused-expressions
// logger.add(winston.transports.Papertrail, {
// host: wiki.config.externalLogging.papertrail.host,
// port: wiki.config.externalLogging.papertrail.port,
// level: 'warn',
// program: 'wiki.js'
// })
// }
// if (wiki.config.externalLogging.rollbar) {
// const rollbarTransport = require('./winston-transports/rollbar')
// logger.add(rollbarTransport, {
// level: 'warn',
// key: wiki.config.externalLogging.rollbar
// })
// }
// if (wiki.config.externalLogging.sentry) {
// const sentryTransport = require('./winston-transports/sentry')
// logger.add(sentryTransport, {
// level: 'warn',
// key: wiki.config.externalLogging.sentry
// })
// }
return logger
}
}
'use strict' 'use strict'
/* global winston */ /* global wiki */
const Promise = require('bluebird') const Promise = require('bluebird')
const md = require('markdown-it') const md = require('markdown-it')
...@@ -23,11 +23,12 @@ const mdRemove = require('remove-markdown') ...@@ -23,11 +23,12 @@ const mdRemove = require('remove-markdown')
var mkdown = md({ var mkdown = md({
html: true, html: true,
breaks: appconfig.features.linebreaks, // breaks: wiki.config.features.linebreaks,
breaks: true,
linkify: true, linkify: true,
typography: true, typography: true,
highlight(str, lang) { highlight(str, lang) {
if (appconfig.theme.code.colorize && lang && hljs.getLanguage(lang)) { if (wiki.config.theme.code.colorize && lang && hljs.getLanguage(lang)) {
try { try {
return '<pre class="hljs"><code>' + hljs.highlight(lang, str, true).value + '</code></pre>' return '<pre class="hljs"><code>' + hljs.highlight(lang, str, true).value + '</code></pre>'
} catch (err) { } catch (err) {
...@@ -57,7 +58,8 @@ var mkdown = md({ ...@@ -57,7 +58,8 @@ var mkdown = md({
}) })
.use(mdAttrs) .use(mdAttrs)
if (appconfig.features.mathjax) { // if (wiki.config.features.mathjax) {
if (true) {
mkdown.use(mdMathjax) mkdown.use(mdMathjax)
} }
...@@ -94,7 +96,7 @@ const videoRules = [ ...@@ -94,7 +96,7 @@ const videoRules = [
// Regex // Regex
const textRegex = new RegExp('\\b[a-z0-9-.,' + appdata.regex.cjk + appdata.regex.arabic + ']+\\b', 'g') const textRegex = new RegExp('\\b[a-z0-9-.,' + wiki.data.regex.cjk + wiki.data.regex.arabic + ']+\\b', 'g')
const mathRegex = [ const mathRegex = [
{ {
format: 'TeX', format: 'TeX',
...@@ -301,7 +303,7 @@ const parseContent = (content) => { ...@@ -301,7 +303,7 @@ const parseContent = (content) => {
// Mathjax Post-processor // Mathjax Post-processor
if (appconfig.features.mathjax) { if (wiki.config.features.mathjax) {
return processMathjax(cr.html()) return processMathjax(cr.html())
} else { } else {
return Promise.resolve(cr.html()) return Promise.resolve(cr.html())
...@@ -339,7 +341,7 @@ const processMathjax = (content) => { ...@@ -339,7 +341,7 @@ const processMathjax = (content) => {
resolve(result.svg) resolve(result.svg)
} else { } else {
resolve(currentMatch[0]) resolve(currentMatch[0])
winston.warn(result.errors.join(', ')) wiki.logger.warn(result.errors.join(', '))
} }
}) })
}) })
......
'use strict'
/* global wiki */
const Bull = require('bull')
const Promise = require('bluebird')
module.exports = {
init() {
wiki.data.queues.forEach(queueName => {
this[queueName] = new Bull(queueName, {
prefix: `q-${wiki.config.ha.nodeuid}`,
redis: wiki.config.redis
})
})
return this
},
clean() {
return Promise.each(wiki.data.queues, queueName => {
return new Promise((resolve, reject) => {
let keyStream = wiki.redis.scanStream({
match: `q-${wiki.config.ha.nodeuid}:${queueName}:*`
})
keyStream.on('data', resultKeys => {
if (resultKeys.length > 0) {
wiki.redis.del(resultKeys)
}
})
keyStream.on('end', resolve)
})
}).then(() => {
wiki.logger.info('Purging old queue jobs: OK')
}).return(true).catch(err => {
wiki.logger.error(err)
})
}
}
'use strict'
/* global wiki */
const Redis = require('ioredis')
const { isPlainObject } = require('lodash')
/**
* Redis module
*
* @return {Object} Redis client wrapper instance
*/
module.exports = {
/**
* Initialize Redis client
*
* @return {Object} Redis client instance
*/
init() {
if (isPlainObject(wiki.config.redis)) {
let red = new Redis(wiki.config.redis)
red.on('ready', () => {
wiki.logger.info('Redis connection: OK')
})
return red
} else {
wiki.logger.error('Invalid Redis configuration!')
process.exit(1)
}
}
}
'use strict' 'use strict'
/* global db */ /* global wiki */
const _ = require('lodash') const _ = require('lodash')
...@@ -32,8 +32,8 @@ module.exports = { ...@@ -32,8 +32,8 @@ module.exports = {
init () { init () {
let self = this let self = this
db.onReady.then(() => { wiki.db.onReady.then(() => {
db.User.findOne({ provider: 'local', email: 'guest' }).then((u) => { wiki.db.User.findOne({ provider: 'local', email: 'guest' }).then((u) => {
if (u) { if (u) {
self.guest = u self.guest = u
} }
......
'use strict' 'use strict'
/* global winston */ /* global wiki */
const Promise = require('bluebird') const Promise = require('bluebird')
const _ = require('lodash') const _ = require('lodash')
const searchIndex = require('./search-index') // const searchIndex = require('./search-index')
const stopWord = require('stopword') const stopWord = require('stopword')
const streamToPromise = require('stream-to-promise') const streamToPromise = require('stream-to-promise')
const searchAllowedChars = new RegExp('[^a-z0-9' + appdata.regex.cjk + appdata.regex.arabic + ' ]', 'g') const searchAllowedChars = new RegExp('[^a-z0-9' + wiki.data.regex.cjk + wiki.data.regex.arabic + ' ]', 'g')
module.exports = { module.exports = {
...@@ -22,24 +22,24 @@ module.exports = { ...@@ -22,24 +22,24 @@ module.exports = {
init () { init () {
let self = this let self = this
self._isReady = new Promise((resolve, reject) => { self._isReady = new Promise((resolve, reject) => {
searchIndex({ /*searchIndex({
deletable: true, deletable: true,
fieldedSearch: true, fieldedSearch: true,
indexPath: 'wiki', indexPath: 'wiki',
logLevel: 'error', logLevel: 'error',
stopwords: _.get(stopWord, appconfig.lang, []) stopwords: _.get(stopWord, wiki.config.lang, [])
}, (err, si) => { }, (err, si) => {
if (err) { if (err) {
winston.error('Failed to initialize search index.', err) wiki.logger.error('Failed to initialize search index.', err)
reject(err) reject(err)
} else { } else {
self._si = Promise.promisifyAll(si) self._si = Promise.promisifyAll(si)
self._si.flushAsync().then(() => { self._si.flushAsync().then(() => {
winston.info('Search index flushed and ready.') wiki.logger.info('Search index flushed and ready.')
resolve(true) resolve(true)
}) })
} }
}) }) */
}) })
return self return self
...@@ -95,13 +95,13 @@ module.exports = { ...@@ -95,13 +95,13 @@ module.exports = {
parent: content.parent || '', parent: content.parent || '',
content: content.text || '' content: content.text || ''
}]).then(() => { }]).then(() => {
winston.log('verbose', 'Entry ' + content._id + ' added/updated to search index.') wiki.logger.log('verbose', 'Entry ' + content._id + ' added/updated to search index.')
return true return true
}).catch((err) => { }).catch((err) => {
winston.error(err) wiki.logger.error(err)
}) })
}).catch((err) => { }).catch((err) => {
winston.error(err) wiki.logger.error(err)
}) })
}) })
}, },
...@@ -131,7 +131,7 @@ module.exports = { ...@@ -131,7 +131,7 @@ module.exports = {
if (err.type === 'NotFoundError') { if (err.type === 'NotFoundError') {
return true return true
} else { } else {
winston.error(err) wiki.logger.error(err)
} }
}) })
}) })
...@@ -204,7 +204,7 @@ module.exports = { ...@@ -204,7 +204,7 @@ module.exports = {
suggest: [] suggest: []
} }
} else { } else {
winston.error(err) wiki.logger.error(err)
} }
}) })
} }
......
'use strict' 'use strict'
/* global db, git, lang, upl */ /* global wiki */
const path = require('path') const path = require('path')
const Promise = require('bluebird') const Promise = require('bluebird')
...@@ -32,8 +32,8 @@ module.exports = { ...@@ -32,8 +32,8 @@ module.exports = {
init () { init () {
let self = this let self = this
self._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads') self._uploadsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, 'uploads')
self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs') self._uploadsThumbsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'thumbs')
return self return self
}, },
...@@ -59,16 +59,16 @@ module.exports = { ...@@ -59,16 +59,16 @@ module.exports = {
self._watcher.on('add', (p) => { self._watcher.on('add', (p) => {
let pInfo = self.parseUploadsRelPath(p) let pInfo = self.parseUploadsRelPath(p)
return self.processFile(pInfo.folder, pInfo.filename).then((mData) => { return self.processFile(pInfo.folder, pInfo.filename).then((mData) => {
return db.UplFile.findByIdAndUpdate(mData._id, mData, { upsert: true }) return wiki.db.UplFile.findByIdAndUpdate(mData._id, mData, { upsert: true })
}).then(() => { }).then(() => {
return git.commitUploads(lang.t('git:uploaded', { path: p })) return wiki.git.commitUploads(wiki.lang.t('git:uploaded', { path: p }))
}) })
}) })
// -> Remove upload file // -> Remove upload file
self._watcher.on('unlink', (p) => { self._watcher.on('unlink', (p) => {
return git.commitUploads(lang.t('git:deleted', { path: p })) return wiki.git.commitUploads(wiki.lang.t('git:deleted', { path: p }))
}) })
}, },
...@@ -91,8 +91,8 @@ module.exports = { ...@@ -91,8 +91,8 @@ module.exports = {
// Add folders to DB // Add folders to DB
return db.UplFolder.remove({}).then(() => { return wiki.db.UplFolder.remove({}).then(() => {
return db.UplFolder.insertMany(_.map(folderNames, (f) => { return wiki.db.UplFolder.insertMany(_.map(folderNames, (f) => {
return { return {
_id: 'f:' + f, _id: 'f:' + f,
name: f name: f
...@@ -107,7 +107,7 @@ module.exports = { ...@@ -107,7 +107,7 @@ module.exports = {
let fldPath = path.join(self._uploadsPath, fldName) let fldPath = path.join(self._uploadsPath, fldName)
return fs.readdirAsync(fldPath).then((fList) => { return fs.readdirAsync(fldPath).then((fList) => {
return Promise.map(fList, (f) => { return Promise.map(fList, (f) => {
return upl.processFile(fldName, f).then((mData) => { return wiki.upl.processFile(fldName, f).then((mData) => {
if (mData) { if (mData) {
allFiles.push(mData) allFiles.push(mData)
} }
...@@ -118,9 +118,9 @@ module.exports = { ...@@ -118,9 +118,9 @@ module.exports = {
}, {concurrency: 1}).finally(() => { }, {concurrency: 1}).finally(() => {
// Add files to DB // Add files to DB
return db.UplFile.remove({}).then(() => { return wiki.db.UplFile.remove({}).then(() => {
if (_.isArray(allFiles) && allFiles.length > 0) { if (_.isArray(allFiles) && allFiles.length > 0) {
return db.UplFile.insertMany(allFiles) return wiki.db.UplFile.insertMany(allFiles)
} else { } else {
return true return true
} }
...@@ -131,7 +131,7 @@ module.exports = { ...@@ -131,7 +131,7 @@ module.exports = {
}).then(() => { }).then(() => {
// Watch for new changes // Watch for new changes
return upl.watch() return wiki.upl.watch()
}) })
}, },
......
'use strict' 'use strict'
/* global db, lang, lcdata, upl, winston */ /* global wiki */
const path = require('path') const path = require('path')
const Promise = require('bluebird') const Promise = require('bluebird')
...@@ -27,8 +27,8 @@ module.exports = { ...@@ -27,8 +27,8 @@ module.exports = {
* @return {Object} Uploads model instance * @return {Object} Uploads model instance
*/ */
init () { init () {
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads') this._uploadsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, 'uploads')
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs') this._uploadsThumbsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'thumbs')
return this return this
}, },
...@@ -48,7 +48,7 @@ module.exports = { ...@@ -48,7 +48,7 @@ module.exports = {
* @return {Array<String>} The uploads folders. * @return {Array<String>} The uploads folders.
*/ */
getUploadsFolders () { getUploadsFolders () {
return db.UplFolder.find({}, 'name').sort('name').exec().then((results) => { return wiki.db.Folder.find({}, 'name').sort('name').exec().then((results) => {
return (results) ? _.map(results, 'name') : [{ name: '' }] return (results) ? _.map(results, 'name') : [{ name: '' }]
}) })
}, },
...@@ -69,7 +69,7 @@ module.exports = { ...@@ -69,7 +69,7 @@ module.exports = {
} }
return fs.ensureDirAsync(path.join(self._uploadsPath, folderName)).then(() => { return fs.ensureDirAsync(path.join(self._uploadsPath, folderName)).then(() => {
return db.UplFolder.findOneAndUpdate({ return wiki.db.UplFolder.findOneAndUpdate({
_id: 'f:' + folderName _id: 'f:' + folderName
}, { }, {
name: folderName name: folderName
...@@ -88,7 +88,7 @@ module.exports = { ...@@ -88,7 +88,7 @@ module.exports = {
* @return {Boolean} True if valid * @return {Boolean} True if valid
*/ */
validateUploadsFolder (folderName) { validateUploadsFolder (folderName) {
return db.UplFolder.findOne({ name: folderName }).then((f) => { return wiki.db.UplFolder.findOne({ name: folderName }).then((f) => {
return (f) ? path.resolve(this._uploadsPath, folderName) : false return (f) ? path.resolve(this._uploadsPath, folderName) : false
}) })
}, },
...@@ -101,7 +101,7 @@ module.exports = { ...@@ -101,7 +101,7 @@ module.exports = {
*/ */
addUploadsFiles (arrFiles) { addUploadsFiles (arrFiles) {
if (_.isArray(arrFiles) || _.isPlainObject(arrFiles)) { if (_.isArray(arrFiles) || _.isPlainObject(arrFiles)) {
// this._uploadsDb.Files.insert(arrFiles); // this._uploadswiki.Db.Files.insert(arrFiles);
} }
}, },
...@@ -113,7 +113,7 @@ module.exports = { ...@@ -113,7 +113,7 @@ module.exports = {
* @return {Array<Object>} The files matching the query * @return {Array<Object>} The files matching the query
*/ */
getUploadsFiles (cat, fld) { getUploadsFiles (cat, fld) {
return db.UplFile.find({ return wiki.db.UplFile.find({
category: cat, category: cat,
folder: 'f:' + fld folder: 'f:' + fld
}).sort('filename').exec() }).sort('filename').exec()
...@@ -128,7 +128,7 @@ module.exports = { ...@@ -128,7 +128,7 @@ module.exports = {
deleteUploadsFile (uid) { deleteUploadsFile (uid) {
let self = this let self = this
return db.UplFile.findOneAndRemove({ _id: uid }).then((f) => { return wiki.db.UplFile.findOneAndRemove({ _id: uid }).then((f) => {
if (f) { if (f) {
return self.deleteUploadsFileTry(f, 0) return self.deleteUploadsFileTry(f, 0)
} }
...@@ -150,7 +150,7 @@ module.exports = { ...@@ -150,7 +150,7 @@ module.exports = {
return self.deleteUploadsFileTry(f, attempt + 1) return self.deleteUploadsFileTry(f, attempt + 1)
}) })
} else { } else {
winston.warn('Unable to delete uploads file ' + f.filename + '. File is locked by another process and multiple attempts failed.') wiki.logger.warn('Unable to delete uploads file ' + f.filename + '. File is locked by another process and multiple attempts failed.')
return true return true
} }
}) })
...@@ -168,12 +168,12 @@ module.exports = { ...@@ -168,12 +168,12 @@ module.exports = {
let fUrlFilename = _.last(_.split(fUrlObj.pathname, '/')) let fUrlFilename = _.last(_.split(fUrlObj.pathname, '/'))
let destFolder = _.chain(fFolder).trim().toLower().value() let destFolder = _.chain(fFolder).trim().toLower().value()
return upl.validateUploadsFolder(destFolder).then((destFolderPath) => { return wiki.upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
if (!destFolderPath) { if (!destFolderPath) {
return Promise.reject(new Error(lang.t('errors:invalidfolder'))) return Promise.reject(new Error(wiki.lang.t('errors:invalidfolder')))
} }
return lcdata.validateUploadsFilename(fUrlFilename, destFolder).then((destFilename) => { return wiki.disk.validateUploadsFilename(fUrlFilename, destFolder).then((destFilename) => {
let destFilePath = path.resolve(destFolderPath, destFilename) let destFilePath = path.resolve(destFolderPath, destFilename)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
...@@ -194,7 +194,7 @@ module.exports = { ...@@ -194,7 +194,7 @@ module.exports = {
rq.abort() rq.abort()
destFileStream.destroy() destFileStream.destroy()
fs.remove(destFilePath) fs.remove(destFilePath)
reject(new Error(lang.t('errors:remotetoolarge'))) reject(new Error(wiki.lang.t('errors:remotetoolarge')))
} }
}).on('error', (err) => { }).on('error', (err) => {
destFileStream.destroy() destFileStream.destroy()
...@@ -223,15 +223,15 @@ module.exports = { ...@@ -223,15 +223,15 @@ module.exports = {
moveUploadsFile (uid, fld, nFilename) { moveUploadsFile (uid, fld, nFilename) {
let self = this let self = this
return db.UplFolder.findById('f:' + fld).then((folder) => { return wiki.db.UplFolder.finwiki.dById('f:' + fld).then((folder) => {
if (folder) { if (folder) {
return db.UplFile.findById(uid).then((originFile) => { return wiki.db.UplFile.finwiki.dById(uid).then((originFile) => {
// -> Check if rename is valid // -> Check if rename is valid
let nameCheck = null let nameCheck = null
if (nFilename) { if (nFilename) {
let originFileObj = path.parse(originFile.filename) let originFileObj = path.parse(originFile.filename)
nameCheck = lcdata.validateUploadsFilename(nFilename + originFileObj.ext, folder.name) nameCheck = wiki.disk.validateUploadsFilename(nFilename + originFileObj.ext, folder.name)
} else { } else {
nameCheck = Promise.resolve(originFile.filename) nameCheck = Promise.resolve(originFile.filename)
} }
...@@ -245,12 +245,12 @@ module.exports = { ...@@ -245,12 +245,12 @@ module.exports = {
// -> Check for invalid operations // -> Check for invalid operations
if (sourceFilePath === destFilePath) { if (sourceFilePath === destFilePath) {
return Promise.reject(new Error(lang.t('errors:invalidoperation'))) return Promise.reject(new Error(wiki.lang.t('errors:invalidoperation')))
} }
// -> Delete DB entry // -> Delete wiki.DB entry
preMoveOps.push(db.UplFile.findByIdAndRemove(uid)) preMoveOps.push(wiki.db.UplFile.finwiki.dByIdAndRemove(uid))
// -> Move thumbnail ahead to avoid re-generation // -> Move thumbnail ahead to avoid re-generation
...@@ -273,7 +273,7 @@ module.exports = { ...@@ -273,7 +273,7 @@ module.exports = {
}) })
}) })
} else { } else {
return Promise.reject(new Error(lang.t('errors:invaliddestfolder'))) return Promise.reject(new Error(wiki.lang.t('errors:invaliddestfolder')))
} }
}) })
} }
......
'use strict'
/* global wiki */
const Promise = require('bluebird')
const fs = Promise.promisifyAll(require('fs-extra'))
const klaw = require('klaw')
const moment = require('moment')
const path = require('path')
const entryHelper = require('../helpers/entry')
module.exports = (job) => {
return wiki.git.resync().then(() => {
// -> Stream all documents
let cacheJobs = []
let jobCbStreamDocsResolve = null
let jobCbStreamDocs = new Promise((resolve, reject) => {
jobCbStreamDocsResolve = resolve
})
klaw(wiki.REPOPATH).on('data', function (item) {
if (path.extname(item.path) === '.md' && path.basename(item.path) !== 'README.md') {
let entryPath = entryHelper.parsePath(entryHelper.getEntryPathFromFullPath(item.path))
let cachePath = entryHelper.getCachePath(entryPath)
// -> Purge outdated cache
cacheJobs.push(
fs.statAsync(cachePath).then((st) => {
return moment(st.mtime).isBefore(item.stats.mtime) ? 'expired' : 'active'
}).catch((err) => {
return (err.code !== 'EEXIST') ? err : 'new'
}).then((fileStatus) => {
// -> Delete expired cache file
if (fileStatus === 'expired') {
return fs.unlinkAsync(cachePath).return(fileStatus)
}
return fileStatus
}).then((fileStatus) => {
// -> Update cache and search index
if (fileStatus !== 'active') {
return global.entries.updateCache(entryPath).then(entry => {
process.send({
action: 'searchAdd',
content: entry
})
return true
})
}
return true
})
)
}
}).on('end', () => {
jobCbStreamDocsResolve(Promise.all(cacheJobs))
})
return jobCbStreamDocs
}).then(() => {
wiki.logger.info('Git remote repository sync: DONE')
return true
})
}
'use strict'
/* global wiki */
const Promise = require('bluebird')
const fs = Promise.promisifyAll(require('fs-extra'))
const moment = require('moment')
const path = require('path')
module.exports = (job) => {
return fs.readdirAsync(wiki.UPLTEMPPATH).then((ls) => {
let fifteenAgo = moment().subtract(15, 'minutes')
return Promise.map(ls, (f) => {
return fs.statAsync(path.join(wiki.UPLTEMPPATH, f)).then((s) => { return { filename: f, stat: s } })
}).filter((s) => { return s.stat.isFile() }).then((arrFiles) => {
return Promise.map(arrFiles, (f) => {
if (moment(f.stat.ctime).isBefore(fifteenAgo, 'minute')) {
return fs.unlinkAsync(path.join(wiki.UPLTEMPPATH, f.filename))
} else {
return true
}
})
})
}).then(() => {
wiki.logger.info('Purging temporary upload files: DONE')
return true
})
}
const _ = require('lodash')
const fs = require('fs-extra')
const path = require('path')
/* global wiki */
module.exports = {
Query: {
authentication(obj, args, context, info) {
switch (args.mode) {
case 'active':
let strategies = _.chain(wiki.auth.strategies).map(str => {
return {
key: str.key,
title: str.title,
useForm: str.useForm
}
}).sortBy(['title']).value()
let localStrategy = _.remove(strategies, str => str.key === 'local')
return _.concat(localStrategy, strategies)
case 'all':
break
default:
return null
}
}
},
Mutation: {},
AuthenticationProvider: {
icon (ap, args) {
return fs.readFileAsync(path.join(wiki.ROOTPATH, `assets/svg/auth-icon-${ap.key}.svg`), 'utf8').catch(err => {
if (err.code === 'ENOENT') {
return null
}
throw err
})
}
}
}
/* global wiki */
module.exports = {
Query: {
comments(obj, args, context, info) {
return wiki.db.Comment.findAll({ where: args })
}
},
Mutation: {
createComment(obj, args) {
return wiki.db.Comment.create({
content: args.content,
author: args.userId,
document: args.documentId
})
},
deleteComment(obj, args) {
return wiki.db.Comment.destroy({
where: {
id: args.id
},
limit: 1
})
},
modifyComment(obj, args) {
return wiki.db.Comment.update({
content: args.content
}, {
where: { id: args.id }
})
}
},
Comment: {
author(cm) {
return cm.getAuthor()
},
document(cm) {
return cm.getDocument()
}
}
}
/* global wiki */
module.exports = {
Query: {
documents(obj, args, context, info) {
return wiki.db.Document.findAll({ where: args })
}
},
Mutation: {
createDocument(obj, args) {
return wiki.db.Document.create(args)
},
deleteDocument(obj, args) {
return wiki.db.Document.destroy({
where: {
id: args.id
},
limit: 1
})
},
modifyDocument(obj, args) {
return wiki.db.Document.update({
title: args.title,
subtitle: args.subtitle
}, {
where: { id: args.id }
})
},
moveDocument(obj, args) {
return wiki.db.Document.update({
path: args.path
}, {
where: { id: args.id }
})
}
},
Document: {
comments(doc) {
return doc.getComments()
},
tags(doc) {
return doc.getTags()
}
}
}
/* global wiki */
const gql = require('graphql')
module.exports = {
Query: {
files(obj, args, context, info) {
return wiki.db.File.findAll({ where: args })
}
},
Mutation: {
uploadFile(obj, args) {
// todo
return wiki.db.File.create(args)
},
deleteFile(obj, args) {
return wiki.db.File.destroy({
where: {
id: args.id
},
limit: 1
})
},
renameFile(obj, args) {
return wiki.db.File.update({
filename: args.filename
}, {
where: { id: args.id }
})
},
moveFile(obj, args) {
return wiki.db.File.findById(args.fileId).then(fl => {
if (!fl) {
throw new gql.GraphQLError('Invalid File ID')
}
return wiki.db.Folder.findById(args.folderId).then(fld => {
if (!fld) {
throw new gql.GraphQLError('Invalid Folder ID')
}
return fl.setFolder(fld)
})
})
}
},
File: {
folder(fl) {
return fl.getFolder()
}
}
}
/* global wiki */
module.exports = {
Query: {
folders(obj, args, context, info) {
return wiki.db.Folder.findAll({ where: args })
}
},
Mutation: {
createFolder(obj, args) {
return wiki.db.Folder.create(args)
},
deleteGroup(obj, args) {
return wiki.db.Folder.destroy({
where: {
id: args.id
},
limit: 1
})
},
renameFolder(obj, args) {
return wiki.db.Folder.update({
name: args.name
}, {
where: { id: args.id }
})
}
},
Folder: {
files(grp) {
return grp.getFiles()
}
}
}
/* global wiki */
const gql = require('graphql')
module.exports = {
Query: {
groups(obj, args, context, info) {
return wiki.db.Group.findAll({ where: args })
}
},
Mutation: {
assignUserToGroup(obj, args) {
return wiki.db.Group.findById(args.groupId).then(grp => {
if (!grp) {
throw new gql.GraphQLError('Invalid Group ID')
}
return wiki.db.User.findById(args.userId).then(usr => {
if (!usr) {
throw new gql.GraphQLError('Invalid User ID')
}
return grp.addUser(usr)
})
})
},
createGroup(obj, args) {
return wiki.db.Group.create(args)
},
deleteGroup(obj, args) {
return wiki.db.Group.destroy({
where: {
id: args.id
},
limit: 1
})
},
removeUserFromGroup(obj, args) {
return wiki.db.Group.findById(args.groupId).then(grp => {
if (!grp) {
throw new gql.GraphQLError('Invalid Group ID')
}
return wiki.db.User.findById(args.userId).then(usr => {
if (!usr) {
throw new gql.GraphQLError('Invalid User ID')
}
return grp.removeUser(usr)
})
})
},
renameGroup(obj, args) {
return wiki.db.Group.update({
name: args.name
}, {
where: { id: args.id }
})
}
},
Group: {
users(grp) {
return grp.getUsers()
}
}
}
/* global wiki */
const gql = require('graphql')
module.exports = {
Query: {
rights(obj, args, context, info) {
return wiki.db.Right.findAll({ where: args })
}
},
Mutation: {
addRightToGroup(obj, args) {
return wiki.db.Group.findById(args.groupId).then(grp => {
if (!grp) {
throw new gql.GraphQLError('Invalid Group ID')
}
return wiki.db.Right.create({
path: args.path,
role: args.role,
exact: args.exact,
allow: args.allow,
group: grp
})
})
},
removeRightFromGroup(obj, args) {
return wiki.db.Right.destroy({
where: {
id: args.rightId
},
limit: 1
})
},
modifyRight(obj, args) {
return wiki.db.Right.update({
path: args.path,
role: args.role,
exact: args.exact,
allow: args.allow
}, {
where: {
id: args.id
}
})
}
},
Right: {
group(rt) {
return rt.getGroup()
}
}
}
/* global wiki */
const _ = require('lodash')
module.exports = {
Query: {
settings(obj, args, context, info) {
return wiki.db.Setting.findAll({ where: args, raw: true }).then(entries => {
return _.map(entries, entry => {
entry.config = JSON.stringify(entry.config)
return entry
})
})
}
},
Mutation: {
setConfigEntry(obj, args) {
return wiki.db.Setting.update({
value: args.value
}, { where: { key: args.key } })
}
}
}
/* global wiki */
const gql = require('graphql')
module.exports = {
Query: {
tags(obj, args, context, info) {
return wiki.db.Tag.findAll({ where: args })
}
},
Mutation: {
assignTagToDocument(obj, args) {
return wiki.db.Tag.findById(args.tagId).then(tag => {
if (!tag) {
throw new gql.GraphQLError('Invalid Tag ID')
}
return wiki.db.Document.findById(args.documentId).then(doc => {
if (!doc) {
throw new gql.GraphQLError('Invalid Document ID')
}
return tag.addDocument(doc)
})
})
},
createTag(obj, args) {
return wiki.db.Tag.create(args)
},
deleteTag(obj, args) {
return wiki.db.Tag.destroy({
where: {
id: args.id
},
limit: 1
})
},
removeTagFromDocument(obj, args) {
return wiki.db.Tag.findById(args.tagId).then(tag => {
if (!tag) {
throw new gql.GraphQLError('Invalid Tag ID')
}
return wiki.db.Document.findById(args.documentId).then(doc => {
if (!doc) {
throw new gql.GraphQLError('Invalid Document ID')
}
return tag.removeDocument(doc)
})
})
},
renameTag(obj, args) {
return wiki.db.Group.update({
key: args.key
}, {
where: { id: args.id }
})
}
},
Tag: {
documents(tag) {
return tag.getDocuments()
}
}
}
/* global wiki */
module.exports = {
Query: {
translations (obj, args, context, info) {
return wiki.lang.getByNamespace(args.locale, args.namespace)
}
},
Mutation: {},
Translation: {}
}
/* global wiki */
module.exports = {
Query: {
users(obj, args, context, info) {
return wiki.db.User.findAll({ where: args })
}
},
Mutation: {
createUser(obj, args) {
return wiki.db.User.create(args)
},
deleteUser(obj, args) {
return wiki.db.User.destroy({
where: {
id: args.id
},
limit: 1
})
},
modifyUser(obj, args) {
return wiki.db.User.update({
email: args.email,
name: args.name,
provider: args.provider,
providerId: args.providerId,
role: args.role
}, {
where: { id: args.id }
})
},
resetUserPassword(obj, args) {
return false
},
setUserPassword(obj, args) {
return false
}
},
User: {
groups(usr) {
return usr.getGroups()
}
}
}
const gql = require('graphql')
module.exports = {
Date: new gql.GraphQLScalarType({
name: 'Date',
description: 'ISO date-time string at UTC',
parseValue(value) {
return new Date(value)
},
serialize(value) {
return value.toISOString()
},
parseLiteral(ast) {
if (ast.kind !== gql.Kind.STRING) {
throw new TypeError('Date value must be an string!')
}
return new Date(ast.value)
}
})
}
# SCALARS
scalar Date
# ENUMS
enum UserRole {
guest
user
admin
}
enum FileType {
binary
image
}
enum RightRole {
read
write
manage
}
# INTERFACES
interface Base {
id: Int!
createdAt: Date
updatedAt: Date
}
# TYPES
type AuthenticationProvider {
key: String!
useForm: Boolean!
title: String!
props: [String]
icon: String
config: String
}
type Comment implements Base {
id: Int!
createdAt: Date
updatedAt: Date
content: String
document: Document!
author: User!
}
type Document implements Base {
id: Int!
createdAt: Date
updatedAt: Date
path: String!
title: String!
subtitle: String
parentPath: String
parentTitle: String
isDirectory: Boolean!
isEntry: Boolean!
searchContent: String
comments: [Comment]
tags: [Tag]
}
type File implements Base {
id: Int!
createdAt: Date
updatedAt: Date
category: FileType!
mime: String!
extra: String
filename: String!
basename: String!
filesize: Int!
folder: Folder
}
type Folder implements Base {
id: Int!
createdAt: Date
updatedAt: Date
name: String!
files: [File]
}
type Group implements Base {
id: Int!
createdAt: Date
updatedAt: Date
name: String!
users: [User]
rights: [Right]
}
type Right implements Base {
id: Int!
createdAt: Date
updatedAt: Date
path: String!
role: RightRole!
exact: Boolean!
allow: Boolean!
group: Group!
}
type SearchResult {
path: String
title: String
tags: [String]
}
type Setting implements Base {
id: Int!
createdAt: Date
updatedAt: Date
key: String!
config: String!
}
# Tags are attached to one or more documents
type Tag implements Base {
id: Int!
createdAt: Date
updatedAt: Date
key: String!
documents: [Document]
}
type Translation {
key: String!
value: String!
}
# A User
type User implements Base {
id: Int!
createdAt: Date
updatedAt: Date
email: String!
provider: String!
providerId: String
name: String
role: UserRole!
groups: [Group]
}
type OperationResult {
succeded: Boolean!
message: String
}
# Query (Read)
type Query {
authentication(mode: String!): [AuthenticationProvider]
comments(id: Int): [Comment]
documents(id: Int, path: String): [Document]
files(id: Int): [File]
folders(id: Int, name: String): [Folder]
groups(id: Int, name: String): [Group]
rights(id: Int): [Right]
search(q: String, tags: [String]): [SearchResult]
settings(key: String): [Setting]
tags(key: String): [Tag]
translations(locale: String!, namespace: String!): [Translation]
users(id: Int, email: String, provider: String, providerId: String, role: UserRole): [User]
}
# Mutations (Create, Update, Delete)
type Mutation {
addRightToGroup(
groupId: Int!
path: String!
role: RightRole!
exact: Boolean!
allow: Boolean!
): Right
assignTagToDocument(
tagId: Int!
documentId: Int!
): OperationResult
assignUserToGroup(
userId: Int!
groupId: Int!
): OperationResult
createComment(
userId: Int!
documentId: Int!
content: String!
): Comment
createDocument(
path: String!
title: String!
subtitle: String
): Document
createFolder(
name: String!
): Folder
createGroup(
name: String!
): Group
createTag(
name: String!
): Tag
createUser(
email: String!
name: String
passwordRaw: String
provider: String!
providerId: String
role: UserRole!
): User
deleteComment(
id: Int!
): OperationResult
deleteDocument(
id: Int!
): OperationResult
deleteFile(
id: Int!
): OperationResult
deleteFolder(
id: Int!
): OperationResult
deleteGroup(
id: Int!
): OperationResult
deleteTag(
id: Int!
): OperationResult
deleteUser(
id: Int!
): OperationResult
modifyComment(
id: Int!
content: String!
): Document
modifyDocument(
id: Int!
title: String
subtitle: String
): Document
modifyUser(
id: Int!
email: String
name: String
provider: String
providerId: String
role: UserRole
): User
modifyRight(
id: Int!
path: String
role: RightRole
exact: Boolean
allow: Boolean
): Right
moveDocument(
id: Int!
path: String!
): OperationResult
moveFile(
id: Int!
folderId: Int!
): OperationResult
renameFile(
id: Int!
name: String!
): OperationResult
renameFolder(
id: Int!
name: String!
): OperationResult
renameGroup(
id: Int!
name: String!
): OperationResult
renameTag(
id: Int!
key: String!
): OperationResult
removeTagFromDocument(
tagId: Int!
documentId: Int!
): OperationResult
removeRightFromGroup(
rightId: Int!
): OperationResult
removeUserFromGroup(
userId: Int!
groupId: Int!
): OperationResult
resetUserPassword(
id: Int!
): OperationResult
setConfigEntry(
key: String!
value: String!
): OperationResult
setUserPassword(
id: Int!
passwordRaw: String!
): OperationResult
uploadFile(
category: FileType!
filename: String!
): File
}
doctype html extends ../master.pug
html(data-logic='login')
head
meta(http-equiv='X-UA-Compatible', content='IE=edge')
meta(charset='UTF-8')
meta(name='viewport', content='width=device-width, initial-scale=1')
meta(name='theme-color', content='#009688')
meta(name='msapplication-TileColor', content='#009688')
meta(name='msapplication-TileImage', content='/favicons/ms-icon-144x144.png')
title= appconfig.title
// Favicon
each favsize in [57, 60, 72, 76, 114, 120, 144, 152, 180]
link(rel='apple-touch-icon', sizes=favsize + 'x' + favsize, href='/favicons/apple-icon-' + favsize + 'x' + favsize + '.png')
link(rel='icon', type='image/png', sizes='192x192', href='/favicons/android-icon-192x192.png')
each favsize in [32, 96, 16]
link(rel='icon', type='image/png', sizes=favsize + 'x' + favsize, href='/favicons/favicon-' + favsize + 'x' + favsize + '.png')
link(rel='manifest', href='/manifest.json')
// JS / CSS
script(type='text/javascript', src=appconfig.host + '/js/vendor.js')
script(type='text/javascript', src=appconfig.host + '/js/app.js')
block body
body body
#bg #app.is-fullscreen
each bg in _.sampleSize([1, 2, 3],3) login
div(style='background-image:url(/images/bg_' + bg + '.jpg);')
#root
h1= appconfig.title
h2= t('auth:loginrequired')
if appflash.length > 0
h3
i.icon-warning-outline
= appflash[0].title
h4= appflash[0].message
if appconfig.auth.local.enabled
form(method='post', action='/login')
input#login-user(type='text', name='email', placeholder=t('auth:fields.emailuser'))
input#login-pass(type='password', name='password', placeholder=t('auth:fields.password'))
button(type='submit')= t('auth:actions.login')
if appconfig.authStrategies.socialEnabled
#social
if appconfig.auth.local.enabled
span= t('auth:loginusingalt')
else
span= t('auth:loginusing')
if appconfig.auth.microsoft && appconfig.auth.microsoft.enabled
button.ms(onclick='window.location.assign("/login/ms")')
i.icon-windows2
span= t('auth:providers.windowslive')
if appconfig.auth.azure && appconfig.auth.azure.enabled
button.ms(onclick='window.location.assign("/login/azure")')
i.icon-windows2
span= t('auth:providers.azure')
if appconfig.auth.google && appconfig.auth.google.enabled
button.google(onclick='window.location.assign("/login/google")')
i.icon-google
span= t('auth:providers.google')
if appconfig.auth.facebook && appconfig.auth.facebook.enabled
button.facebook(onclick='window.location.assign("/login/facebook")')
i.icon-facebook
span= t('auth:providers.facebook')
if appconfig.auth.github && appconfig.auth.github.enabled
button.github(onclick='window.location.assign("/login/github")')
i.icon-github
span= t('auth:providers.github')
if appconfig.auth.slack && appconfig.auth.slack.enabled
button.slack(onclick='window.location.assign("/login/slack")')
i.icon-slack
span= t('auth:providers.slack')
#copyright
= t('footer.poweredby') + ' '
a.icon(href='https://github.com/Requarks/wiki')
i.icon-github
a(href='https://wiki.requarks.io/') Wiki.js
doctype html extends ../master.pug
html(data-logic='configure')
head
meta(http-equiv='X-UA-Compatible', content='IE=edge')
meta(charset='UTF-8')
title Wiki.js | Configure
// Favicon
each favsize in [32, 96, 16]
link(rel='icon', type='image/png', sizes=favsize + 'x' + favsize, href='/favicons/favicon-' + favsize + 'x' + favsize + '.png')
// JS / CSS
script(type='text/javascript').
var appconfig = !{JSON.stringify(conf)};
var runmode = !{JSON.stringify(runmode)};
script(type='text/javascript', src='/js/vendor.js')
script(type='text/javascript', src='/js/configure.js')
block body
body body
#root #app.config-manager
#header-container config-manager(inline-template)
nav.nav#header
.nav-left
a.nav-item
h1
i.icon-layers
| Wiki.js
main
.container .container
transition(name='tst-welcome') transition(name='tst-welcome')
.welcome(v-if='state === "welcome" || state === "restart"') .welcome(v-if='state === "welcome" || state === "restart"')
...@@ -44,10 +22,22 @@ html(data-logic='configure') ...@@ -44,10 +22,22 @@ html(data-logic='configure')
i(v-if='loading') i(v-if='loading')
.panel-content.is-text .panel-content.is-text
p This installation wizard will guide you through the steps needed to get your wiki up and running in no time! p This installation wizard will guide you through the steps needed to get your wiki up and running in no time!
p Detailed information about installation and usage can be found on the #[a(href='https://docs.wiki.requarks.io/') official documentation site]. #[br] Should you have any question or would like to report something that doesn't look right, feel free to create a new issue on the #[a(href='https://github.com/Requarks/wiki/issues') GitHub project]. p Detailed information about installation and usage can be found on the #[a(href='https://wiki.requarks.io/docs') official documentation site]. #[br] Should you have any question or would like to report something that doesn't look right, feel free to create a new issue on the #[a(href='https://github.com/Requarks/wiki/issues') GitHub project].
.panel-content.form-sections
section
p #[i.nc-icon-outline.tech_cd-reader] You are about to install Wiki.js #[strong= packageObj.version].
section
p.control.is-fullwidth
input#ipt-telemetry(type='checkbox', v-model='conf.telemetry', name='ipt-telemetry')
label.label(for='ipt-telemetry') Enable telemetry
span.desc Help Wiki.js developers improve this app with anonymized #[a(href='https://wiki.requarks.io/docs/telemetry') telemetry].
p.control.is-fullwidth
input#ipt-upgrade(type='checkbox', v-model='conf.upgrade', name='ipt-upgrade')
label.label(for='ipt-upgrade') Upgrade from Wiki.js 1.x
span.desc Check this box if you are upgrading from Wiki.js 1.x and wish to migrate your existing data.
.panel-footer .panel-footer
.progress-bar: div(v-bind:style='{width: currentProgress}') .progress-bar: div(v-bind:style='{width: currentProgress}')
button.button.is-light-blue(v-on:click='proceedToSyscheck', v-bind:disabled='loading') Start button.button.is-small.is-light-blue(v-on:click='proceedToSyscheck', v-bind:disabled='loading') Start
//- ============================================== //- ==============================================
//- SYSTEM CHECK //- SYSTEM CHECK
...@@ -69,9 +59,10 @@ html(data-logic='configure') ...@@ -69,9 +59,10 @@ html(data-logic='configure')
p(v-if='!loading && !syscheck.ok') #[i.icon-square-cross] Error: {{ syscheck.error }} p(v-if='!loading && !syscheck.ok') #[i.icon-square-cross] Error: {{ syscheck.error }}
.panel-footer .panel-footer
.progress-bar: div(v-bind:style='{width: currentProgress}') .progress-bar: div(v-bind:style='{width: currentProgress}')
button.button.is-light-blue.is-outlined(v-on:click='proceedToWelcome', v-bind:disabled='loading') Back button.button.is-small.is-light-blue.is-outlined(v-on:click='proceedToWelcome', v-bind:disabled='loading') Back
button.button.is-teal(v-on:click='proceedToSyscheck', v-if='!loading && !syscheck.ok') Check Again button.button.is-small.is-teal(v-on:click='proceedToSyscheck', v-if='!loading && !syscheck.ok') Check Again
button.button.is-light-blue(v-on:click='proceedToGeneral', v-if='loading || syscheck.ok', v-bind:disabled='loading') Continue button.button.is-small.is-red.is-outlined(v-on:click='proceedToGeneral', v-if='!loading && !syscheck.ok') Continue Anyway
button.button.is-small.is-light-blue(v-on:click='proceedToGeneral', v-if='loading || syscheck.ok', v-bind:disabled='loading') Continue
//- ============================================== //- ==============================================
//- GENERAL //- GENERAL
...@@ -92,29 +83,33 @@ html(data-logic='configure') ...@@ -92,29 +83,33 @@ html(data-logic='configure')
p.control.is-fullwidth p.control.is-fullwidth
label.label Host label.label Host
input(type='text', placeholder='http://', v-model='conf.host', data-vv-scope='general', name='ipt-host', v-validate='{ required: true, min: 4 }') input(type='text', placeholder='http://', v-model='conf.host', data-vv-scope='general', name='ipt-host', v-validate='{ required: true, min: 4 }')
span.desc The full URL to your wiki, without the trailing slash. E.g.: http://wiki.domain.com. Note that sub-folders are #[u not supported]. span.desc The full URL to your wiki, without the trailing slash, e.g.: http://wiki.domain.com. Make sure to include the port if different than 80/443.
if !runmode.staticPort section
section p.control
p.control label.label Port
label.label Port input(type='text', placeholder='e.g. 80', v-model.number='conf.port', data-vv-scope='general', name='ipt-port', v-validate='{ required: true }')
input(type='text', placeholder='e.g. 80', v-model.number='conf.port', data-vv-scope='general', name='ipt-port', v-validate='{ required: true }') span.desc The port on which Wiki.js will listen to. Usually port 80 if connecting directly, or a random port (e.g. 3000) if using a web server in front of it.<br>Set <strong>$(PORT)</strong> to use PORT environment variable.
span.desc The port on which Wiki.js will listen to. Usually port 80 if connecting directly, or a random port (e.g. 3000) if using a web server in front of it.<br>Set <strong>$(PORT)</strong> to use PORT environment variable.
section section
p.control p.control
label.label Site UI Language label.label Site UI Language
select(v-model='conf.lang') select(v-model='conf.lang')
each lg in langs each lg in data.langs
option(value=lg.id)= lg.name option(value=lg.id)= lg.name
span.desc The language in which navigation, help and other UI elements will be displayed. span.desc The language in which navigation, help and other UI elements will be displayed.
section section
p.control.is-fullwidth p.control.is-fullwidth
label.label Local Repository Path
input(type='text', placeholder='e.g. ./repo', v-model='conf.pathRepo', data-vv-scope='general', name='ipt-repopath', v-validate='{ required: true, min: 2 }')
span.desc The path where the local git repository will be created, used to store content in markdown files and uploads.#[br] #[strong It is recommended to leave the default value].
section
p.control.is-fullwidth
input#ipt-public(type='checkbox', v-model='conf.public', data-vv-scope='general', name='ipt-public') input#ipt-public(type='checkbox', v-model='conf.public', data-vv-scope='general', name='ipt-public')
label.label(for='ipt-public') Public Access label.label(for='ipt-public') Public Access
span.desc Should the site be accessible (read only) without login. span.desc Should the site be accessible (read only) without login.
.panel-footer .panel-footer
.progress-bar: div(v-bind:style='{width: currentProgress}') .progress-bar: div(v-bind:style='{width: currentProgress}')
button.button.is-light-blue.is-outlined(v-on:click='proceedToSyscheck', v-bind:disabled='loading') Back button.button.is-small.is-light-blue.is-outlined(v-on:click='proceedToSyscheck', v-bind:disabled='loading') Back
button.button.is-light-blue(v-on:click='proceedToConsiderations', v-bind:disabled='loading || errors.any("general")') Continue button.button.is-small.is-light-blue(v-on:click='proceedToConsiderations', v-bind:disabled='loading || errors.any("general")') Continue
//- ============================================== //- ==============================================
//- CONSIDERATIONS //- CONSIDERATIONS
...@@ -134,88 +129,18 @@ html(data-logic='configure') ...@@ -134,88 +129,18 @@ html(data-logic='configure')
li - Do not rewrite URLs after the domain. This can cause unexpected issues in Wiki.js navigation. li - Do not rewrite URLs after the domain. This can cause unexpected issues in Wiki.js navigation.
li - Do not remove or alter the client IP when proxying the requests. This can cause the authentication brute force protection to engage unexpectedly. li - Do not remove or alter the client IP when proxying the requests. This can cause the authentication brute force protection to engage unexpectedly.
template(v-if='considerations.https') template(v-if='considerations.https')
h3 The site will not be using HTTPS? #[i.icon-warning-outline.animated.fadeOut.infinite] h3 The site will not be using HTTPS? #[i.nc-icon-outline.ui-3_alert.animated.fadeOut.infinite]
p The host URL you specified is not HTTPS. It is highly recommended to use HTTPS. You must use a web server / proxy (e.g. nginx / apache / IIS) in front of Wiki.js to use HTTPS. Wiki.js does not provide HTTPS handling by itself. p The host URL you specified is not HTTPS. It is highly recommended to use HTTPS. You must use a web server / proxy (e.g. nginx / apache / IIS) in front of Wiki.js to use HTTPS. Wiki.js does not provide HTTPS handling by itself.
template(v-if='considerations.port') template(v-if='considerations.port')
h3 You are using a non-standard port. h3 You are using a non-standard port.
p If you are not planning on using a web server / proxy in front of Wiki.js, be aware that users will need to specify the port when accessing the wiki. Make sure this is the intended behavior. Otherwise set a standard HTTP port such as 80. p If you are not planning on using a web server / proxy in front of Wiki.js, be aware that users will need to specify the port when accessing the wiki. Make sure this is the intended behavior. Otherwise set a standard HTTP port such as 80.
template(v-if='considerations.localhost') template(v-if='considerations.localhost')
h3 Are you sure you want to use localhost as the host base URL? #[i.icon-warning-outline.animated.fadeOut.infinite] h3 Are you sure you want to use localhost as the host base URL? #[i.nc-icon-outline.ui-3_alert.animated.fadeOut.infinite]
p The host URL you specified is localhost. Unless you are a developer running Wiki.js locally on your machine, this is not recommended! p The host URL you specified is localhost. Unless you are a developer running Wiki.js locally on your machine, this is not recommended!
.panel-footer .panel-footer
.progress-bar: div(v-bind:style='{width: currentProgress}') .progress-bar: div(v-bind:style='{width: currentProgress}')
button.button.is-light-blue.is-outlined(v-on:click='proceedToGeneral', v-bind:disabled='loading') Back button.button.is-small.is-light-blue.is-outlined(v-on:click='proceedToGeneral', v-bind:disabled='loading') Back
button.button.is-light-blue(v-on:click='proceedToDb', v-bind:disabled='loading') Continue button.button.is-small.is-light-blue(v-on:click='proceedToGit', v-bind:disabled='loading') Continue
//- ==============================================
//- DATABASE
//- ==============================================
template(v-else-if='state === "db"')
.panel
h2.panel-title.is-featured
span Database
i(v-if='loading')
.panel-content.is-text
p Wiki.js stores administrative data such as users, permissions and assets metadata in a MongoDB database. Article contents and uploads are <u>not</u> stored in the DB. Instead, they are stored on-disk and synced automatically with a remote git repository of your choice.
.panel-content.form-sections
section
p.control.is-fullwidth
label.label MongoDB Connection String
input(type='text', placeholder='e.g. mongodb://localhost:27017/wiki', v-model='conf.db', data-vv-scope='db', name='ipt-db', v-validate='{ required: true, min: 3 }')
span.desc The connection string to your MongoDB server. Leave the default localhost value if MongoDB is installed on the same server.<br />You can also specify an environment variable as the connection string, e.g. $(MONGO_URI).
.panel-footer
.progress-bar: div(v-bind:style='{width: currentProgress}')
button.button.is-light-blue.is-outlined(v-on:click='proceedToConsiderations', v-bind:disabled='loading') Back
button.button.is-light-blue(v-on:click='proceedToDbcheck', v-bind:disabled='loading || errors.any("db")') Connect
//- ==============================================
//- DATABASE CHECK
//- ==============================================
template(v-else-if='state === "dbcheck"')
.panel
h2.panel-title.is-featured
span Database Check
i(v-if='loading')
.panel-content.is-text
p(v-if='loading') #[i.icon-loader.animated.rotateIn.infinite] Testing the connection to MongoDB...
p(v-if='!loading && dbcheck.ok')
i.icon-check
strong Connected successfully!
p(v-if='!loading && !dbcheck.ok') #[i.icon-square-cross] Error: {{ dbcheck.error }}
.panel-footer
.progress-bar: div(v-bind:style='{width: currentProgress}')
button.button.is-light-blue.is-outlined(v-on:click='proceedToDb', v-bind:disabled='loading') Back
button.button.is-teal(v-on:click='proceedToDbcheck', v-if='!loading && !dbcheck.ok') Try Again
button.button.is-light-blue(v-on:click='proceedToPaths', v-if='loading || dbcheck.ok', v-bind:disabled='loading') Continue
//- ==============================================
//- PATHS
//- ==============================================
template(v-else-if='state === "paths"')
.panel
h2.panel-title.is-featured
span Paths
i(v-if='loading')
.panel-content.is-text
p It is recommended to leave the default values.
.panel-content.form-sections
section
p.control.is-fullwidth
label.label Local Data Path
input(type='text', placeholder='e.g. ./data', v-model='conf.pathData', data-vv-scope='paths', name='ipt-datapath', v-validate='{ required: true, min: 2 }')
span.desc The path where cache (processed content, thumbnails, search index, etc.) will be stored on disk.
section
p.control.is-fullwidth
label.label Local Repository Path
input(type='text', placeholder='e.g. ./repo', v-model='conf.pathRepo', data-vv-scope='paths', name='ipt-repopath', v-validate='{ required: true, min: 2 }')
span.desc The path where the local git repository will be created, used to store content in markdown files and uploads.
.panel-footer
.progress-bar: div(v-bind:style='{width: currentProgress}')
button.button.is-light-blue.is-outlined(v-on:click='proceedToDb', v-bind:disabled='loading') Back
button.button.is-light-blue(v-on:click='proceedToGit', v-bind:disabled='loading || errors.any("paths")') Continue
//- ============================================== //- ==============================================
//- GIT //- GIT
...@@ -279,9 +204,9 @@ html(data-logic='configure') ...@@ -279,9 +204,9 @@ html(data-logic='configure')
span.desc The default/fallback email to use when creating commits to the git repository. span.desc The default/fallback email to use when creating commits to the git repository.
.panel-footer .panel-footer
.progress-bar: div(v-bind:style='{width: currentProgress}') .progress-bar: div(v-bind:style='{width: currentProgress}')
button.button.is-light-blue.is-outlined(v-on:click='proceedToPaths', v-bind:disabled='loading') Back button.button.is-small.is-light-blue.is-outlined(v-on:click='proceedToGeneral', v-bind:disabled='loading') Back
button.button.is-light-blue.is-outlined(v-on:click='conf.gitUseRemote = false; proceedToGitCheck()', v-bind:disabled='loading') Skip this step button.button.is-small.is-light-blue.is-outlined(v-on:click='conf.gitUseRemote = false; proceedToGitCheck()', v-bind:disabled='loading') Skip this step
button.button.is-light-blue(v-on:click='conf.gitUseRemote = true; proceedToGitCheck()', v-bind:disabled='loading || errors.any("git")') Continue button.button.is-small.is-light-blue(v-on:click='conf.gitUseRemote = true; proceedToGitCheck()', v-bind:disabled='loading || errors.any("git")') Continue
//- ============================================== //- ==============================================
//- GIT CHECK //- GIT CHECK
...@@ -303,9 +228,9 @@ html(data-logic='configure') ...@@ -303,9 +228,9 @@ html(data-logic='configure')
p(v-if='!loading && !gitcheck.ok') #[i.icon-square-cross] Error: {{ gitcheck.error }} p(v-if='!loading && !gitcheck.ok') #[i.icon-square-cross] Error: {{ gitcheck.error }}
.panel-footer .panel-footer
.progress-bar: div(v-bind:style='{width: currentProgress}') .progress-bar: div(v-bind:style='{width: currentProgress}')
button.button.is-light-blue.is-outlined(v-on:click='proceedToGit', v-bind:disabled='loading') Back button.button.is-small.is-light-blue.is-outlined(v-on:click='proceedToGit', v-bind:disabled='loading') Back
button.button.is-teal(v-on:click='proceedToGitCheck', v-if='!loading && !gitcheck.ok') Try Again button.button.is-small.is-teal(v-on:click='proceedToGitCheck', v-if='!loading && !gitcheck.ok') Try Again
button.button.is-light-blue(v-on:click='proceedToAdmin', v-if='loading || gitcheck.ok', v-bind:disabled='loading') Continue button.button.is-small.is-light-blue(v-on:click='proceedToAdmin', v-if='loading || gitcheck.ok', v-bind:disabled='loading') Continue
//- ============================================== //- ==============================================
//- ADMINISTRATOR ACCOUNT //- ADMINISTRATOR ACCOUNT
...@@ -337,8 +262,8 @@ html(data-logic='configure') ...@@ -337,8 +262,8 @@ html(data-logic='configure')
span.desc Verify your password again. span.desc Verify your password again.
.panel-footer .panel-footer
.progress-bar: div(v-bind:style='{width: currentProgress}') .progress-bar: div(v-bind:style='{width: currentProgress}')
button.button.is-light-blue.is-outlined(v-on:click='proceedToGit', v-bind:disabled='loading') Back button.button.is-small.is-light-blue.is-outlined(v-on:click='proceedToGit', v-bind:disabled='loading') Back
button.button.is-light-blue(v-on:click='proceedToFinal', v-bind:disabled='loading || errors.any("admin")') Continue button.button.is-small.is-light-blue(v-on:click='proceedToFinal', v-bind:disabled='loading || errors.any("admin")') Continue
//- ============================================== //- ==============================================
//- FINAL //- FINAL
...@@ -359,9 +284,9 @@ html(data-logic='configure') ...@@ -359,9 +284,9 @@ html(data-logic='configure')
p(v-if='!loading && !final.ok') #[i.icon-square-cross] Error: {{ final.error }} p(v-if='!loading && !final.ok') #[i.icon-square-cross] Error: {{ final.error }}
.panel-footer .panel-footer
.progress-bar: div(v-bind:style='{width: currentProgress}') .progress-bar: div(v-bind:style='{width: currentProgress}')
button.button.is-light-blue.is-outlined(v-on:click='proceedToAdmin', v-bind:disabled='loading') Back button.button.is-small.is-light-blue.is-outlined(v-on:click='proceedToAdmin', v-bind:disabled='loading') Back
button.button.is-teal(v-on:click='proceedToFinal', v-if='!loading && !final.ok') Try Again button.button.is-small.is-teal(v-on:click='proceedToFinal', v-if='!loading && !final.ok') Try Again
button.button.is-green(v-on:click='finish', v-if='loading || final.ok', v-bind:disabled='loading') Start button.button.is-small.is-green(v-on:click='finish', v-if='loading || final.ok', v-bind:disabled='loading') Start
//- ============================================== //- ==============================================
//- RESTART //- RESTART
...@@ -376,11 +301,4 @@ html(data-logic='configure') ...@@ -376,11 +301,4 @@ html(data-logic='configure')
p #[i.icon-loader.animated.rotateIn.infinite] Restarting Wiki.js in normal mode... p #[i.icon-loader.animated.rotateIn.infinite] Restarting Wiki.js in normal mode...
p You'll automatically be redirected to the homepage when ready. This usually takes about 30 seconds. p You'll automatically be redirected to the homepage when ready. This usually takes about 30 seconds.
.panel-footer .panel-footer
button.button.is-green(disabled='disabled') Start button.button.is-small.is-green(disabled='disabled') Start
footer.footer
span
| Powered by
a(href='https://github.com/Requarks/wiki') Wiki.js
| .
block outside
doctype html extends ./master.pug
html(data-logic='error')
head
meta(http-equiv='X-UA-Compatible', content='IE=edge')
meta(charset='UTF-8')
meta(name='viewport', content='width=device-width, initial-scale=1')
meta(name='theme-color', content='#009688')
meta(name='msapplication-TileColor', content='#009688')
meta(name='msapplication-TileImage', content=appconfig.host + '/favicons/ms-icon-144x144.png')
title= appconfig.title
// Favicon
each favsize in [57, 60, 72, 76, 114, 120, 144, 152, 180]
link(rel='apple-touch-icon', sizes=favsize + 'x' + favsize, href=appconfig.host + '/favicons/apple-icon-' + favsize + 'x' + favsize + '.png')
link(rel='icon', type='image/png', sizes='192x192', href=appconfig.host + '/favicons/android-icon-192x192.png')
each favsize in [32, 96, 16]
link(rel='icon', type='image/png', sizes=favsize + 'x' + favsize, href=appconfig.host + '/favicons/favicon-' + favsize + 'x' + favsize + '.png')
link(rel='manifest', href=appconfig.host + '/manifest.json')
// JS / CSS
script(type='text/javascript', src=appconfig.host + '/js/vendor.js')
script(type='text/javascript', src=appconfig.host + '/js/app.js')
block body
body(class='is-error') body(class='is-error')
.container .container
a(href='/'): img(src=appconfig.host + '/images/logo.png') a(href='/'): img(src=config.site.path + '/images/logo.png')
h1= message h1= message
h2= t('errors:generic') h2= t('errors:generic')
a.button.is-amber.is-inverted.is-featured(href=appconfig.host + '/')= t('errors:actions.gohome') a.button.is-amber.is-inverted.is-featured(href=config.site.path+ '/')= t('errors:actions.gohome')
if error.stack if error.stack
h3= t('errors:debugmsg') h3= t('errors:debugmsg')
......
doctype html extends ./master.pug
html
head
meta(http-equiv='X-UA-Compatible', content='IE=edge')
meta(charset='UTF-8')
meta(name='viewport', content='width=device-width, initial-scale=1')
meta(name='theme-color', content='#009688')
meta(name='msapplication-TileColor', content='#009688')
meta(name='msapplication-TileImage', content=appconfig.host + '/favicons/ms-icon-144x144.png')
title= appconfig.title
//- Favicon block body
each favsize in [57, 60, 72, 76, 114, 120, 144, 152, 180] body
link(rel='apple-touch-icon', sizes=favsize + 'x' + favsize, href=appconfig.host + '/favicons/apple-icon-' + favsize + 'x' + favsize + '.png') #app.has-stickynav(class=['is-primary-' + appconfig.theme.primary, 'is-alternate-' + appconfig.theme.alt])
link(rel='icon', type='image/png', sizes='192x192', href=appconfig.host + '/favicons/android-icon-192x192.png')
each favsize in [32, 96, 16]
link(rel='icon', type='image/png', sizes=favsize + 'x' + favsize, href=appconfig.host + '/favicons/favicon-' + favsize + 'x' + favsize + '.png')
link(rel='manifest', href=appconfig.host + '/manifest.json')
//- Site Lang
script.
var siteLang = '!{appconfig.lang}';
var siteRoot = '!{appconfig.host}';
//- JS / CSS
script(type='text/javascript', src=appconfig.host + '/js/vendor.js')
script(type='text/javascript', src=appconfig.host + '/js/app.js')
block head
body(class={ 'rtl': appconfig.langRtl })
#root.has-stickynav(class=['is-primary-' + appconfig.theme.primary, 'is-alternate-' + appconfig.theme.alt])
include ./common/header.pug include ./common/header.pug
alert alert
main main
......
doctype html
html
head
meta(http-equiv='X-UA-Compatible', content='IE=edge')
meta(charset='UTF-8')
meta(name='viewport', content='width=device-width, initial-scale=1')
meta(name='theme-color', content='#009688')
meta(name='msapplication-TileColor', content='#009688')
meta(name='msapplication-TileImage', content=config.site.path + '/favicons/ms-icon-144x144.png')
title= config.site.title
//- Favicon
each favsize in [57, 60, 72, 76, 114, 120, 144, 152, 180]
link(rel='apple-touch-icon', sizes=favsize + 'x' + favsize, href=config.site.path + '/favicons/apple-icon-' + favsize + 'x' + favsize + '.png')
link(rel='icon', type='image/png', sizes='192x192', href=config.site.path + '/favicons/android-icon-192x192.png')
each favsize in [32, 96, 16]
link(rel='icon', type='image/png', sizes=favsize + 'x' + favsize, href=config.site.path + '/favicons/favicon-' + favsize + 'x' + favsize + '.png')
link(rel='manifest', href=config.site.path + '/manifest.json')
//- Site Lang
script.
var siteConfig = !{JSON.stringify(config.site)}
//- JS / CSS
script(type='text/javascript', src=config.site.path + '/js/libs.js')
script(type='text/javascript', src=config.site.path + '/js/app.js')
block head
block body
/* global wiki */
const Promise = require('bluebird')
module.exports = Promise.join(
wiki.db.onReady,
wiki.configSvc.loadFromDb(['features', 'git', 'logging', 'site', 'uploads'])
).then(() => {
const path = require('path')
wiki.REPOPATH = path.resolve(wiki.ROOTPATH, wiki.config.paths.repo)
wiki.DATAPATH = path.resolve(wiki.ROOTPATH, wiki.config.paths.data)
wiki.UPLTEMPPATH = path.join(wiki.DATAPATH, 'temp-upload')
// ----------------------------------------
// Load global modules
// ----------------------------------------
// wiki.upl = require('./modules/uploads-agent').init()
// wiki.git = require('./modules/git').init()
// wiki.entries = require('./modules/entries').init()
wiki.lang = require('i18next')
wiki.mark = require('./modules/markdown')
// ----------------------------------------
// Localization Engine
// ----------------------------------------
const i18nBackend = require('i18next-node-fs-backend')
wiki.lang.use(i18nBackend).init({
load: 'languageOnly',
ns: ['common', 'admin', 'auth', 'errors', 'git'],
defaultNS: 'common',
saveMissing: false,
preload: [wiki.config.lang],
lng: wiki.config.lang,
fallbackLng: 'en',
backend: {
loadPath: path.join(wiki.SERVERPATH, 'locales/{{lng}}/{{ns}}.json')
}
})
// ----------------------------------------
// Start Queues
// ----------------------------------------
const Bull = require('bull')
const autoload = require('auto-load')
let queues = autoload(path.join(wiki.SERVERPATH, 'queues'))
for (let queueName in queues) {
new Bull(queueName, {
prefix: `q-${wiki.config.ha.nodeuid}`,
redis: wiki.config.redis
}).process(queues[queueName])
}
// ----------------------------------------
// Shutdown gracefully
// ----------------------------------------
process.on('disconnect', () => {
wiki.logger.warn('Lost connection to Master. Exiting...')
process.exit()
})
})
name: Default
author: Nicolas Giard
site: https://wiki.requarks.io/
version: 1.0.0
requirements:
minimum: '>= 2.0.0'
maximum: '< 3.0.0'
fields:
primary:
title: Primary Color
description: Used for top navigation bar, headers, links, etc.
type: color
default: indigo
alt:
title: Alternate Color
description: Used for the sidebar (in a darker tone)
type: color
default: blue-grey
codeDark:
title: Code Blocks - Use Dark Theme
description: todo
type: boolean
default: true
codeColorize:
title: Code Blocks - Colorize syntax
description: todo
type: boolean
default: true
\ No newline at end of file
This diff was suppressed by a .gitattributes entry.
...@@ -6,16 +6,15 @@ ...@@ -6,16 +6,15 @@
* Client & Server compiler / bundler / watcher * Client & Server compiler / bundler / watcher
*/ */
const autoprefixer = require('autoprefixer')
const colors = require('colors/safe') const colors = require('colors/safe')
const fsbx = require('fuse-box') const fsbx = require('fuse-box')
const nodemon = require('nodemon') const nodemon = require('nodemon')
const babel = require('babel-core')
const uglify = require('uglify-es')
const fs = require('fs-extra') const fs = require('fs-extra')
// ====================================================== // -------------------------------------------------------
// Parse cmd arguments // Parse cmd arguments
// ====================================================== // -------------------------------------------------------
const args = require('yargs') const args = require('yargs')
.option('d', { .option('d', {
...@@ -23,30 +22,21 @@ const args = require('yargs') ...@@ -23,30 +22,21 @@ const args = require('yargs')
describe: 'Start in Developer mode', describe: 'Start in Developer mode',
type: 'boolean' type: 'boolean'
}) })
.option('c', {
alias: 'dev-configure',
describe: 'Start in Configure Developer mode',
type: 'boolean'
})
.help('h') .help('h')
.alias('h', 'help') .alias('h', 'help')
.argv .argv
let mode = 'build' const dev = args.dev
const dev = args.d || args.c
if (args.d) { if (dev) {
console.info(colors.bgWhite.black(' Starting Fuse in DEVELOPER mode... ')) console.info(colors.bgWhite.black(' Starting Fuse in DEVELOPER mode... '))
mode = 'dev'
} else if (args.c) {
console.info(colors.bgWhite.black(' Starting Fuse in CONFIGURE DEVELOPER mode... '))
mode = 'dev-configure'
} else { } else {
console.info(colors.bgWhite.black(' Starting Fuse in BUILD mode... ')) console.info(colors.bgWhite.black(' Starting Fuse in BUILD mode... '))
} }
// ====================================================== // -------------------------------------------------------
// BUILD VARS // BUILD VARS
// ====================================================== // -------------------------------------------------------
const ALIASES = { const ALIASES = {
'brace-ext-modelist': 'brace/ext/modelist.js', 'brace-ext-modelist': 'brace/ext/modelist.js',
...@@ -71,16 +61,31 @@ const SHIMS = { ...@@ -71,16 +61,31 @@ const SHIMS = {
} }
} }
// ====================================================== // -------------------------------------------------------
// Global Tasks // Global Tasks
// ====================================================== // -------------------------------------------------------
console.info(colors.white('└── ') + colors.green('Running global tasks...')) console.info(colors.white('└── ') + colors.green('Running global tasks...'))
let globalTasks = require('./fuse_tasks') let globalTasks = require('./fuse_tasks')
// ====================================================== // -------------------------------------------------------
// Fuse Tasks // FUSEBOX PRODUCER
// ====================================================== // -------------------------------------------------------
const babelrc = fs.readJsonSync('.babelrc')
const scssChain = [
fsbx.SassPlugin({
includePaths: ['node_modules'],
outputStyle: dev ? 'nested' : 'compressed'
}),
fsbx.PostCSS([
autoprefixer({
remove: false,
browsers: babelrc.presets[0][1].targets.browsers
})
]),
fsbx.CSSPlugin()
]
globalTasks.then(() => { globalTasks.then(() => {
let fuse = fsbx.FuseBox.init({ let fuse = fsbx.FuseBox.init({
...@@ -91,100 +96,60 @@ globalTasks.then(() => { ...@@ -91,100 +96,60 @@ globalTasks.then(() => {
tsConfig: './tsconfig.json', tsConfig: './tsconfig.json',
plugins: [ plugins: [
fsbx.EnvPlugin({ NODE_ENV: (dev) ? 'development' : 'production' }), fsbx.EnvPlugin({ NODE_ENV: (dev) ? 'development' : 'production' }),
fsbx.VuePlugin(), fsbx.VueComponentPlugin({
['.scss', fsbx.SassPlugin({ outputStyle: (dev) ? 'nested' : 'compressed' }), fsbx.CSSPlugin()], script: fsbx.BabelPlugin(babelrc),
fsbx.BabelPlugin({ comments: false, presets: ['es2015'] }), template: fsbx.ConsolidatePlugin({
engine: 'pug'
}),
style: scssChain
}),
scssChain,
fsbx.BabelPlugin(babelrc),
fsbx.JSONPlugin() fsbx.JSONPlugin()
/* !dev && fsbx.QuantumPlugin({
target: 'browser',
uglify: true,
api: (core) => {
core.solveComputed('default/js/components/editor-codeblock.vue', {
mapping: '/js/ace/ace.js',
fn: (statement, core) => {
statement.setExpression(`'/js/ace/ace.js'`)
}
})
core.solveComputed('default/js/components/editor.component.js', {
mapping: '/js/simplemde/simplemde.min.js',
fn: (statement, core) => {
statement.setExpression(`'/js/simplemde/simplemde.min.js'`)
}
})
}
}) */
// !dev && fsbx.UglifyESPlugin()
], ],
debug: false, debug: false,
log: true log: true
}) })
const bundleVendor = fuse.bundle('vendor').shim(SHIMS).instructions('~ index.js') // eslint-disable-line no-unused-vars // -------------------------------------------------------
const bundleApp = fuse.bundle('app').instructions('!> [index.js]') // FUSEBOX DEV
// const bundleApp = fuse.bundle('app').shim(SHIMS).instructions('> index.js') // -------------------------------------------------------
const bundleSetup = fuse.bundle('configure').instructions('> configure.js')
if (dev) {
switch (mode) { fuse.dev({
case 'dev': port: 5555,
bundleApp.watch() httpServer: false
break })
case 'dev-configure':
bundleSetup.watch()
break
} }
// -------------------------------------------------------
// FUSEBOX BUNDLES
// -------------------------------------------------------
if (dev) {
fuse.bundle('libs').shim(SHIMS).instructions('~ index.js')
fuse.bundle('app').instructions('!> [index.js]').hmr({ reload: true }).watch()
} else {
fuse.bundle('bundle.min.js').shim(SHIMS).instructions('> index.js')
}
// -------------------------------------------------------
// FUSEBOX RUN
// -------------------------------------------------------
fuse.run().then(() => { fuse.run().then(() => {
console.info(colors.green.bold('\nAssets compilation + bundling completed.')) console.info(colors.green.bold('\nAssets compilation + bundling completed.'))
if (dev) { if (dev) {
nodemon({ nodemon({
exec: (args.d) ? 'node server' : 'node wiki configure', exec: 'node server',
ignore: ['assets/', 'client/', 'data/', 'repo/', 'tests/'], ignore: ['assets/', 'client/', 'data/', 'repo/', 'tests/', 'tools/'],
ext: 'js json', ext: 'js json graphql',
watch: (args.d) ? ['server'] : ['server/configure.js'], watch: ['server'],
env: { 'NODE_ENV': 'development' } env: { 'NODE_ENV': 'development' }
}) })
} else {
console.info(colors.yellow.bold('\nTranspiling vendor bundle...'))
let appCode = babel.transform(fs.readFileSync('./assets/js/app.js', 'utf8'), {
babelrc: false,
compact: false,
filename: 'app.js',
plugins: ['transform-object-assign']
}).code
let vendorCode = babel.transform(fs.readFileSync('./assets/js/vendor.js', 'utf8'), {
babelrc: false,
comments: false,
compact: false,
filename: 'vendor.js',
plugins: [
'transform-es2015-arrow-functions',
'transform-es2015-block-scoped-functions',
'transform-es2015-block-scoping',
'transform-es2015-classes',
'transform-es2015-computed-properties',
'transform-es2015-destructuring',
'transform-es2015-duplicate-keys',
'transform-es2015-for-of',
'transform-es2015-function-name',
'transform-es2015-literals',
'transform-es2015-object-super',
'transform-es2015-parameters',
'transform-es2015-shorthand-properties',
'transform-es2015-spread',
'transform-es2015-sticky-regex',
'transform-es2015-template-literals',
'transform-es2015-typeof-symbol',
'transform-es2015-unicode-regex'
]
}).code
console.info(colors.yellow.bold('Minifing bundles...'))
fs.writeFileSync('./assets/js/vendor.js', uglify.minify(vendorCode).code, 'utf8')
fs.writeFileSync('./assets/js/app.js', uglify.minify(appCode).code, 'utf8')
fs.writeFileSync('./assets/js/configure.js', uglify.minify(fs.readFileSync('./assets/js/configure.js', 'utf8')).code, 'utf8')
console.info(colors.green.bold('\nBUILD SUCCEEDED.'))
return true
} }
return true
}).catch(err => { }).catch(err => {
console.error(colors.red(' X Bundle compilation failed! ' + err.message)) console.error(colors.red(' X Bundle compilation failed! ' + err.message))
process.exit(1) process.exit(1)
......
...@@ -66,27 +66,6 @@ module.exports = Promise.mapSeries([ ...@@ -66,27 +66,6 @@ module.exports = Promise.mapSeries([
}) })
}, },
/** /**
* i18n
*/
() => {
console.info(colors.white(' └── ') + colors.green('Copying i18n client files...'))
return fs.ensureDirAsync('./assets/js/i18n').then(() => {
return fs.readJsonAsync('./server/locales/en/browser.json').then(enContent => {
return fs.readdirAsync('./server/locales').then(langs => {
return Promise.map(langs, lang => {
console.info(colors.white(' ' + lang + '.json'))
let outputPath = path.join('./assets/js/i18n', lang + '.json')
return fs.readJsonAsync(path.join('./server/locales', lang, 'browser.json'), 'utf8').then((content) => {
return fs.outputJsonAsync(outputPath, _.defaultsDeep(content, enContent))
}).catch(err => { // eslint-disable-line handle-callback-err
return fs.outputJsonAsync(outputPath, enContent)
})
})
})
})
})
},
/**
* Delete Fusebox cache * Delete Fusebox cache
*/ */
() => { () => {
......
#!/usr/bin/env node #!/usr/bin/env node
'use strict'
// =========================================== // ===========================================
// Wiki.js // Wiki.js
// 1.0.0 // 2.0
// Licensed under AGPLv3 // Licensed under AGPLv3
// =========================================== // ===========================================
const init = require('./server/init') const Promise = require('bluebird')
const fs = Promise.promisifyAll(require('fs-extra'))
const pm2 = Promise.promisifyAll(require('pm2'))
const ora = require('ora')
const path = require('path')
const ROOTPATH = process.cwd()
const init = {
/**
* Start in background mode
*/
start () {
let spinner = ora('Initializing...').start()
return fs.emptyDirAsync(path.join(ROOTPATH, './logs')).then(() => {
return pm2.connectAsync().then(() => {
return pm2.startAsync({
name: 'wiki',
script: 'server',
cwd: ROOTPATH,
output: path.join(ROOTPATH, './logs/wiki-output.log'),
error: path.join(ROOTPATH, './logs/wiki-error.log'),
minUptime: 5000,
maxRestarts: 5
}).then(() => {
spinner.succeed('Wiki.js has started successfully.')
}).finally(() => {
pm2.disconnect()
})
})
}).catch(err => {
spinner.fail(err)
process.exit(1)
})
},
/**
* Stop Wiki.js process(es)
*/
stop () {
let spinner = ora('Shutting down Wiki.js...').start()
return pm2.connectAsync().then(() => {
return pm2.stopAsync('wiki').then(() => {
spinner.succeed('Wiki.js has stopped successfully.')
}).finally(() => {
pm2.disconnect()
})
}).catch(err => {
spinner.fail(err)
process.exit(1)
})
},
/**
* Restart Wiki.js process(es)
*/
restart: function () {
let self = this
return self.stop().delay(1000).then(() => {
self.startDetect()
})
}
}
require('yargs') // eslint-disable-line no-unused-expressions require('yargs') // eslint-disable-line no-unused-expressions
.usage('Usage: node $0 <cmd> [args]') .usage('Usage: node $0 <cmd> [args]')
...@@ -16,7 +75,7 @@ require('yargs') // eslint-disable-line no-unused-expressions ...@@ -16,7 +75,7 @@ require('yargs') // eslint-disable-line no-unused-expressions
alias: ['boot', 'init'], alias: ['boot', 'init'],
desc: 'Start Wiki.js process', desc: 'Start Wiki.js process',
handler: argv => { handler: argv => {
init.startDetect() init.start()
} }
}) })
.command({ .command({
...@@ -35,18 +94,9 @@ require('yargs') // eslint-disable-line no-unused-expressions ...@@ -35,18 +94,9 @@ require('yargs') // eslint-disable-line no-unused-expressions
init.restart() init.restart()
} }
}) })
.command({
command: 'configure [port]',
alias: ['config', 'conf', 'cfg', 'setup'],
desc: 'Configure Wiki.js using the web-based setup wizard',
builder: (yargs) => yargs.default('port', 3000),
handler: argv => {
init.configure(argv.port)
}
})
.recommendCommands() .recommendCommands()
.demandCommand(1, 'You must provide one of the accepted commands above.') .demandCommand(1, 'You must provide one of the accepted commands above.')
.help() .help()
.version() .version()
.epilogue('Read the docs at https://wiki.requarks.io') .epilogue('Read the docs at https://docs.requarks.io/wiki')
.argv .argv
This diff was suppressed by a .gitattributes entry.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment