@ -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": {
|
||||
"node": true,
|
||||
"es6": true,
|
||||
"jest": true
|
||||
},
|
||||
"globals": {
|
||||
// Client
|
||||
"document": false,
|
||||
"navigator": false,
|
||||
"window": false,
|
||||
"siteLang": 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
|
||||
"FuseBox": false
|
||||
}
|
||||
}
|
||||
|
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'
|
||||
|
||||
let logic = document.documentElement.dataset.logic
|
||||
|
||||
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
|
||||
}
|
||||
require('./scss/app.scss')
|
||||
require('./js/app.js')
|
||||
|
@ -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 @@
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: $core-font-standard;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #FFF;
|
||||
transition: color 0.4s ease;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: mc('orange','600');
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#bg {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
background-color: #000;
|
||||
|
||||
> div {
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 3s ease, visibility 3s;
|
||||
animation: bg 30s linear infinite;
|
||||
|
||||
&:nth-child(1) {
|
||||
animation-delay: 10s;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
animation-delay: 20s;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#root {
|
||||
position: fixed;
|
||||
top: 15vh;
|
||||
left: 10vw;
|
||||
z-index: 2;
|
||||
color: #FFF;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
h1 {
|
||||
font-size: 4rem;
|
||||
font-weight: bold;
|
||||
color: #FFF;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
animation: headerIntro 3s ease;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: normal;
|
||||
color: rgba(255,255,255,0.7);
|
||||
padding: 0;
|
||||
margin: 0 0 25px 0;
|
||||
animation: headerIntro 3s ease;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: normal;
|
||||
color: #FB8C00;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
animation: shake 1s ease;
|
||||
|
||||
> .fa {
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 0.8rem;
|
||||
font-weight: normal;
|
||||
color: rgba(255,255,255,0.7);
|
||||
padding: 0;
|
||||
margin: 0 0 15px 0;
|
||||
animation: fadeIn 3s ease;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
input[type=text], input[type=password] {
|
||||
width: 350px;
|
||||
max-width: 80vw;
|
||||
border: 1px solid rgba(255,255,255,0.3);
|
||||
border-radius: 3px;
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
padding: 0 15px;
|
||||
height: 40px;
|
||||
margin: 0 0 10px 0;
|
||||
color: #FFF;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
transition: all 0.4s ease;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: mc('orange','600');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: mc('orange','600');
|
||||
color: #FFF;
|
||||
border: 1px solid lighten(mc('orange','600'), 10%);
|
||||
border-radius: 3px;
|
||||
height: 40px;
|
||||
width: 125px;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
margin: 15px 0 0 0;
|
||||
transition: all 0.4s ease;
|
||||
cursor: pointer;
|
||||
|
||||
span {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #FFF;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: darken(mc('orange','600'), 10%);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#social {
|
||||
margin-top: 25px;
|
||||
|
||||
> span {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
color: rgba(255,255,255,0.7);
|
||||
}
|
||||
|
||||
button {
|
||||
margin-right: 5px;
|
||||
width: auto;
|
||||
padding: 0 15px;
|
||||
|
||||
> i {
|
||||
margin-right: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
&.ms {
|
||||
background-color: mc('blue','600');
|
||||
border-color: lighten(mc('blue','600'), 10%);
|
||||
|
||||
&:focus {
|
||||
border-color: #FFF;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: darken(mc('blue','600'), 10%);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.google {
|
||||
background-color: mc('light-blue','600');
|
||||
border-color: lighten(mc('light-blue','600'), 10%);
|
||||
|
||||
&:focus {
|
||||
border-color: #FFF;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: darken(mc('light-blue','600'), 10%);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.facebook {
|
||||
background-color: mc('indigo','600');
|
||||
border-color: lighten(mc('indigo','600'), 10%);
|
||||
|
||||
&:focus {
|
||||
border-color: #FFF;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: darken(mc('indigo','600'), 10%);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.github {
|
||||
background-color: mc('blue-grey','700');
|
||||
border-color: lighten(mc('blue-grey','700'), 10%);
|
||||
|
||||
&:focus {
|
||||
border-color: #FFF;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: darken(mc('blue-grey','700'), 10%);
|
||||
}
|
||||
}
|
||||
|
||||
&.slack {
|
||||
background-color: mc('purple','700');
|
||||
border-color: lighten(mc('purple','700'), 10%);
|
||||
|
||||
&:focus {
|
||||
border-color: #FFF;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: darken(mc('purple','700'), 10%);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#copyright {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
position: absolute;
|
||||
left: 10vw;
|
||||
bottom: 10vh;
|
||||
z-index: 2;
|
||||
color: rgba(255,255,255,0.5);
|
||||
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;
|
||||
}
|
||||
.login {
|
||||
background-image: linear-gradient(to right, mc('blue', '400'), mc('blue', '600'));
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.is-error {
|
||||
background-image: linear-gradient(to right, mc('red', '400'), mc('red', '600'));
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background-image: url('../svg/login-bg.svg');
|
||||
background-position: center bottom;
|
||||
background-size: cover;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
&-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 400px;
|
||||
align-items: stretch;
|
||||
box-shadow: 0 14px 28px rgba(0,0,0,0.2);
|
||||
border-radius: 6px;
|
||||
|
||||
&.is-expanded {
|
||||
width: 650px;
|
||||
|
||||
.login-frame {
|
||||
border-radius: 0 6px 6px 0;
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
|
||||
@include until($tablet) {
|
||||
width: 350px;
|
||||
|
||||
&.is-expanded {
|
||||
width: 400px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-error {
|
||||
position: absolute;
|
||||
bottom: 105%;
|
||||
width: 100%;
|
||||
min-height: 50px;
|
||||
background-image: radial-gradient(ellipse at top left, rgba(255,255,255,.9) 0%,rgba(255,255,255,.8) 100%);
|
||||
box-shadow: 0 5px 10px rgba(0,0,0,0.2);
|
||||
border-radius: 6px;
|
||||
color: mc('red', '800');
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
|
||||
strong {
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
display: block;
|
||||
padding: 0 1rem 0 0;
|
||||
border-right: 1px solid mc('red', '200');
|
||||
}
|
||||
span {
|
||||
padding: 0 0 0 1rem;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&-providers {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 250px;
|
||||
|
||||
border-right: none;
|
||||
border-radius: 6px 0 0 6px;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
|
||||
@include until($tablet) {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
button {
|
||||
flex: 1 1;
|
||||
padding: 5px 15px;
|
||||
border: none;
|
||||
color: #FFF;
|
||||
background: linear-gradient(to right, rgba(mc('light-blue', '800'), .7), rgba(mc('light-blue', '800'), 1));
|
||||
border-top: 1px solid rgba(mc('light-blue', '900'), .5);
|
||||
font-family: $core-font-standard;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
transition: all .4s ease;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@include until($tablet) {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: mc('light-blue', '900');
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-top: none;
|
||||
|
||||
&.is-active {
|
||||
border-top: 1px solid rgba(255,255,255, .5);
|
||||
}
|
||||
}
|
||||
|
||||
&.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');
|
||||
cursor: default;
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
svg path {
|
||||
fill: mc('light-blue', '800');
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
margin-right: 10px;
|
||||
font-size: 16px;
|
||||
|
||||
@include until($tablet) {
|
||||
margin-right: 0;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-right: 10px;
|
||||
width: auto;
|
||||
height: 20px;
|
||||
max-width: 18px;
|
||||
max-height: 20px;
|
||||
|
||||
path {
|
||||
fill: #FFF;
|
||||
}
|
||||
|
||||
@include until($tablet) {
|
||||
margin-right: 0;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
font-weight: 600;
|
||||
|
||||
@include until($tablet) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-frame {
|
||||
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);
|
||||
border-radius: 6px;
|
||||
width: 400px;
|
||||
padding: 1rem;
|
||||
color: mc('grey', '700');
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
|
||||
@include until($tablet) {
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
color: mc('light-blue', '700');
|
||||
text-shadow: 1px 1px 0 #FFF;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 300;
|
||||
color: mc('grey', '700');
|
||||
text-shadow: 1px 1px 0 #FFF;
|
||||
padding: 0;
|
||||
margin: 0 0 25px 0;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
input[type=text], input[type=password] {
|
||||
width: 100%;
|
||||
border: 1px solid #FFF;
|
||||
border-radius: 3px;
|
||||
background-color: rgba(255,255,255,.9);
|
||||
box-shadow: inset 0 0 0 3px rgba(255,255,255, .25);
|
||||
padding: 0 15px;
|
||||
height: 40px;
|
||||
margin: 0 0 10px 0;
|
||||
color: mc('grey', '700');
|
||||
font-weight: 600;
|
||||
font-size: .8rem;
|
||||
transition: all 0.4s ease;
|
||||
text-align: center;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: mc('light-blue','500');
|
||||
background-color: rgba(255,255,255,1);
|
||||
box-shadow: inset 0 0 0 3px rgba(mc('light-blue','500'), .25);
|
||||
color: mc('light-blue', '800');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&-copyright {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 10vh;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
color: mc('grey', '500');
|
||||
font-weight: 400;
|
||||
|
||||
a {
|
||||
font-weight: 600;
|
||||
color: mc('light-blue', '500');
|
||||
margin-left: .25rem;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
// 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'
|
||||
const cluster = require('cluster')
|
||||
|
||||
let wiki = {
|
||||
IS_DEBUG: process.env.NODE_ENV === 'development',
|
||||
IS_MASTER: cluster.isMaster,
|
||||
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
|
||||
|
||||
// if (IS_DEBUG) {
|
||||
// if (wiki.IS_DEBUG) {
|
||||
// require('@glimpse/glimpse').init()
|
||||
// }
|
||||
|
||||
let appconf = require('./libs/config')()
|
||||
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'
|
||||
}))
|
||||
wiki.configSvc.init()
|
||||
|
||||
// ----------------------------------------
|
||||
// Passport Authentication
|
||||
// Init Logger
|
||||
// ----------------------------------------
|
||||
|
||||
require('./libs/auth')(passport)
|
||||
global.rights = require('./libs/rights')
|
||||
global.rights.init()
|
||||
wiki.logger = require('./modules/logger').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
|
||||
// ----------------------------------------
|
||||
|
||||
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')
|
||||
}
|
||||
})
|
||||
|
||||
// Init DB
|
||||
// ----------------------------------------
|
||||
// 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) {
|
||||
case 'searchAdd':
|
||||
global.search.add(m.content)
|
||||
break
|
||||
}
|
||||
})
|
||||
wiki.db = require('./modules/db').init()
|
||||
|
||||
// ----------------------------------------
|
||||
// Graceful shutdown
|
||||
// Start Kernel
|
||||
// ----------------------------------------
|
||||
|
||||
graceful.on('exit', () => {
|
||||
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()
|
||||
})
|
||||
})
|
||||
wiki.kernel.init()
|
||||
|
@ -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
|
||||
*
|
||||
* @type {<Mongoose.Schema>}
|
||||
*/
|
||||
var settingSchema = Mongoose.Schema({
|
||||
key: {
|
||||
type: String,
|
||||
required: true,
|
||||
index: true
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}, { timestamps: {} })
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
let settingSchema = sequelize.define('setting', {
|
||||
key: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
config: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: false
|
||||
}
|
||||
}, {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|