@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
@ -1,27 +1,14 @@
|
|||||||
{
|
{
|
||||||
"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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
After Width: | Height: | Size: 297 KiB |
Before Width: | Height: | Size: 138 KiB |
Before Width: | Height: | Size: 144 KiB |
Before Width: | Height: | Size: 134 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 434 B |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1018 B |
After Width: | Height: | Size: 872 B |
After Width: | Height: | Size: 729 B |
After Width: | Height: | Size: 389 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
@ -1,4 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
require('./scss/configure.scss')
|
|
||||||
require('./js/configure.js')
|
|
@ -1,17 +1,4 @@
|
|||||||
'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
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,153 @@
|
|||||||
|
'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 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
@ -0,0 +1,239 @@
|
|||||||
|
/* 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
<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>
|
||||||
|
|
@ -0,0 +1,22 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
import GRAPHQL from './graphql'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
GRAPHQL
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
/* global $ */
|
|
||||||
|
|
||||||
$(() => {
|
|
||||||
$('#login-user').focus()
|
|
||||||
})
|
|
@ -0,0 +1,52 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
@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';
|
|
@ -1,306 +1,264 @@
|
|||||||
|
.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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
![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)
|
|
@ -1,25 +0,0 @@
|
|||||||
'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()
|
|
@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"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": {}
|
|
||||||
}
|
|
@ -1,222 +0,0 @@
|
|||||||
// ===========================================
|
|
||||||
// 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()
|
|
||||||
})
|
|
@ -0,0 +1,37 @@
|
|||||||
|
'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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
'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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
'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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
'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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
'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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
'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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
'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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
'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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
@ -1,276 +1,43 @@
|
|||||||
'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()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
'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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,261 +0,0 @@
|
|||||||
'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 }
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
'use strict'
|
|
||||||
|
|
||||||
const fs = require('fs')
|
|
||||||
const yaml = require('js-yaml')
|
|
||||||
const _ = require('lodash')
|
|
||||||
const path = require('path')
|
|
||||||
const cfgHelper = require('../helpers/config')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load Application Configuration
|
|
||||||
*
|
|
||||||
* @param {Object} confPaths Path to the configuration files
|
|
||||||
* @return {Object} Application Configuration
|
|
||||||
*/
|
|
||||||
module.exports = (confPaths) => {
|
|
||||||
confPaths = _.defaults(confPaths, {
|
|
||||||
config: path.join(ROOTPATH, 'config.yml'),
|
|
||||||
data: path.join(SERVERPATH, 'app/data.yml'),
|
|
||||||
dataRegex: path.join(SERVERPATH, 'app/regex.js')
|
|
||||||
})
|
|
||||||
|
|
||||||
let appconfig = {}
|
|
||||||
let appdata = {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
appconfig = yaml.safeLoad(
|
|
||||||
cfgHelper.parseConfigValue(
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge with defaults
|
|
||||||
|
|
||||||
appconfig = _.defaultsDeep(appconfig, appdata.defaults.config)
|
|
||||||
|
|
||||||
// Check port
|
|
||||||
|
|
||||||
if (appconfig.port < 1) {
|
|
||||||
appconfig.port = process.env.PORT || 80
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert booleans
|
|
||||||
|
|
||||||
appconfig.public = (appconfig.public === true || _.toLower(appconfig.public) === 'true')
|
|
||||||
|
|
||||||
// List authentication strategies
|
|
||||||
|
|
||||||
appconfig.authStrategies = {
|
|
||||||
list: _.filter(appconfig.auth, ['enabled', true]),
|
|
||||||
socialEnabled: (_.chain(appconfig.auth).omit(['local', 'ldap']).filter(['enabled', true]).value().length > 0)
|
|
||||||
}
|
|
||||||
if (appconfig.authStrategies.list.length < 1) {
|
|
||||||
console.error(new Error('You must enable at least 1 authentication strategy!'))
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
config: appconfig,
|
|
||||||
data: appdata
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
'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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
'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
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
'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
|
|
||||||
}
|
|
@ -0,0 +1,186 @@
|
|||||||
|
/* 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
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* 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' })
|
||||||
|
}
|
@ -1,20 +0,0 @@
|
|||||||
'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)
|
|
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Comment schema
|
||||||
|
*/
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
let commentSchema = sequelize.define('comment', {
|
||||||
|
content: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
timestamps: true,
|
||||||
|
version: true
|
||||||
|
})
|
||||||
|
|
||||||
|
return commentSchema
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
@ -1,42 +0,0 @@
|
|||||||
'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)
|
|
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Group schema
|
||||||
|
*/
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
let groupSchema = sequelize.define('group', {
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
timestamps: true,
|
||||||
|
version: true
|
||||||
|
})
|
||||||
|
|
||||||
|
return groupSchema
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
@ -1,22 +1,26 @@
|
|||||||
'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
|
||||||
|
}
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
@ -1,46 +0,0 @@
|
|||||||
'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)
|
|
@ -1,21 +0,0 @@
|
|||||||
'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)
|
|
@ -0,0 +1,106 @@
|
|||||||
|
/* 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
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
/* global wiki */
|
||||||
|
|
||||||
|
const fs = require('fs')
|
||||||
|
const yaml = require('js-yaml')
|
||||||
|
const _ = require('lodash')
|
||||||
|
const path = require('path')
|
||||||
|
const cfgHelper = require('../helpers/config')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* Load root config from disk
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
let confPaths = {
|
||||||
|
config: path.join(wiki.ROOTPATH, 'config.yml'),
|
||||||
|
data: path.join(wiki.SERVERPATH, 'app/data.yml'),
|
||||||
|
dataRegex: path.join(wiki.SERVERPATH, 'app/regex.js')
|
||||||
|
}
|
||||||
|
|
||||||
|
let appconfig = {}
|
||||||
|
let appdata = {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
appconfig = yaml.safeLoad(
|
||||||
|
cfgHelper.parseConfigValue(
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge with defaults
|
||||||
|
|
||||||
|
appconfig = _.defaultsDeep(appconfig, appdata.defaults.config)
|
||||||
|
|
||||||
|
// Check port
|
||||||
|
|
||||||
|
if (appconfig.port < 1) {
|
||||||
|
appconfig.port = process.env.PORT || 80
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert booleans
|
||||||
|
|
||||||
|
appconfig.public = (appconfig.public === true || _.toLower(appconfig.public) === 'true')
|
||||||
|
|
||||||
|
// List authentication strategies
|
||||||
|
wiki.config = appconfig
|
||||||
|
wiki.data = appdata
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load config from DB
|
||||||
|
*
|
||||||
|
* @param {Array} subsets Array of subsets to load
|
||||||
|
* @returns Promise
|
||||||
|
*/
|
||||||
|
loadFromDb(subsets) {
|
||||||
|
if (!_.isArray(subsets) || subsets.length === 0) {
|
||||||
|
subsets = wiki.data.configNamespaces
|
||||||
|
}
|
||||||
|
|
||||||
|
return wiki.db.Setting.findAll({
|
||||||
|
attributes: ['key', 'config'],
|
||||||
|
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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
'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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
'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
|
@ -0,0 +1,91 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
'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
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
'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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
'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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|