feat: modular auth + queue tasks

This commit is contained in:
NGPixel 2017-07-29 17:33:08 -04:00
parent 9c112ab535
commit f32429325c
25 changed files with 822 additions and 770 deletions

View File

@ -5,7 +5,7 @@
# https://docs.requarks.io/wiki/install # https://docs.requarks.io/wiki/install
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# Port the main server should listen to (80 by default) # Port the main server should listen to
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
port: 80 port: 80
@ -39,3 +39,9 @@ redis:
db: 0 db: 0
password: null password: null
# ---------------------------------------------------------------------
# Background Workers
# ---------------------------------------------------------------------
# Leave 0 for auto based on CPU cores
workers: 0

View File

@ -52,7 +52,6 @@
"connect-flash": "~0.1.1", "connect-flash": "~0.1.1",
"connect-redis": "~3.3.0", "connect-redis": "~3.3.0",
"cookie-parser": "~1.4.3", "cookie-parser": "~1.4.3",
"cron": "~1.2.1",
"diff2html": "~2.3.0", "diff2html": "~2.3.0",
"execa": "~0.7.0", "execa": "~0.7.0",
"express": "~4.15.3", "express": "~4.15.3",

View File

@ -5,65 +5,22 @@
name: Wiki.js name: Wiki.js
defaults: defaults:
config: config:
title: Wiki
host: http://localhost
port: 80 port: 80
paths: paths:
repo: ./repo repo: ./repo
data: ./data data: ./data
uploads: db:
maxImageFileSize: 3, host: localhost
maxOtherFileSize: 100 port: 5432
lang: en user: wikijs
public: false pass: wikijsrocks
auth: db: wiki
defaultReadAccess: false redis:
local: host: localhost
enabled: true port: 6379
microsoft: db: 0
enabled: false password: null
google: workers: 0
enabled: false
facebook:
enabled: false
github:
enabled: false
slack:
enabled: false
ldap:
enabled: false
azure:
enabled: false
db: mongodb://localhost/wiki
sessionSecret: null
admin: null
git:
url: null
branch: master
auth:
type: basic
username: null
password: null
privateKey: null
sslVerify: true
serverEmail: wiki@example.com
showUserEmail: true
features:
linebreaks: true
mathjax: true
externalLogging:
bugsnap: false
loggly: false
papertrail: false
rollbar: false
sentry: false
theme:
primary: indigo
alt: blue-grey
footer: blue-grey
code:
dark: true
colorize: true
authProviders: authProviders:
- local - local
- microsoft - microsoft
@ -112,6 +69,9 @@ langs:
- -
id: ko id: ko
name: Korean - 한국어 name: Korean - 한국어
-
id: pt
name: Portuguese - Português
- -
id: ru id: ru
name: Russian - Русский name: Russian - Русский

View File

@ -0,0 +1,33 @@
'use strict'
/* global wiki */
// ------------------------------------
// Azure AD Account
// ------------------------------------
const AzureAdOAuth2Strategy = require('passport-azure-ad-oauth2').Strategy
module.exports = (passport) => {
if (wiki.config.auth.azure && wiki.config.auth.azure.enabled) {
const jwt = require('jsonwebtoken')
passport.use('azure_ad_oauth2',
new AzureAdOAuth2Strategy({
clientID: wiki.config.auth.azure.clientId,
clientSecret: wiki.config.auth.azure.clientSecret,
callbackURL: wiki.config.host + '/login/azure/callback',
resource: wiki.config.auth.azure.resource,
tenant: wiki.config.auth.azure.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
})
}
))
}
}

View File

@ -0,0 +1,28 @@
'use strict'
/* global wiki */
// ------------------------------------
// Facebook Account
// ------------------------------------
const FacebookStrategy = require('passport-facebook').Strategy
module.exports = (passport) => {
if (wiki.config.auth.facebook && wiki.config.auth.facebook.enabled) {
passport.use('facebook',
new FacebookStrategy({
clientID: wiki.config.auth.facebook.clientId,
clientSecret: wiki.config.auth.facebook.clientSecret,
callbackURL: wiki.config.host + '/login/facebook/callback',
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
})
}
))
}
}

View File

@ -0,0 +1,28 @@
'use strict'
/* global wiki */
// ------------------------------------
// GitHub Account
// ------------------------------------
const GitHubStrategy = require('passport-github2').Strategy
module.exports = (passport) => {
if (wiki.config.auth.github && wiki.config.auth.github.enabled) {
passport.use('github',
new GitHubStrategy({
clientID: wiki.config.auth.github.clientId,
clientSecret: wiki.config.auth.github.clientSecret,
callbackURL: wiki.config.host + '/login/github/callback',
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
})
}
))
}
}

View File

@ -0,0 +1,27 @@
'use strict'
/* global wiki */
// ------------------------------------
// Google ID Account
// ------------------------------------
const GoogleStrategy = require('passport-google-oauth20').Strategy
module.exports = (passport) => {
if (wiki.config.auth.google && wiki.config.auth.google.enabled) {
passport.use('google',
new GoogleStrategy({
clientID: wiki.config.auth.google.clientId,
clientSecret: wiki.config.auth.google.clientSecret,
callbackURL: wiki.config.host + '/login/google/callback'
}, (accessToken, refreshToken, profile, cb) => {
wiki.db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
}

View File

@ -0,0 +1,41 @@
'use strict'
/* global wiki */
// ------------------------------------
// LDAP Account
// ------------------------------------
const LdapStrategy = require('passport-ldapauth').Strategy
module.exports = (passport) => {
if (wiki.config.auth.ldap && wiki.config.auth.ldap.enabled) {
passport.use('ldapauth',
new LdapStrategy({
server: {
url: wiki.config.auth.ldap.url,
bindDn: wiki.config.auth.ldap.bindDn,
bindCredentials: wiki.config.auth.ldap.bindCredentials,
searchBase: wiki.config.auth.ldap.searchBase,
searchFilter: wiki.config.auth.ldap.searchFilter,
searchAttributes: ['displayName', 'name', 'cn', 'mail'],
tlsOptions: (wiki.config.auth.ldap.tlsEnabled) ? {
ca: [
fs.readFileSync(wiki.config.auth.ldap.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
})
}
))
}
}

View File

@ -0,0 +1,34 @@
'use strict'
/* global wiki */
// ------------------------------------
// Local Account
// ------------------------------------
const LocalStrategy = require('passport-local').Strategy
module.exports = (passport) => {
if (wiki.config.auth.local && wiki.config.auth.local.enabled) {
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)
})
}
))
}
}

View File

@ -0,0 +1,27 @@
'use strict'
/* global wiki */
// ------------------------------------
// Microsoft Account
// ------------------------------------
const WindowsLiveStrategy = require('passport-windowslive').Strategy
module.exports = (passport) => {
if (wiki.config.auth.microsoft && wiki.config.auth.microsoft.enabled) {
passport.use('windowslive',
new WindowsLiveStrategy({
clientID: wiki.config.auth.microsoft.clientId,
clientSecret: wiki.config.auth.microsoft.clientSecret,
callbackURL: wiki.config.host + '/login/ms/callback'
}, function (accessToken, refreshToken, profile, cb) {
wiki.db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
}

View File

@ -0,0 +1,27 @@
'use strict'
/* global wiki */
// ------------------------------------
// Slack Account
// ------------------------------------
const SlackStrategy = require('passport-slack').Strategy
module.exports = (passport) => {
if (wiki.config.auth.slack && wiki.config.auth.slack.enabled) {
passport.use('slack',
new SlackStrategy({
clientID: wiki.config.auth.slack.clientId,
clientSecret: wiki.config.auth.slack.clientSecret,
callbackURL: wiki.config.host + '/login/slack/callback'
}, (accessToken, refreshToken, profile, cb) => {
wiki.db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
}

View File

@ -6,10 +6,14 @@
// =========================================== // ===========================================
const path = require('path') const path = require('path')
const cluster = require('cluster')
let wiki = { let wiki = {
IS_DEBUG: process.env.NODE_ENV === 'development', IS_DEBUG: process.env.NODE_ENV === 'development',
IS_MASTER: cluster.isMaster,
ROOTPATH: process.cwd(), ROOTPATH: process.cwd(),
SERVERPATH: path.join(process.cwd(), 'server') SERVERPATH: path.join(process.cwd(), 'server'),
configSvc: require('./modules/config')
} }
global.wiki = wiki global.wiki = wiki
@ -19,29 +23,36 @@ process.env.VIPS_WARNING = false
// require('@glimpse/glimpse').init() // require('@glimpse/glimpse').init()
// } // }
let appconf = require('./modules/config')() wiki.configSvc.init()
wiki.config = appconf.config
wiki.data = appconf.data
// ---------------------------------------- // ----------------------------------------
// Load Winston // Init Logger
// ---------------------------------------- // ----------------------------------------
wiki.logger = require('./modules/logger')() wiki.logger = require('./modules/logger').init()
// ----------------------------------------
// Init DB
// ----------------------------------------
wiki.db = require('./modules/db').init()
// ---------------------------------------- // ----------------------------------------
// Start Cluster // Start Cluster
// ---------------------------------------- // ----------------------------------------
const cluster = require('cluster')
const numCPUs = require('os').cpus().length const numCPUs = require('os').cpus().length
let numWorkers = (wiki.config.workers > 0) ? wiki.config.workers : numCPUs
if (numWorkers > numCPUs) {
numWorkers = numCPUs
}
if (cluster.isMaster) { if (cluster.isMaster) {
wiki.logger.info('Wiki.js is initializing...') wiki.logger.info('Wiki.js is initializing...')
require('./master') require('./master')
for (let i = 0; i < numCPUs; i++) { for (let i = 0; i < numWorkers; i++) {
cluster.fork() cluster.fork()
} }
@ -50,5 +61,5 @@ if (cluster.isMaster) {
}) })
} else { } else {
wiki.logger.info(`Background Worker #${cluster.worker.id} is starting...`) wiki.logger.info(`Background Worker #${cluster.worker.id} is starting...`)
// require('./worker') require('./worker')
} }

View File

@ -2,231 +2,241 @@
/* global wiki */ /* global wiki */
const path = require('path') const Promise = require('bluebird')
// ---------------------------------------- module.exports = Promise.join(
// Load global modules wiki.db.onReady,
// ---------------------------------------- wiki.configSvc.loadFromDb()
).then(() => {
// ----------------------------------------
// Load global modules
// ----------------------------------------
wiki.disk = require('./modules/disk').init() wiki.disk = require('./modules/disk').init()
wiki.db = require('./modules/db').init() wiki.entries = require('./modules/entries').init()
wiki.entries = require('./modules/entries').init() wiki.git = require('./modules/git').init(false)
wiki.git = require('./modules/git').init(false) wiki.lang = require('i18next')
wiki.lang = require('i18next') wiki.mark = require('./modules/markdown')
wiki.mark = require('./modules/markdown') wiki.redis = require('./modules/redis').init()
wiki.redis = require('./modules/redis').init() wiki.search = require('./modules/search').init()
wiki.search = require('./modules/search').init() wiki.upl = require('./modules/uploads').init()
wiki.upl = require('./modules/uploads').init()
// ---------------------------------------- // ----------------------------------------
// Load modules // Load modules
// ---------------------------------------- // ----------------------------------------
const autoload = require('auto-load') const autoload = require('auto-load')
const bodyParser = require('body-parser') const bodyParser = require('body-parser')
const compression = require('compression') const compression = require('compression')
const cookieParser = require('cookie-parser') const cookieParser = require('cookie-parser')
const express = require('express') const express = require('express')
const favicon = require('serve-favicon') const favicon = require('serve-favicon')
const flash = require('connect-flash') const flash = require('connect-flash')
const http = require('http') const http = require('http')
const i18nBackend = require('i18next-node-fs-backend') const i18nBackend = require('i18next-node-fs-backend')
const passport = require('passport') const path = require('path')
const passportSocketIo = require('passport.socketio') const passport = require('passport')
const session = require('express-session') const passportSocketIo = require('passport.socketio')
const SessionRedisStore = require('connect-redis')(session) const session = require('express-session')
const graceful = require('node-graceful') const SessionRedisStore = require('connect-redis')(session)
const socketio = require('socket.io') const graceful = require('node-graceful')
const graphqlApollo = require('apollo-server-express') const socketio = require('socket.io')
const graphqlSchema = require('./modules/graphql') const graphqlApollo = require('apollo-server-express')
const graphqlSchema = require('./modules/graphql')
var mw = autoload(path.join(wiki.SERVERPATH, '/middlewares')) var mw = autoload(path.join(wiki.SERVERPATH, '/middlewares'))
var ctrl = autoload(path.join(wiki.SERVERPATH, '/controllers')) var ctrl = autoload(path.join(wiki.SERVERPATH, '/controllers'))
// ---------------------------------------- // ----------------------------------------
// Define Express App // Define Express App
// ---------------------------------------- // ----------------------------------------
const app = express() const app = express()
wiki.app = app wiki.app = app
app.use(compression()) app.use(compression())
// ---------------------------------------- // ----------------------------------------
// Security // Security
// ---------------------------------------- // ----------------------------------------
app.use(mw.security) app.use(mw.security)
// ---------------------------------------- // ----------------------------------------
// Public Assets // Public Assets
// ---------------------------------------- // ----------------------------------------
app.use(favicon(path.join(wiki.ROOTPATH, 'assets', 'favicon.ico'))) app.use(favicon(path.join(wiki.ROOTPATH, 'assets', 'favicon.ico')))
app.use(express.static(path.join(wiki.ROOTPATH, 'assets'), { app.use(express.static(path.join(wiki.ROOTPATH, 'assets'), {
index: false, index: false,
maxAge: '7d' maxAge: '7d'
})) }))
// ---------------------------------------- // ----------------------------------------
// Passport Authentication // Passport Authentication
// ---------------------------------------- // ----------------------------------------
require('./modules/auth')(passport) require('./modules/auth')(passport)
wiki.rights = require('./modules/rights') wiki.rights = require('./modules/rights')
wiki.rights.init() // wiki.rights.init()
let sessionStore = new SessionRedisStore({ let sessionStore = new SessionRedisStore({
client: wiki.redis client: wiki.redis
})
app.use(cookieParser())
app.use(session({
name: 'wikijs.sid',
store: sessionStore,
secret: wiki.config.sessionSecret,
resave: false,
saveUninitialized: false
}))
app.use(flash())
app.use(passport.initialize())
app.use(passport.session())
// ----------------------------------------
// SEO
// ----------------------------------------
app.use(mw.seo)
// ----------------------------------------
// Localization Engine
// ----------------------------------------
wiki.lang.use(i18nBackend).init({
load: 'languageOnly',
ns: ['common', 'admin', 'auth', 'errors', 'git'],
defaultNS: 'common',
saveMissing: false,
preload: [wiki.config.lang],
lng: wiki.config.lang,
fallbackLng: 'en',
backend: {
loadPath: path.join(wiki.SERVERPATH, 'locales/{{lng}}/{{ns}}.json')
}
})
// ----------------------------------------
// 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._ = require('lodash')
app.locals.t = wiki.lang.t.bind(wiki.lang)
app.locals.moment = require('moment')
app.locals.moment.locale(wiki.config.lang)
app.locals.appconfig = 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 : {}
}) })
})
// ---------------------------------------- app.use(cookieParser())
// Start HTTP server app.use(session({
// ---------------------------------------- name: 'wikijs.sid',
store: sessionStore,
secret: wiki.config.site.sessionSecret,
resave: false,
saveUninitialized: false
}))
app.use(flash())
app.use(passport.initialize())
app.use(passport.session())
wiki.logger.info('Starting HTTP/WS server on port ' + wiki.config.port + '...') // ----------------------------------------
// SEO
// ----------------------------------------
app.set('port', wiki.config.port) app.use(mw.seo)
var server = http.createServer(app)
var io = socketio(server)
server.listen(wiki.config.port) // ----------------------------------------
server.on('error', (error) => { // Localization Engine
if (error.syscall !== 'listen') { // ----------------------------------------
throw error
}
// handle specific listen errors with friendly messages wiki.lang.use(i18nBackend).init({
switch (error.code) { load: 'languageOnly',
case 'EACCES': ns: ['common', 'admin', 'auth', 'errors', 'git'],
wiki.logger.error('Listening on port ' + wiki.config.port + ' requires elevated privileges!') defaultNS: 'common',
return process.exit(1) saveMissing: false,
case 'EADDRINUSE': preload: [wiki.config.site.lang],
wiki.logger.error('Port ' + wiki.config.port + ' is already in use!') lng: wiki.config.site.lang,
return process.exit(1) fallbackLng: 'en',
default: backend: {
loadPath: path.join(wiki.SERVERPATH, 'locales/{{lng}}/{{ns}}.json')
}
})
// ----------------------------------------
// 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._ = require('lodash')
app.locals.t = wiki.lang.t.bind(wiki.config.site.lang)
app.locals.moment = require('moment')
app.locals.moment.locale(wiki.config.site.lang)
app.locals.appconfig = 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('Starting HTTP/WS server on port ' + wiki.config.port + '...')
app.set('port', wiki.config.port)
var server = http.createServer(app)
var io = socketio(server)
server.listen(wiki.config.port)
server.on('error', (error) => {
if (error.syscall !== 'listen') {
throw error throw error
} }
})
server.on('listening', () => { // handle specific listen errors with friendly messages
wiki.logger.info('HTTP/WS server started successfully! [RUNNING]') switch (error.code) {
}) case 'EACCES':
wiki.logger.error('Listening on port ' + wiki.config.port + ' requires elevated privileges!')
// ---------------------------------------- return process.exit(1)
// WebSocket case 'EADDRINUSE':
// ---------------------------------------- wiki.logger.error('Port ' + wiki.config.port + ' is already in use!')
return process.exit(1)
io.use(passportSocketIo.authorize({ default:
key: 'wikijs.sid', throw error
store: sessionStore, }
secret: wiki.config.sessionSecret,
cookieParser,
success: (data, accept) => {
accept()
},
fail: (data, message, error, accept) => {
accept()
}
}))
io.on('connection', ctrl.ws)
// ----------------------------------------
// Graceful shutdown
// ----------------------------------------
graceful.on('exit', () => {
// wiki.logger.info('- SHUTTING DOWN - Terminating Background Agent...')
// bgAgent.kill()
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()
}) })
server.on('listening', () => {
wiki.logger.info('HTTP/WS server started successfully! [RUNNING]')
})
// ----------------------------------------
// WebSocket
// ----------------------------------------
io.use(passportSocketIo.authorize({
key: 'wikijs.sid',
store: sessionStore,
secret: wiki.config.site.sessionSecret,
cookieParser,
success: (data, accept) => {
accept()
},
fail: (data, message, error, accept) => {
accept()
}
}))
io.on('connection', ctrl.ws)
// ----------------------------------------
// Graceful shutdown
// ----------------------------------------
graceful.on('exit', () => {
// wiki.logger.info('- SHUTTING DOWN - Terminating Background Agent...')
// bgAgent.kill()
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
}).catch(err => {
wiki.logger.error(err)
process.exit(1)
}) })

View File

@ -24,232 +24,51 @@ module.exports = function (passport) {
}) })
}) })
// Local Account
if (wiki.config.auth.local && wiki.config.auth.local.enabled) {
const LocalStrategy = require('passport-local').Strategy
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)
})
}
))
}
// Google ID
if (wiki.config.auth.google && wiki.config.auth.google.enabled) {
const GoogleStrategy = require('passport-google-oauth20').Strategy
passport.use('google',
new GoogleStrategy({
clientID: wiki.config.auth.google.clientId,
clientSecret: wiki.config.auth.google.clientSecret,
callbackURL: wiki.config.host + '/login/google/callback'
}, (accessToken, refreshToken, profile, cb) => {
wiki.db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
// Microsoft Accounts
if (wiki.config.auth.microsoft && wiki.config.auth.microsoft.enabled) {
const WindowsLiveStrategy = require('passport-windowslive').Strategy
passport.use('windowslive',
new WindowsLiveStrategy({
clientID: wiki.config.auth.microsoft.clientId,
clientSecret: wiki.config.auth.microsoft.clientSecret,
callbackURL: wiki.config.host + '/login/ms/callback'
}, function (accessToken, refreshToken, profile, cb) {
wiki.db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
// Facebook
if (wiki.config.auth.facebook && wiki.config.auth.facebook.enabled) {
const FacebookStrategy = require('passport-facebook').Strategy
passport.use('facebook',
new FacebookStrategy({
clientID: wiki.config.auth.facebook.clientId,
clientSecret: wiki.config.auth.facebook.clientSecret,
callbackURL: wiki.config.host + '/login/facebook/callback',
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
})
}
))
}
// GitHub
if (wiki.config.auth.github && wiki.config.auth.github.enabled) {
const GitHubStrategy = require('passport-github2').Strategy
passport.use('github',
new GitHubStrategy({
clientID: wiki.config.auth.github.clientId,
clientSecret: wiki.config.auth.github.clientSecret,
callbackURL: wiki.config.host + '/login/github/callback',
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
})
}
))
}
// Slack
if (wiki.config.auth.slack && wiki.config.auth.slack.enabled) {
const SlackStrategy = require('passport-slack').Strategy
passport.use('slack',
new SlackStrategy({
clientID: wiki.config.auth.slack.clientId,
clientSecret: wiki.config.auth.slack.clientSecret,
callbackURL: wiki.config.host + '/login/slack/callback'
}, (accessToken, refreshToken, profile, cb) => {
wiki.db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
// LDAP
if (wiki.config.auth.ldap && wiki.config.auth.ldap.enabled) {
const LdapStrategy = require('passport-ldapauth').Strategy
passport.use('ldapauth',
new LdapStrategy({
server: {
url: wiki.config.auth.ldap.url,
bindDn: wiki.config.auth.ldap.bindDn,
bindCredentials: wiki.config.auth.ldap.bindCredentials,
searchBase: wiki.config.auth.ldap.searchBase,
searchFilter: wiki.config.auth.ldap.searchFilter,
searchAttributes: ['displayName', 'name', 'cn', 'mail'],
tlsOptions: (wiki.config.auth.ldap.tlsEnabled) ? {
ca: [
fs.readFileSync(wiki.config.auth.ldap.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
})
}
))
}
// AZURE AD
if (wiki.config.auth.azure && wiki.config.auth.azure.enabled) {
const AzureAdOAuth2Strategy = require('passport-azure-ad-oauth2').Strategy
const jwt = require('jsonwebtoken')
passport.use('azure_ad_oauth2',
new AzureAdOAuth2Strategy({
clientID: wiki.config.auth.azure.clientId,
clientSecret: wiki.config.auth.azure.clientSecret,
callbackURL: wiki.config.host + '/login/azure/callback',
resource: wiki.config.auth.azure.resource,
tenant: wiki.config.auth.azure.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
})
}
))
}
// Create users for first-time // Create users for first-time
wiki.db.onReady.then(() => { return wiki.db.User.findOne({ provider: 'local', email: 'guest@example.com' }).then((c) => {
return wiki.db.User.findOne({ provider: 'local', email: 'guest' }).then((c) => { if (c < 1) {
if (c < 1) { // Create guest account
// Create guest account
return wiki.db.User.create({ return wiki.db.User.create({
provider: 'local', provider: 'local',
email: 'guest@example.com', email: 'guest@example.com',
name: 'Guest', name: 'Guest',
password: '', password: '',
role: 'guest' role: 'guest'
}).then(() => { }).then(() => {
wiki.logger.info('[AUTH] Guest account created successfully!') wiki.logger.info('[AUTH] Guest account created successfully!')
return true return true
}).catch((err) => { }).catch((err) => {
wiki.logger.error('[AUTH] An error occured while creating guest account:') wiki.logger.error('[AUTH] An error occured while creating guest account:')
wiki.logger.error(err) wiki.logger.error(err)
return 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 }
})
}) })
// .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 }
// })
} }

View File

@ -8,62 +8,95 @@ const _ = require('lodash')
const path = require('path') const path = require('path')
const cfgHelper = require('../helpers/config') const cfgHelper = require('../helpers/config')
/** module.exports = {
* Load Application Configuration SUBSETS: ['auth', 'features', 'git', 'logging', 'site', 'theme', 'uploads'],
*
* @param {Object} confPaths Path to the configuration files
* @return {Object} Application Configuration
*/
module.exports = (confPaths) => {
confPaths = _.defaults(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 = {} * Load root config from disk
*
* @param {any} confPaths
* @returns
*/
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')
}
try { let appconfig = {}
appconfig = yaml.safeLoad( let appdata = {}
cfgHelper.parseConfigValue(
fs.readFileSync(confPaths.config, 'utf8') try {
appconfig = yaml.safeLoad(
cfgHelper.parseConfigValue(
fs.readFileSync(confPaths.config, 'utf8')
)
) )
) appdata = yaml.safeLoad(fs.readFileSync(confPaths.data, 'utf8'))
appdata = yaml.safeLoad(fs.readFileSync(confPaths.data, 'utf8')) appdata.regex = require(confPaths.dataRegex)
appdata.regex = require(confPaths.dataRegex) } catch (ex) {
} catch (ex) { console.error(ex)
console.error(ex) process.exit(1)
process.exit(1) }
}
// Merge with defaults // Merge with defaults
appconfig = _.defaultsDeep(appconfig, appdata.defaults.config) appconfig = _.defaultsDeep(appconfig, appdata.defaults.config)
// Check port // Check port
if (appconfig.port < 1) { if (appconfig.port < 1) {
appconfig.port = process.env.PORT || 80 appconfig.port = process.env.PORT || 80
} }
// Convert booleans // Convert booleans
appconfig.public = (appconfig.public === true || _.toLower(appconfig.public) === 'true') appconfig.public = (appconfig.public === true || _.toLower(appconfig.public) === 'true')
// List authentication strategies // List authentication strategies
wiki.config = appconfig
wiki.data = appdata
appconfig.authStrategies = { // List authentication strategies
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 { // appconfig.authStrategies = {
config: appconfig, // list: _.filter(appconfig.auth, ['enabled', true]),
data: appdata // socialEnabled: (_.chain(appconfig.auth).omit('local').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)
// }
},
/**
* Load config from DB
*
* @param {Array} subsets Array of subsets to load
* @returns Promise
*/
loadFromDb(subsets) {
if (!_.isArray(subsets) || subsets.length === 0) {
subsets = this.SUBSETS
}
return wiki.db.Setting.findAll({
attributes: ['key', 'config'],
where: {
key: {
$in: subsets
}
}
}).then(results => {
if (_.isArray(results) && results.length > 0) {
results.forEach(result => {
wiki.config[result.key] = result.config
})
return true
} else {
return Promise.reject(new Error('Invalid DB Configuration result set'))
}
})
} }
} }

View File

@ -5,6 +5,7 @@
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const _ = require('lodash') const _ = require('lodash')
const Promise = require('bluebird')
/** /**
* PostgreSQL DB module * PostgreSQL DB module
@ -33,7 +34,8 @@ module.exports = {
max: 10, max: 10,
min: 0, min: 0,
idle: 10000 idle: 10000
} },
logging: false
}) })
// Attempt to connect and authenticate to DB // Attempt to connect and authenticate to DB
@ -63,10 +65,10 @@ module.exports = {
// Sync DB // Sync DB
self.onReady = self.inst.sync({ self.onReady = (wiki.IS_MASTER) ? self.inst.sync({
force: false, force: false,
logging: false logging: false
}) }) : Promise.resolve()
return self return self
} }

View File

@ -27,7 +27,7 @@ module.exports = {
this._uploadsThumbsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'thumbs') this._uploadsThumbsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'thumbs')
this.createBaseDirectories() this.createBaseDirectories()
this.initMulter() // this.initMulter()
return this return this
}, },
@ -37,8 +37,10 @@ module.exports = {
*/ */
initMulter () { initMulter () {
let maxFileSizes = { let maxFileSizes = {
img: wiki.config.uploads.maxImageFileSize * 1024 * 1024, // img: wiki.config.uploads.maxImageFileSize * 1024 * 1024,
file: wiki.config.uploads.maxOtherFileSize * 1024 * 1024 // file: wiki.config.uploads.maxOtherFileSize * 1024 * 1024
img: 3 * 1024 * 1024,
file: 10 * 1024 * 1024
} }
// -> IMAGES // -> IMAGES

View File

@ -51,7 +51,7 @@ module.exports = {
// -> Initialize repository // -> Initialize repository
self.onReady = self._initRepo() self.onReady = (wiki.IS_MASTER) ? self._initRepo() : Promise.resolve()
// Define signature // Define signature

View File

@ -4,75 +4,77 @@
const cluster = require('cluster') const cluster = require('cluster')
module.exports = () => { module.exports = {
let winston = require('winston') init() {
let winston = require('winston')
// Console // Console
let logger = new (winston.Logger)({ let logger = new (winston.Logger)({
level: (wiki.IS_DEBUG) ? 'debug' : 'info', level: (wiki.IS_DEBUG) ? 'debug' : 'info',
transports: [ transports: [
new (winston.transports.Console)({ new (winston.transports.Console)({
level: (wiki.IS_DEBUG) ? 'debug' : 'info', level: (wiki.IS_DEBUG) ? 'debug' : 'info',
prettyPrint: true, prettyPrint: true,
colorize: true, colorize: true,
silent: false, silent: false,
timestamp: true 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) { logger.filters.push((level, msg) => {
require('winston-loggly-bulk') let processName = (cluster.isMaster) ? 'MASTER' : `WORKER-${cluster.worker.id}`
logger.add(winston.transports.Loggly, { return '[' + processName + '] ' + msg
token: wiki.config.externalLogging.loggly.token,
subdomain: wiki.config.externalLogging.loggly.subdomain,
tags: ['wiki-js'],
level: 'warn',
json: true
}) })
}
if (wiki.config.externalLogging.papertrail) { // External services
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) { // if (wiki.config.externalLogging.bugsnag) {
const rollbarTransport = require('./winston-transports/rollbar') // const bugsnagTransport = require('./winston-transports/bugsnag')
logger.add(rollbarTransport, { // logger.add(bugsnagTransport, {
level: 'warn', // level: 'warn',
key: wiki.config.externalLogging.rollbar // key: wiki.config.externalLogging.bugsnag
}) // })
} // }
if (wiki.config.externalLogging.sentry) { // if (wiki.config.externalLogging.loggly) {
const sentryTransport = require('./winston-transports/sentry') // require('winston-loggly-bulk')
logger.add(sentryTransport, { // logger.add(winston.transports.Loggly, {
level: 'warn', // token: wiki.config.externalLogging.loggly.token,
key: wiki.config.externalLogging.sentry // subdomain: wiki.config.externalLogging.loggly.subdomain,
}) // tags: ['wiki-js'],
} // level: 'warn',
// json: true
// })
// }
return logger // 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
}
} }

View File

@ -23,7 +23,8 @@ const mdRemove = require('remove-markdown')
var mkdown = md({ var mkdown = md({
html: true, html: true,
breaks: wiki.config.features.linebreaks, // breaks: wiki.config.features.linebreaks,
breaks: true,
linkify: true, linkify: true,
typography: true, typography: true,
highlight(str, lang) { highlight(str, lang) {
@ -57,7 +58,8 @@ var mkdown = md({
}) })
.use(mdAttrs) .use(mdAttrs)
if (wiki.config.features.mathjax) { // if (wiki.config.features.mathjax) {
if (true) {
mkdown.use(mdMathjax) mkdown.use(mdMathjax)
} }

View File

@ -1,6 +1,6 @@
'use strict' 'use strict'
/* global db, git, lang, upl */ /* global wiki */
const path = require('path') const path = require('path')
const Promise = require('bluebird') const Promise = require('bluebird')
@ -32,8 +32,8 @@ module.exports = {
init () { init () {
let self = this let self = this
self._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads') self._uploadsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, 'uploads')
self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs') self._uploadsThumbsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'thumbs')
return self return self
}, },
@ -59,16 +59,16 @@ module.exports = {
self._watcher.on('add', (p) => { self._watcher.on('add', (p) => {
let pInfo = self.parseUploadsRelPath(p) let pInfo = self.parseUploadsRelPath(p)
return self.processFile(pInfo.folder, pInfo.filename).then((mData) => { return self.processFile(pInfo.folder, pInfo.filename).then((mData) => {
return db.UplFile.findByIdAndUpdate(mData._id, mData, { upsert: true }) return wiki.db.UplFile.findByIdAndUpdate(mData._id, mData, { upsert: true })
}).then(() => { }).then(() => {
return git.commitUploads(lang.t('git:uploaded', { path: p })) return wiki.git.commitUploads(wiki.lang.t('git:uploaded', { path: p }))
}) })
}) })
// -> Remove upload file // -> Remove upload file
self._watcher.on('unlink', (p) => { self._watcher.on('unlink', (p) => {
return git.commitUploads(lang.t('git:deleted', { path: p })) return wiki.git.commitUploads(wiki.lang.t('git:deleted', { path: p }))
}) })
}, },
@ -91,8 +91,8 @@ module.exports = {
// Add folders to DB // Add folders to DB
return db.UplFolder.remove({}).then(() => { return wiki.db.UplFolder.remove({}).then(() => {
return db.UplFolder.insertMany(_.map(folderNames, (f) => { return wiki.db.UplFolder.insertMany(_.map(folderNames, (f) => {
return { return {
_id: 'f:' + f, _id: 'f:' + f,
name: f name: f
@ -107,7 +107,7 @@ module.exports = {
let fldPath = path.join(self._uploadsPath, fldName) let fldPath = path.join(self._uploadsPath, fldName)
return fs.readdirAsync(fldPath).then((fList) => { return fs.readdirAsync(fldPath).then((fList) => {
return Promise.map(fList, (f) => { return Promise.map(fList, (f) => {
return upl.processFile(fldName, f).then((mData) => { return wiki.upl.processFile(fldName, f).then((mData) => {
if (mData) { if (mData) {
allFiles.push(mData) allFiles.push(mData)
} }
@ -118,9 +118,9 @@ module.exports = {
}, {concurrency: 1}).finally(() => { }, {concurrency: 1}).finally(() => {
// Add files to DB // Add files to DB
return db.UplFile.remove({}).then(() => { return wiki.db.UplFile.remove({}).then(() => {
if (_.isArray(allFiles) && allFiles.length > 0) { if (_.isArray(allFiles) && allFiles.length > 0) {
return db.UplFile.insertMany(allFiles) return wiki.db.UplFile.insertMany(allFiles)
} else { } else {
return true return true
} }
@ -131,7 +131,7 @@ module.exports = {
}).then(() => { }).then(() => {
// Watch for new changes // Watch for new changes
return upl.watch() return wiki.upl.watch()
}) })
}, },

65
server/queues/git-sync.js Normal file
View File

@ -0,0 +1,65 @@
'use strict'
/* global wiki */
const Promise = require('bluebird')
const fs = Promise.promisifyAll(require('fs-extra'))
const klaw = require('klaw')
const moment = require('moment')
const path = require('path')
const entryHelper = require('../helpers/entry')
module.exports = (job, done) => {
return wiki.git.resync().then(() => {
// -> Stream all documents
let cacheJobs = []
let jobCbStreamDocsResolve = null
let jobCbStreamDocs = new Promise((resolve, reject) => {
jobCbStreamDocsResolve = resolve
})
klaw(wiki.REPOPATH).on('data', function (item) {
if (path.extname(item.path) === '.md' && path.basename(item.path) !== 'README.md') {
let entryPath = entryHelper.parsePath(entryHelper.getEntryPathFromFullPath(item.path))
let cachePath = entryHelper.getCachePath(entryPath)
// -> Purge outdated cache
cacheJobs.push(
fs.statAsync(cachePath).then((st) => {
return moment(st.mtime).isBefore(item.stats.mtime) ? 'expired' : 'active'
}).catch((err) => {
return (err.code !== 'EEXIST') ? err : 'new'
}).then((fileStatus) => {
// -> Delete expired cache file
if (fileStatus === 'expired') {
return fs.unlinkAsync(cachePath).return(fileStatus)
}
return fileStatus
}).then((fileStatus) => {
// -> Update cache and search index
if (fileStatus !== 'active') {
return global.entries.updateCache(entryPath).then(entry => {
process.send({
action: 'searchAdd',
content: entry
})
return true
})
}
return true
})
)
}
}).on('end', () => {
jobCbStreamDocsResolve(Promise.all(cacheJobs))
})
return jobCbStreamDocs
})
}

View File

@ -0,0 +1,26 @@
'use strict'
/* global wiki */
const Promise = require('bluebird')
const fs = Promise.promisifyAll(require('fs-extra'))
const moment = require('moment')
const path = require('path')
module.exports = (job, done) => {
return fs.readdirAsync(wiki.UPLTEMPPATH).then((ls) => {
let fifteenAgo = moment().subtract(15, 'minutes')
return Promise.map(ls, (f) => {
return fs.statAsync(path.join(wiki.UPLTEMPPATH, f)).then((s) => { return { filename: f, stat: s } })
}).filter((s) => { return s.stat.isFile() }).then((arrFiles) => {
return Promise.map(arrFiles, (f) => {
if (moment(f.stat.ctime).isBefore(fifteenAgo, 'minute')) {
return fs.unlinkAsync(path.join(wiki.UPLTEMPPATH, f.filename))
} else {
return true
}
})
})
})
}

View File

@ -4,30 +4,27 @@
const path = require('path') const path = require('path')
wiki.REPOPATH = path.resolve(wiki.ROOTPATH, wiki.config.paths.repo)
wiki.DATAPATH = path.resolve(wiki.ROOTPATH, wiki.config.paths.data)
wiki.UPLTEMPPATH = path.join(wiki.DATAPATH, 'temp-upload')
// ---------------------------------------- // ----------------------------------------
// Load global modules // Load global modules
// ---------------------------------------- // ----------------------------------------
wiki.db = require('./modules/db').init() // wiki.upl = require('./modules/uploads-agent').init()
wiki.upl = require('./modules/uploads-agent').init() // wiki.git = require('./modules/git').init()
wiki.git = require('./modules/git').init() // wiki.entries = require('./modules/entries').init()
wiki.entries = require('./modules/entries').init()
wiki.lang = require('i18next') wiki.lang = require('i18next')
wiki.mark = require('./modules/markdown') wiki.mark = require('./modules/markdown')
// ---------------------------------------- // ----------------------------------------
// Load modules // Load local modules
// ---------------------------------------- // ----------------------------------------
const moment = require('moment')
const Promise = require('bluebird') 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 i18nBackend = require('i18next-node-fs-backend')
const entryHelper = require('./helpers/entry')
// ---------------------------------------- // ----------------------------------------
// Localization Engine // Localization Engine
// ---------------------------------------- // ----------------------------------------
@ -46,143 +43,21 @@ wiki.lang.use(i18nBackend).init({
}) })
// ---------------------------------------- // ----------------------------------------
// Start Cron // Start Queues
// ---------------------------------------- // ----------------------------------------
let job const Bull = require('bull')
let jobIsBusy = false const autoload = require('auto-load')
let jobUplWatchStarted = false
wiki.db.onReady.then(() => { let queues = autoload(path.join(wiki.SERVERPATH, 'queues'))
return wiki.db.Entry.remove({})
}).then(() => {
job = new Cron({
cronTime: '0 */5 * * * *',
onTick: () => {
// Make sure we don't start two concurrent jobs
if (jobIsBusy) { Promise.join(
wiki.logger.warn('Previous job has not completed gracefully or is still running! Skipping for now. (This is not normal, you should investigate)') wiki.db.onReady
return // wiki.upl.initialScan()
} ).then(() => {
wiki.logger.info('Running all jobs...') for (let queueName in queues) {
jobIsBusy = true new Bull(queueName, { redis: wiki.config.redis }).process(queues[queueName])
}
// Prepare async job collector
let jobs = []
let repoPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.repo)
let dataPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.data)
let uploadsTempPath = path.join(dataPath, 'temp-upload')
// ----------------------------------------
// REGULAR JOBS
// ----------------------------------------
//* ****************************************
// -> Sync with Git remote
//* ****************************************
jobs.push(wiki.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(() => {
wiki.logger.info('All jobs completed successfully! Going to sleep for now.')
if (!jobUplWatchStarted) {
jobUplWatchStarted = true
wiki.upl.initialScan().then(() => {
job.start()
})
}
return true
}).catch((err) => {
wiki.logger.error('One or more jobs have failed: ', err)
}).finally(() => {
jobIsBusy = false
})
},
start: false,
timeZone: 'UTC',
runOnInit: true
})
}) })
// ---------------------------------------- // ----------------------------------------
@ -190,11 +65,6 @@ wiki.db.onReady.then(() => {
// ---------------------------------------- // ----------------------------------------
process.on('disconnect', () => { process.on('disconnect', () => {
wiki.logger.warn('Lost connection to main server. Exiting...') wiki.logger.warn('Lost connection to Master. Exiting...')
job.stop()
process.exit() process.exit()
}) })
process.on('exit', () => {
job.stop()
})

View File

@ -1609,7 +1609,7 @@ cron-parser@^2.4.1:
is-nan "^1.2.1" is-nan "^1.2.1"
moment-timezone "^0.5.0" moment-timezone "^0.5.0"
cron@1.2.1, cron@~1.2.1: cron@1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/cron/-/cron-1.2.1.tgz#3a86c09b41b8f261ac863a7cc85ea4735857eab2" resolved "https://registry.yarnpkg.com/cron/-/cron-1.2.1.tgz#3a86c09b41b8f261ac863a7cc85ea4735857eab2"
dependencies: dependencies: