From 3471a7a6f9591de856a96520c6b0b9d3ff426f36 Mon Sep 17 00:00:00 2001 From: NGPixel Date: Sun, 4 Feb 2018 00:53:13 -0500 Subject: [PATCH] refactor: project cleanup + onboarding page --- client/js/components/setup.vue | 11 +- client/scss/app.scss | 2 + client/scss/base/base.scss | 26 +- client/scss/pages/_welcome.scss | 42 ++ package.json | 2 - server/controllers/auth.js | 2 +- server/controllers/common.js | 307 +----------- server/{modules => core}/auth.js | 2 +- server/{modules => core}/config.js | 10 +- server/{modules => core}/db.js | 86 ++-- server/{modules => core}/graphql.js | 10 +- server/{modules => core}/kernel.js | 2 +- server/{modules => core}/localization.js | 4 + server/{modules => core}/logger.js | 8 +- server/{modules => core}/queue.js | 6 +- server/{modules => core}/redis.js | 18 +- server/{modules => core}/system.js | 7 +- server/{modules => core}/telemetry.js | 2 +- server/index.js | 10 +- server/master.js | 51 +- .../authentication/auth0.js | 0 .../authentication/azure.js | 0 .../authentication/discord.js | 0 .../authentication/dropbox.js | 0 .../authentication/facebook.js | 0 .../authentication/github.js | 0 .../authentication/google.js | 0 .../authentication/ldap.js | 0 .../authentication/local.js | 0 .../authentication/microsoft.js | 0 .../authentication/oauth2.js | 0 .../authentication/slack.js | 0 .../authentication/twitch.js | 0 server/modules/disk.js | 169 ------- server/modules/documents.js | 455 ------------------ server/modules/git.js | 306 ------------ .../logging/bugsnag.js | 0 .../logging/console.js | 0 .../{extensions => modules}/logging/loggly.js | 0 .../logging/papertrail.js | 0 .../logging/rollbar.js | 0 .../{extensions => modules}/logging/sentry.js | 0 server/modules/markdown.js | 417 ---------------- .../renderer/common/mathjax.js | 0 .../renderer/markdown/abbreviations.js | 0 .../renderer/markdown/emoji.js | 0 .../renderer/markdown/expand-tabs.js | 0 .../renderer/markdown/footnotes.js | 0 .../renderer/markdown/mathjax.js | 0 .../renderer/markdown/tasks-lists.js | 0 server/modules/rights.js | 122 ----- server/modules/search.js | 211 -------- .../{extensions => modules}/storage/disk.js | 0 server/{extensions => modules}/storage/git.js | 0 server/modules/uploads-agent.js | 252 ---------- server/modules/uploads.js | 281 ----------- server/setup.js | 13 +- server/views/{pages => }/admin/_layout.pug | 0 server/views/{pages => }/admin/profile.pug | 0 server/views/{pages => }/admin/settings.pug | 0 server/views/{pages => }/admin/stats.pug | 0 server/views/{pages => }/admin/system.pug | 0 server/views/{pages => }/admin/theme.pug | 0 server/views/{pages => }/admin/users-edit.pug | 0 server/views/{pages => }/admin/users.pug | 0 server/views/{ => errors}/error-forbidden.pug | 0 server/views/{ => errors}/error-notexist.pug | 0 server/views/layout.pug | 7 +- server/views/{pages => main}/all.pug | 0 server/views/{pages => main}/create.pug | 0 server/views/{pages => main}/edit.pug | 0 server/views/{pages => main}/history.pug | 0 server/views/{pages => main}/login.pug | 0 server/views/{pages => main}/setup.pug | 0 server/views/{pages => main}/source.pug | 0 server/views/{pages => main}/view.pug | 0 server/views/main/welcome.pug | 10 + server/views/pages/welcome.pug | 11 - server/worker.js | 10 +- yarn.lock | 87 +--- 80 files changed, 177 insertions(+), 2782 deletions(-) create mode 100644 client/scss/pages/_welcome.scss rename server/{modules => core}/auth.js (97%) rename server/{modules => core}/config.js (100%) rename server/{modules => core}/db.js (62%) rename server/{modules => core}/graphql.js (98%) rename server/{modules => core}/kernel.js (100%) rename server/{modules => core}/localization.js (91%) rename server/{modules => core}/logger.js (93%) rename server/{modules => core}/queue.js (98%) rename server/{modules => core}/redis.js (74%) rename server/{modules => core}/system.js (97%) rename server/{modules => core}/telemetry.js (100%) rename server/{extensions => modules}/authentication/auth0.js (100%) rename server/{extensions => modules}/authentication/azure.js (100%) rename server/{extensions => modules}/authentication/discord.js (100%) rename server/{extensions => modules}/authentication/dropbox.js (100%) rename server/{extensions => modules}/authentication/facebook.js (100%) rename server/{extensions => modules}/authentication/github.js (100%) rename server/{extensions => modules}/authentication/google.js (100%) rename server/{extensions => modules}/authentication/ldap.js (100%) rename server/{extensions => modules}/authentication/local.js (100%) rename server/{extensions => modules}/authentication/microsoft.js (100%) rename server/{extensions => modules}/authentication/oauth2.js (100%) rename server/{extensions => modules}/authentication/slack.js (100%) rename server/{extensions => modules}/authentication/twitch.js (100%) delete mode 100644 server/modules/disk.js delete mode 100644 server/modules/documents.js delete mode 100644 server/modules/git.js rename server/{extensions => modules}/logging/bugsnag.js (100%) rename server/{extensions => modules}/logging/console.js (100%) rename server/{extensions => modules}/logging/loggly.js (100%) rename server/{extensions => modules}/logging/papertrail.js (100%) rename server/{extensions => modules}/logging/rollbar.js (100%) rename server/{extensions => modules}/logging/sentry.js (100%) delete mode 100644 server/modules/markdown.js rename server/{extensions => modules}/renderer/common/mathjax.js (100%) rename server/{extensions => modules}/renderer/markdown/abbreviations.js (100%) rename server/{extensions => modules}/renderer/markdown/emoji.js (100%) rename server/{extensions => modules}/renderer/markdown/expand-tabs.js (100%) rename server/{extensions => modules}/renderer/markdown/footnotes.js (100%) rename server/{extensions => modules}/renderer/markdown/mathjax.js (100%) rename server/{extensions => modules}/renderer/markdown/tasks-lists.js (100%) delete mode 100644 server/modules/rights.js delete mode 100644 server/modules/search.js rename server/{extensions => modules}/storage/disk.js (100%) rename server/{extensions => modules}/storage/git.js (100%) delete mode 100644 server/modules/uploads-agent.js delete mode 100644 server/modules/uploads.js rename server/views/{pages => }/admin/_layout.pug (100%) rename server/views/{pages => }/admin/profile.pug (100%) rename server/views/{pages => }/admin/settings.pug (100%) rename server/views/{pages => }/admin/stats.pug (100%) rename server/views/{pages => }/admin/system.pug (100%) rename server/views/{pages => }/admin/theme.pug (100%) rename server/views/{pages => }/admin/users-edit.pug (100%) rename server/views/{pages => }/admin/users.pug (100%) rename server/views/{ => errors}/error-forbidden.pug (100%) rename server/views/{ => errors}/error-notexist.pug (100%) rename server/views/{pages => main}/all.pug (100%) rename server/views/{pages => main}/create.pug (100%) rename server/views/{pages => main}/edit.pug (100%) rename server/views/{pages => main}/history.pug (100%) rename server/views/{pages => main}/login.pug (100%) rename server/views/{pages => main}/setup.pug (100%) rename server/views/{pages => main}/source.pug (100%) rename server/views/{pages => main}/view.pug (100%) create mode 100644 server/views/main/welcome.pug delete mode 100644 server/views/pages/welcome.pug diff --git a/client/js/components/setup.vue b/client/js/components/setup.vue index 6101479d..618b5f0f 100644 --- a/client/js/components/setup.vue +++ b/client/js/components/setup.vue @@ -353,6 +353,7 @@ /* global siteConfig */ import axios from 'axios' +import _ from 'lodash' export default { props: { @@ -491,7 +492,7 @@ export default { results: [] } - this.$helpers._.delay(() => { + _.delay(() => { axios.post('/syscheck', self.conf).then(resp => { if (resp.data.ok === true) { self.syscheck.ok = true @@ -517,9 +518,9 @@ export default { }, proceedToConsiderations: function (ev) { this.considerations = { - https: !this.$helpers._.startsWith(this.conf.host, 'https'), + https: false, port: false, // TODO - localhost: this.$helpers._.includes(this.conf.host, 'localhost') + localhost: false } this.state = 'considerations' this.loading = false @@ -542,7 +543,7 @@ export default { error: '' } - this.$helpers._.delay(() => { + _.delay(() => { axios.post('/gitcheck', self.conf).then(resp => { if (resp.data.ok === true) { self.gitcheck.ok = true @@ -587,7 +588,7 @@ export default { redirectUrl: '' } - this.$helpers._.delay(() => { + _.delay(() => { axios.post('/finalize', self.conf).then(resp => { if (resp.data.ok === true) { self.$helpers._.delay(() => { diff --git a/client/scss/app.scss b/client/scss/app.scss index 5e9ba402..c764b423 100644 --- a/client/scss/app.scss +++ b/client/scss/app.scss @@ -30,6 +30,8 @@ @import 'components/editor'; +@import 'pages/welcome'; + @import 'layout/_header'; @import 'layout/_loader'; @import 'layout/_rtl'; diff --git a/client/scss/base/base.scss b/client/scss/base/base.scss index a9bb13d3..ad834d1d 100644 --- a/client/scss/base/base.scss +++ b/client/scss/base/base.scss @@ -16,7 +16,6 @@ html { min-height: 100%; &.is-fullscreen { - //width: 100vw; height: 100vh; } } @@ -27,7 +26,10 @@ body { } main { - background-color: lighten(mc('blue-grey','50'), 5%); + background-color: mc('blue','500'); + background-image: linear-gradient(to bottom, mc('blue', '700') 0%, mc('blue', '500') 100%); + padding: 50px; + min-height: 100vh; } a { @@ -42,28 +44,8 @@ a { // Container -.has-stickynav { - padding-top: 50px; -} - .container { position: relative; - - @include desktop { - margin: 0 auto; - max-width: 960px; - - // Modifiers - &.is-fluid { - margin: 0; - max-width: none; - } - } - - - @include widescreen { - max-width: 1200px; - } } .content { diff --git a/client/scss/pages/_welcome.scss b/client/scss/pages/_welcome.scss new file mode 100644 index 00000000..c63e4c26 --- /dev/null +++ b/client/scss/pages/_welcome.scss @@ -0,0 +1,42 @@ +.onboarding { + background: linear-gradient(to bottom, mc('grey', '900') 0%, mc('grey', '800') 100%); + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: mc('grey', '50'); + + &::before { + content: ''; + display:block; + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + background-image: url('../static/svg/login-bg-motif.svg'); + background-repeat: repeat; + background-size: 500px; + z-index: 0; + opacity: .75; + } + + img { + width: 500px; + filter: grayscale(100%) brightness(160%); + margin-bottom: 3rem; + z-index: 2; + } + h1 { + margin-bottom: 1rem; + z-index: 2; + } + h2 { + margin-bottom: 3rem; + z-index: 2; + } + .button { + z-index: 2; + } +} diff --git a/package.json b/package.json index c0da1a62..0f41793a 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,6 @@ "filesize.js": "1.0.2", "follow-redirects": "1.4.1", "fs-extra": "5.0.0", - "git-wrapper2-promise": "0.2.9", "graphql": "0.12.3", "graphql-tools": "2.19.0", "highlight.js": "9.12.0", @@ -96,7 +95,6 @@ "mongodb": "3.0.1", "multer": "1.3.0", "node-2fa": "1.1.2", - "nodegit": "0.20.3", "ora": "1.3.0", "passport": "0.4.0", "passport-auth0": "0.6.1", diff --git a/server/controllers/auth.js b/server/controllers/auth.js index 5c564a54..dae3a6e8 100644 --- a/server/controllers/auth.js +++ b/server/controllers/auth.js @@ -34,7 +34,7 @@ const bruteforce = new ExpressBrute(EBstore, { * Login form */ router.get('/login', function (req, res, next) { - res.render('pages/login') + res.render('main/login') }) router.post('/login', bruteforce.prevent, function (req, res, next) { diff --git a/server/controllers/common.js b/server/controllers/common.js index c974112c..17cb037f 100644 --- a/server/controllers/common.js +++ b/server/controllers/common.js @@ -1,316 +1,11 @@ -'use strict' - -/* global entries, git, lang, winston */ - const express = require('express') const router = express.Router() -const _ = require('lodash') - -const entryHelper = require('../helpers/entry') - -// ========================================== -// EDIT MODE -// ========================================== - -/** - * Edit document in Markdown - */ -router.get('/edit/*', (req, res, next) => { - if (!res.locals.rights.write) { - return res.render('error-forbidden') - } - - let safePath = entryHelper.parsePath(_.replace(req.path, '/edit', '')) - - entries.fetchOriginal(safePath, { - parseMarkdown: false, - parseMeta: true, - parseTree: false, - includeMarkdown: true, - includeParentInfo: false, - cache: false - }).then((pageData) => { - if (pageData) { - res.render('pages/edit', { pageData }) - } else { - throw new Error(lang.t('errors:invalidpath')) - } - return true - }).catch((err) => { - res.render('error', { - message: err.message, - error: {} - }) - }) -}) - -router.put('/edit/*', (req, res, next) => { - if (!res.locals.rights.write) { - return res.json({ - ok: false, - error: lang.t('errors:forbidden') - }) - } - - let safePath = entryHelper.parsePath(_.replace(req.path, '/edit', '')) - - entries.update(safePath, req.body.markdown, req.user).then(() => { - return res.json({ - ok: true - }) || true - }).catch((err) => { - res.json({ - ok: false, - error: err.message - }) - }) -}) - -// ========================================== -// CREATE MODE -// ========================================== - -router.get('/create/*', (req, res, next) => { - if (!res.locals.rights.write) { - return res.render('error-forbidden') - } - - if (_.some(['create', 'edit', 'account', 'source', 'history', 'mk', 'all'], (e) => { return _.startsWith(req.path, '/create/' + e) })) { - return res.render('error', { - message: lang.t('errors:reservedname'), - error: {} - }) - } - - let safePath = entryHelper.parsePath(_.replace(req.path, '/create', '')) - - entries.exists(safePath).then((docExists) => { - if (!docExists) { - return entries.getStarter(safePath).then((contents) => { - let pageData = { - markdown: contents, - meta: { - title: _.startCase(safePath), - path: safePath - } - } - res.render('pages/create', { pageData }) - - return true - }).catch((err) => { - winston.warn(err) - throw new Error(lang.t('errors:starterfailed')) - }) - } else { - throw new Error(lang.t('errors:alreadyexists')) - } - }).catch((err) => { - res.render('error', { - message: err.message, - error: {} - }) - }) -}) - -router.put('/create/*', (req, res, next) => { - if (!res.locals.rights.write) { - return res.json({ - ok: false, - error: lang.t('errors:forbidden') - }) - } - - let safePath = entryHelper.parsePath(_.replace(req.path, '/create', '')) - - entries.create(safePath, req.body.markdown, req.user).then(() => { - return res.json({ - ok: true - }) || true - }).catch((err) => { - return res.json({ - ok: false, - error: err.message - }) - }) -}) - -// ========================================== -// LIST ALL PAGES -// ========================================== - -/** - * View tree view of all pages - */ -router.use((req, res, next) => { - if (_.endsWith(req.url, '/all')) { - res.render('pages/all') - } else { - next() - } -}) - -// ========================================== -// VIEW MODE -// ========================================== - -/** - * View source of a document - */ -router.get('/source/*', (req, res, next) => { - let safePath = entryHelper.parsePath(_.replace(req.path, '/source', '')) - - entries.fetchOriginal(safePath, { - parseMarkdown: false, - parseMeta: true, - parseTree: false, - includeMarkdown: true, - includeParentInfo: false, - cache: false - }).then((pageData) => { - if (pageData) { - res.render('pages/source', { pageData }) - } else { - throw new Error(lang.t('errors:invalidpath')) - } - return true - }).catch((err) => { - res.render('error', { - message: err.message, - error: {} - }) - }) -}) - -/** - * View history of a document - */ -router.get('/hist/*', (req, res, next) => { - let safePath = entryHelper.parsePath(_.replace(req.path, '/hist', '')) - - entries.getHistory(safePath).then((pageData) => { - if (pageData) { - res.render('pages/history', { pageData }) - } else { - throw new Error(lang.t('errors:invalidpath')) - } - return true - }).catch((err) => { - res.render('error', { - message: err.message, - error: {} - }) - }) -}) - -/** - * View history of a document - */ -router.post('/hist', (req, res, next) => { - let commit = req.body.commit - let safePath = entryHelper.parsePath(req.body.path) - - if (!/^[a-f0-9]{40}$/.test(commit)) { - return res.status(400).json({ ok: false, error: 'Invalid commit' }) - } - - git.getHistoryDiff(safePath, commit).then((diff) => { - res.json({ ok: true, diff }) - return true - }).catch((err) => { - res.status(500).json({ ok: false, error: err.message }) - }) -}) /** * View document */ router.get('/*', (req, res, next) => { - let safePath = entryHelper.parsePath(req.path) - - entries.fetch(safePath).then((pageData) => { - if (pageData) { - res.render('pages/view', { pageData }) - } else { - res.render('error-notexist', { - newpath: safePath - }) - } - return true - }).error((err) => { - if (safePath === 'home') { - res.render('pages/welcome') - } else { - res.render('error-notexist', { - message: err.message, - newpath: safePath - }) - } - return true - }).catch((err) => { - res.render('error', { - message: err.message, - error: {} - }) - }) -}) - -/** - * Move document - */ -router.put('/*', (req, res, next) => { - if (!res.locals.rights.write) { - return res.json({ - ok: false, - error: lang.t('errors:forbidden') - }) - } - - let safePath = entryHelper.parsePath(req.path) - - if (_.isEmpty(req.body.move)) { - return res.json({ - ok: false, - error: lang.t('errors:invalidaction') - }) - } - - let safeNewPath = entryHelper.parsePath(req.body.move) - - entries.move(safePath, safeNewPath, req.user).then(() => { - res.json({ - ok: true - }) - }).catch((err) => { - res.json({ - ok: false, - error: err.message - }) - }) -}) - -/** - * Delete document - */ -router.delete('/*', (req, res, next) => { - if (!res.locals.rights.write) { - return res.json({ - ok: false, - error: lang.t('errors:forbidden') - }) - } - - let safePath = entryHelper.parsePath(req.path) - - entries.remove(safePath, req.user).then(() => { - res.json({ - ok: true - }) - }).catch((err) => { - res.json({ - ok: false, - error: err.message - }) - }) + res.render('main/welcome') }) module.exports = router diff --git a/server/modules/auth.js b/server/core/auth.js similarity index 97% rename from server/modules/auth.js rename to server/core/auth.js index 55f3c0df..a2892ee5 100644 --- a/server/modules/auth.js +++ b/server/core/auth.js @@ -33,7 +33,7 @@ module.exports = { _.forOwn(_.omitBy(wiki.config.auth.strategies, s => s.enabled === false), (strategyConfig, strategyKey) => { strategyConfig.callbackURL = `${wiki.config.site.host}${wiki.config.site.path}login/${strategyKey}/callback` - let strategy = require(`../extensions/authentication/${strategyKey}`) + let strategy = require(`../modules/authentication/${strategyKey}`) try { strategy.init(passport, strategyConfig) } catch (err) { diff --git a/server/modules/config.js b/server/core/config.js similarity index 100% rename from server/modules/config.js rename to server/core/config.js index cedf84e7..f78259b3 100644 --- a/server/modules/config.js +++ b/server/core/config.js @@ -1,10 +1,10 @@ -/* global wiki */ - -const fs = require('fs') -const yaml = require('js-yaml') const _ = require('lodash') -const path = require('path') const cfgHelper = require('../helpers/config') +const fs = require('fs') +const path = require('path') +const yaml = require('js-yaml') + +/* global wiki */ module.exports = { /** diff --git a/server/modules/db.js b/server/core/db.js similarity index 62% rename from server/modules/db.js rename to server/core/db.js index ccc0fed2..4f19b5b5 100644 --- a/server/modules/db.js +++ b/server/core/db.js @@ -1,54 +1,52 @@ -/* global wiki */ - +const _ = require('lodash') const fs = require('fs') const path = require('path') -const _ = require('lodash') const Promise = require('bluebird') const Sequelize = require('sequelize') -const Op = Sequelize.Op + +/* global wiki */ 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 + $eq: Sequelize.Op.eq, + $ne: Sequelize.Op.ne, + $gte: Sequelize.Op.gte, + $gt: Sequelize.Op.gt, + $lte: Sequelize.Op.lte, + $lt: Sequelize.Op.lt, + $not: Sequelize.Op.not, + $in: Sequelize.Op.in, + $notIn: Sequelize.Op.notIn, + $is: Sequelize.Op.is, + $like: Sequelize.Op.like, + $notLike: Sequelize.Op.notLike, + $iLike: Sequelize.Op.iLike, + $notILike: Sequelize.Op.notILike, + $regexp: Sequelize.Op.regexp, + $notRegexp: Sequelize.Op.notRegexp, + $iRegexp: Sequelize.Op.iRegexp, + $notIRegexp: Sequelize.Op.notIRegexp, + $between: Sequelize.Op.between, + $notBetween: Sequelize.Op.notBetween, + $overlap: Sequelize.Op.overlap, + $contains: Sequelize.Op.contains, + $contained: Sequelize.Op.contained, + $adjacent: Sequelize.Op.adjacent, + $strictLeft: Sequelize.Op.strictLeft, + $strictRight: Sequelize.Op.strictRight, + $noExtendRight: Sequelize.Op.noExtendRight, + $noExtendLeft: Sequelize.Op.noExtendLeft, + $and: Sequelize.Op.and, + $or: Sequelize.Op.or, + $any: Sequelize.Op.any, + $all: Sequelize.Op.all, + $values: Sequelize.Op.values, + $col: Sequelize.Op.col } /** * PostgreSQL DB module */ module.exports = { - Sequelize, Op: Sequelize.Op, @@ -59,12 +57,11 @@ module.exports = { */ 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, { + this.inst = new this.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', @@ -79,7 +76,7 @@ module.exports = { // Attempt to connect and authenticate to DB - self.inst.authenticate().then(() => { + this.inst.authenticate().then(() => { wiki.logger.info('Database (PostgreSQL) connection: [ OK ]') }).catch(err => { wiki.logger.error('Failed to connect to PostgreSQL instance.') @@ -128,9 +125,8 @@ module.exports = { // Perform init tasks - self.onReady = Promise.each(initTasksQueue, t => t()).return(true) + this.onReady = Promise.each(initTasksQueue, t => t()).return(true) - return self + return this } - } diff --git a/server/modules/graphql.js b/server/core/graphql.js similarity index 98% rename from server/modules/graphql.js rename to server/core/graphql.js index aa6a6f7d..33c032cd 100644 --- a/server/modules/graphql.js +++ b/server/core/graphql.js @@ -1,12 +1,10 @@ -'use strict' +const _ = require('lodash') +const fs = require('fs') +const gqlTools = require('graphql-tools') +const path = require('path') /* 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') diff --git a/server/modules/kernel.js b/server/core/kernel.js similarity index 100% rename from server/modules/kernel.js rename to server/core/kernel.js index 8d78fc67..f697c47e 100644 --- a/server/modules/kernel.js +++ b/server/core/kernel.js @@ -1,6 +1,6 @@ +const _ = require('lodash') const cluster = require('cluster') const Promise = require('bluebird') -const _ = require('lodash') /* global wiki */ diff --git a/server/modules/localization.js b/server/core/localization.js similarity index 91% rename from server/modules/localization.js rename to server/core/localization.js index 0d133bb2..c178cb4c 100644 --- a/server/modules/localization.js +++ b/server/core/localization.js @@ -1,6 +1,7 @@ const _ = require('lodash') const dotize = require('dotize') const i18nBackend = require('i18next-node-fs-backend') +const i18nMW = require('i18next-express-middleware') const i18next = require('i18next') const path = require('path') const Promise = require('bluebird') @@ -27,6 +28,9 @@ module.exports = { }) return this }, + attachMiddleware (app) { + app.use(i18nMW.handle(this.engine)) + }, async getByNamespace(locale, namespace) { if (this.engine.hasResourceBundle(locale, namespace)) { let data = this.engine.getResourceBundle(locale, namespace) diff --git a/server/modules/logger.js b/server/core/logger.js similarity index 93% rename from server/modules/logger.js rename to server/core/logger.js index 6b5ce4f1..a3bc3b1c 100644 --- a/server/modules/logger.js +++ b/server/core/logger.js @@ -1,10 +1,10 @@ -/* global wiki */ - -const cluster = require('cluster') const _ = require('lodash') +const cluster = require('cluster') const fs = require('fs-extra') const path = require('path') +/* global wiki */ + module.exports = { loggers: {}, init() { @@ -21,7 +21,7 @@ module.exports = { }) _.forOwn(_.omitBy(wiki.config.logging.loggers, s => s.enabled === false), (loggerConfig, loggerKey) => { - let loggerModule = require(`../extensions/logging/${loggerKey}`) + let loggerModule = require(`../modules/logging/${loggerKey}`) loggerModule.init(logger, loggerConfig) fs.readFile(path.join(wiki.ROOTPATH, `assets/svg/auth-icon-${loggerKey}.svg`), 'utf8').then(iconData => { logger.icon = iconData diff --git a/server/modules/queue.js b/server/core/queue.js similarity index 98% rename from server/modules/queue.js rename to server/core/queue.js index d7de0400..158081fd 100644 --- a/server/modules/queue.js +++ b/server/core/queue.js @@ -1,10 +1,8 @@ -'use strict' - -/* global wiki */ - const Bull = require('bull') const Promise = require('bluebird') +/* global wiki */ + module.exports = { init() { wiki.data.queues.forEach(queueName => { diff --git a/server/modules/redis.js b/server/core/redis.js similarity index 74% rename from server/modules/redis.js rename to server/core/redis.js index a4610aed..4cd7cfd3 100644 --- a/server/modules/redis.js +++ b/server/core/redis.js @@ -1,22 +1,9 @@ -'use strict' - -/* global wiki */ - const Redis = require('ioredis') const { isPlainObject } = require('lodash') -/** - * Redis module - * - * @return {Object} Redis client wrapper instance - */ -module.exports = { +/* global wiki */ - /** - * Initialize Redis client - * - * @return {Object} Redis client instance - */ +module.exports = { init() { if (isPlainObject(wiki.config.redis)) { let red = new Redis(wiki.config.redis) @@ -33,5 +20,4 @@ module.exports = { process.exit(1) } } - } diff --git a/server/modules/system.js b/server/core/system.js similarity index 97% rename from server/modules/system.js rename to server/core/system.js index 6752b59e..9c181f5c 100644 --- a/server/modules/system.js +++ b/server/core/system.js @@ -1,9 +1,8 @@ -/* global wiki */ - -const Promise = require('bluebird') -// const pm2 = Promise.promisifyAll(require('pm2')) const _ = require('lodash') const cfgHelper = require('../helpers/config') +const Promise = require('bluebird') + +/* global wiki */ module.exports = { /** diff --git a/server/modules/telemetry.js b/server/core/telemetry.js similarity index 100% rename from server/modules/telemetry.js rename to server/core/telemetry.js index c85f2212..b7dc6ff5 100644 --- a/server/modules/telemetry.js +++ b/server/core/telemetry.js @@ -1,8 +1,8 @@ +const _ = require('lodash') const axios = require('axios') const bugsnag = require('bugsnag') const path = require('path') const uuid = require('uuid/v4') -const _ = require('lodash') /* global wiki */ diff --git a/server/index.js b/server/index.js index 86c6ec59..35229613 100644 --- a/server/index.js +++ b/server/index.js @@ -12,8 +12,8 @@ let wiki = { ROOTPATH: process.cwd(), SERVERPATH: path.join(process.cwd(), 'server'), Error: require('./helpers/error'), - configSvc: require('./modules/config'), - kernel: require('./modules/kernel') + configSvc: require('./core/config'), + kernel: require('./core/kernel') } global.wiki = wiki @@ -27,13 +27,13 @@ wiki.configSvc.init() // Init Logger // ---------------------------------------- -wiki.logger = require('./modules/logger').init() +wiki.logger = require('./core/logger').init() // ---------------------------------------- // Init Telemetry // ---------------------------------------- -wiki.telemetry = require('./modules/telemetry').init() +wiki.telemetry = require('./core/telemetry').init() process.on('unhandledRejection', (err) => { wiki.telemetry.sendError(err) @@ -46,7 +46,7 @@ process.on('uncaughtException', (err) => { // Init DB // ---------------------------------------- -wiki.db = require('./modules/db').init() +wiki.db = require('./core/db').init() // ---------------------------------------- // Start Kernel diff --git a/server/master.js b/server/master.js index 77ca5af6..7edd4168 100644 --- a/server/master.js +++ b/server/master.js @@ -1,37 +1,31 @@ +const autoload = require('auto-load') +const bodyParser = require('body-parser') +const compression = require('compression') +const cookieParser = require('cookie-parser') +const cors = require('cors') +const express = require('express') +const favicon = require('serve-favicon') +const http = require('http') +const path = require('path') +const session = require('express-session') +const SessionRedisStore = require('connect-redis')(session) +const graphqlApollo = require('apollo-server-express') +const graphqlSchema = require('./core/graphql') + /* global wiki */ module.exports = async () => { // ---------------------------------------- - // Load global modules + // Load core 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() + wiki.auth = require('./core/auth').init() + wiki.lang = require('./core/localization').init() // ---------------------------------------- - // Load modules + // Load middlewares // ---------------------------------------- - const autoload = require('auto-load') - const bodyParser = require('body-parser') - const compression = require('compression') - const cookieParser = require('cookie-parser') - const cors = require('cors') - const express = require('express') - const favicon = require('serve-favicon') - const http = require('http') - const path = require('path') - const session = require('express-session') - const SessionRedisStore = require('connect-redis')(session) - 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')) @@ -97,13 +91,18 @@ module.exports = async () => { app.use(bodyParser.json({ limit: '1mb' })) app.use(bodyParser.urlencoded({ extended: false, limit: '1mb' })) + // ---------------------------------------- + // Localization + // ---------------------------------------- + + wiki.lang.attachMiddleware(app) + // ---------------------------------------- // 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 @@ -157,7 +156,7 @@ module.exports = async () => { }) // ---------------------------------------- - // Start HTTP server + // HTTP server // ---------------------------------------- let srvConnections = {} diff --git a/server/extensions/authentication/auth0.js b/server/modules/authentication/auth0.js similarity index 100% rename from server/extensions/authentication/auth0.js rename to server/modules/authentication/auth0.js diff --git a/server/extensions/authentication/azure.js b/server/modules/authentication/azure.js similarity index 100% rename from server/extensions/authentication/azure.js rename to server/modules/authentication/azure.js diff --git a/server/extensions/authentication/discord.js b/server/modules/authentication/discord.js similarity index 100% rename from server/extensions/authentication/discord.js rename to server/modules/authentication/discord.js diff --git a/server/extensions/authentication/dropbox.js b/server/modules/authentication/dropbox.js similarity index 100% rename from server/extensions/authentication/dropbox.js rename to server/modules/authentication/dropbox.js diff --git a/server/extensions/authentication/facebook.js b/server/modules/authentication/facebook.js similarity index 100% rename from server/extensions/authentication/facebook.js rename to server/modules/authentication/facebook.js diff --git a/server/extensions/authentication/github.js b/server/modules/authentication/github.js similarity index 100% rename from server/extensions/authentication/github.js rename to server/modules/authentication/github.js diff --git a/server/extensions/authentication/google.js b/server/modules/authentication/google.js similarity index 100% rename from server/extensions/authentication/google.js rename to server/modules/authentication/google.js diff --git a/server/extensions/authentication/ldap.js b/server/modules/authentication/ldap.js similarity index 100% rename from server/extensions/authentication/ldap.js rename to server/modules/authentication/ldap.js diff --git a/server/extensions/authentication/local.js b/server/modules/authentication/local.js similarity index 100% rename from server/extensions/authentication/local.js rename to server/modules/authentication/local.js diff --git a/server/extensions/authentication/microsoft.js b/server/modules/authentication/microsoft.js similarity index 100% rename from server/extensions/authentication/microsoft.js rename to server/modules/authentication/microsoft.js diff --git a/server/extensions/authentication/oauth2.js b/server/modules/authentication/oauth2.js similarity index 100% rename from server/extensions/authentication/oauth2.js rename to server/modules/authentication/oauth2.js diff --git a/server/extensions/authentication/slack.js b/server/modules/authentication/slack.js similarity index 100% rename from server/extensions/authentication/slack.js rename to server/modules/authentication/slack.js diff --git a/server/extensions/authentication/twitch.js b/server/modules/authentication/twitch.js similarity index 100% rename from server/extensions/authentication/twitch.js rename to server/modules/authentication/twitch.js diff --git a/server/modules/disk.js b/server/modules/disk.js deleted file mode 100644 index 83f8b69b..00000000 --- a/server/modules/disk.js +++ /dev/null @@ -1,169 +0,0 @@ -'use strict' - -/* global wiki */ - -const path = require('path') -const Promise = require('bluebird') -const fs = Promise.promisifyAll(require('fs-extra')) -const multer = require('multer') -const os = require('os') -const _ = require('lodash') - -/** - * Local Disk Storage - */ -module.exports = { - - _uploadsPath: './repo/uploads', - _uploadsThumbsPath: './data/thumbs', - - uploadImgHandler: null, - - /** - * Initialize Local Data Storage model - */ - init () { - this._uploadsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, 'uploads') - this._uploadsThumbsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'thumbs') - - this.createBaseDirectories() - // this.initMulter() - - return this - }, - - /** - * Init Multer upload handlers - */ - initMulter () { - let maxFileSizes = { - // img: wiki.config.uploads.maxImageFileSize * 1024 * 1024, - // file: wiki.config.uploads.maxOtherFileSize * 1024 * 1024 - img: 3 * 1024 * 1024, - file: 10 * 1024 * 1024 - } - - // -> IMAGES - - this.uploadImgHandler = multer({ - storage: multer.diskStorage({ - destination: (req, f, cb) => { - cb(null, path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'temp-upload')) - } - }), - fileFilter: (req, f, cb) => { - // -> Check filesize - - if (f.size > maxFileSizes.img) { - return cb(null, false) - } - - // -> Check MIME type (quick check only) - - if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], f.mimetype)) { - return cb(null, false) - } - - cb(null, true) - } - }).array('imgfile', 20) - - // -> FILES - - this.uploadFileHandler = multer({ - storage: multer.diskStorage({ - destination: (req, f, cb) => { - cb(null, path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'temp-upload')) - } - }), - fileFilter: (req, f, cb) => { - // -> Check filesize - - if (f.size > maxFileSizes.file) { - return cb(null, false) - } - - cb(null, true) - } - }).array('binfile', 20) - - return true - }, - - /** - * Creates a base directories (Synchronous). - */ - createBaseDirectories () { - try { - fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data)) - fs.emptyDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data)) - fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data, './cache')) - fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data, './thumbs')) - fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data, './temp-upload')) - - if (os.type() !== 'Windows_NT') { - fs.chmodSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.data, './temp-upload'), '755') - } - - fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.repo)) - fs.ensureDirSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, './uploads')) - - if (os.type() !== 'Windows_NT') { - fs.chmodSync(path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, './uploads'), '755') - } - } catch (err) { - wiki.logger.error(err) - } - - wiki.logger.info('Disk Data Paths: [ OK ]') - }, - - /** - * Gets the uploads path. - * - * @return {String} The uploads path. - */ - getUploadsPath () { - return this._uploadsPath - }, - - /** - * Gets the thumbnails folder path. - * - * @return {String} The thumbs path. - */ - getThumbsPath () { - return this._uploadsThumbsPath - }, - - /** - * Check if filename is valid and unique - * - * @param {String} f The filename - * @param {String} fld The containing folder - * @param {boolean} isImage Indicates if image - * @return {Promise} Promise of the accepted filename - */ - validateUploadsFilename (f, fld, isImage) { - let fObj = path.parse(f) - let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(new RegExp('[^a-z0-9-' + wiki.data.regex.cjk + wiki.data.regex.arabic + ']', 'g'), '') - let fext = _.toLower(fObj.ext) - - if (isImage && !_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) { - fext = '.png' - } - - f = fname + fext - let fpath = path.resolve(this._uploadsPath, fld, f) - - return fs.statAsync(fpath).then((s) => { - throw new Error(wiki.lang.t('errors:fileexists', { path: f })) - }).catch((err) => { - if (err.code === 'ENOENT') { - return f - } - throw err - }) - } - -} diff --git a/server/modules/documents.js b/server/modules/documents.js deleted file mode 100644 index 0cc59a6f..00000000 --- a/server/modules/documents.js +++ /dev/null @@ -1,455 +0,0 @@ -/* global wiki */ - -const Promise = require('bluebird') -const path = require('path') -const fs = Promise.promisifyAll(require('fs-extra')) -const _ = require('lodash') - -const entryHelper = require('../helpers/entry') - -/** - * Documents Model - */ -module.exports = { - - _repoPath: 'repo', - _cachePath: 'data/cache', - - /** - * Initialize Entries model - * - * @return {Object} Entries model instance - */ - init() { - let self = this - - self._repoPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.repo) - self._cachePath = path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'cache') - wiki.data.repoPath = self._repoPath - wiki.data.cachePath = self._cachePath - - return self - }, - - /** - * Check if a document already exists - * - * @param {String} entryPath The entry path - * @return {Promise} True if exists, false otherwise - */ - exists(entryPath) { - let self = this - - return self.fetchOriginal(entryPath, { - parseMarkdown: false, - parseMeta: false, - parseTree: false, - includeMarkdown: false, - includeParentInfo: false, - cache: false - }).then(() => { - return true - }).catch((err) => { // eslint-disable-line handle-callback-err - return false - }) - }, - - /** - * Fetch a document from cache, otherwise the original - * - * @param {String} entryPath The entry path - * @return {Promise} Page Data - */ - fetch(entryPath) { - let self = this - - let cpath = entryHelper.getCachePath(entryPath) - - return fs.statAsync(cpath).then((st) => { - return st.isFile() - }).catch((err) => { // eslint-disable-line handle-callback-err - return false - }).then((isCache) => { - if (isCache) { - // Load from cache - - return fs.readFileAsync(cpath).then((contents) => { - return JSON.parse(contents) - }).catch((err) => { // eslint-disable-line handle-callback-err - wiki.logger.error('Corrupted cache file. Deleting it...') - fs.unlinkSync(cpath) - return false - }) - } else { - // Load original - - return self.fetchOriginal(entryPath) - } - }) - }, - - /** - * Fetches the original document entry - * - * @param {String} entryPath The entry path - * @param {Object} options The options - * @return {Promise} Page data - */ - fetchOriginal(entryPath, options) { - let self = this - - let fpath = entryHelper.getFullPath(entryPath) - let cpath = entryHelper.getCachePath(entryPath) - - options = _.defaults(options, { - parseMarkdown: true, - parseMeta: true, - parseTree: true, - includeMarkdown: false, - includeParentInfo: true, - cache: true - }) - - return fs.statAsync(fpath).then((st) => { - if (st.isFile()) { - return fs.readFileAsync(fpath, 'utf8').then((contents) => { - let htmlProcessor = (options.parseMarkdown) ? wiki.mark.parseContent(contents) : Promise.resolve('') - - // Parse contents - - return htmlProcessor.then(html => { - let pageData = { - markdown: (options.includeMarkdown) ? contents : '', - html, - meta: (options.parseMeta) ? wiki.mark.parseMeta(contents) : {}, - tree: (options.parseTree) ? wiki.mark.parseTree(contents) : [] - } - - if (!pageData.meta.title) { - pageData.meta.title = _.startCase(entryPath) - } - - pageData.meta.path = entryPath - - // Get parent - - let parentPromise = (options.includeParentInfo) ? self.getParentInfo(entryPath).then((parentData) => { - return (pageData.parent = parentData) - }).catch((err) => { // eslint-disable-line handle-callback-err - return (pageData.parent = false) - }) : Promise.resolve(true) - - return parentPromise.then(() => { - // Cache to disk - - if (options.cache) { - let cacheData = JSON.stringify(_.pick(pageData, ['html', 'meta', 'tree', 'parent']), false, false, false) - return fs.writeFileAsync(cpath, cacheData).catch((err) => { - wiki.logger.error('Unable to write to cache! Performance may be affected.') - wiki.logger.error(err) - return true - }) - } else { - return true - } - }).return(pageData) - }) - }) - } else { - return false - } - }).catch((err) => { // eslint-disable-line handle-callback-err - throw new Promise.OperationalError(wiki.lang.t('errors:notexist', { path: entryPath })) - }) - }, - - /** - * Gets the parent information. - * - * @param {String} entryPath The entry path - * @return {Promise} The parent information. - */ - getParentInfo(entryPath) { - if (_.includes(entryPath, '/')) { - let parentParts = _.initial(_.split(entryPath, '/')) - let parentPath = _.join(parentParts, '/') - let parentFile = _.last(parentParts) - let fpath = entryHelper.getFullPath(parentPath) - - return fs.statAsync(fpath).then((st) => { - if (st.isFile()) { - return fs.readFileAsync(fpath, 'utf8').then((contents) => { - let pageMeta = wiki.mark.parseMeta(contents) - - return { - path: parentPath, - title: (pageMeta.title) ? pageMeta.title : _.startCase(parentFile), - subtitle: (pageMeta.subtitle) ? pageMeta.subtitle : false - } - }) - } else { - return Promise.reject(new Error(wiki.lang.t('errors:parentinvalid'))) - } - }) - } else { - return Promise.reject(new Error(wiki.lang.t('errors:parentisroot'))) - } - }, - - /** - * Update an existing document - * - * @param {String} entryPath The entry path - * @param {String} contents The markdown-formatted contents - * @param {Object} author The author user object - * @return {Promise} True on success, false on failure - */ - update(entryPath, contents, author) { - let self = this - let fpath = entryHelper.getFullPath(entryPath) - - return fs.statAsync(fpath).then((st) => { - if (st.isFile()) { - return self.makePersistent(entryPath, contents, author).then(() => { - return self.updateCache(entryPath).then(entry => { - return wiki.search.add(entry) - }) - }) - } else { - return Promise.reject(new Error(wiki.lang.t('errors:notexist', { path: entryPath }))) - } - }).catch((err) => { - wiki.logger.error(err) - return Promise.reject(new Error(wiki.lang.t('errors:savefailed'))) - }) - }, - - /** - * Update local cache - * - * @param {String} entryPath The entry path - * @return {Promise} Promise of the operation - */ - updateCache(entryPath) { - let self = this - - return self.fetchOriginal(entryPath, { - parseMarkdown: true, - parseMeta: true, - parseTree: true, - includeMarkdown: true, - includeParentInfo: true, - cache: true - }).catch(err => { - wiki.logger.error(err) - return err - }).then((pageData) => { - return { - entryPath, - meta: pageData.meta, - parent: pageData.parent || {}, - text: wiki.mark.removeMarkdown(pageData.markdown) - } - }).catch(err => { - wiki.logger.error(err) - return err - }).then((content) => { - let parentPath = _.chain(content.entryPath).split('/').initial().join('/').value() - return wiki.db.Entry.findOneAndUpdate({ - _id: content.entryPath - }, { - _id: content.entryPath, - title: content.meta.title || content.entryPath, - subtitle: content.meta.subtitle || '', - parentTitle: content.parent.title || '', - parentPath: parentPath, - isDirectory: false, - isEntry: true - }, { - new: true, - upsert: true - }).then(result => { - let plainResult = result.toObject() - plainResult.text = content.text - return plainResult - }) - }).then(result => { - return self.updateTreeInfo().then(() => { - return result - }) - }).catch(err => { - wiki.logger.error(err) - return err - }) - }, - - /** - * Update tree info for all directory and parent entries - * - * @returns {Promise} Promise of the operation - */ - updateTreeInfo() { - return wiki.db.Entry.distinct('parentPath', { parentPath: { $ne: '' } }).then(allPaths => { - if (allPaths.length > 0) { - return Promise.map(allPaths, pathItem => { - let parentPath = _.chain(pathItem).split('/').initial().join('/').value() - let guessedTitle = _.chain(pathItem).split('/').last().startCase().value() - return wiki.db.Entry.update({ _id: pathItem }, { - $set: { isDirectory: true }, - $setOnInsert: { isEntry: false, title: guessedTitle, parentPath } - }, { upsert: true }) - }) - } else { - return true - } - }) - }, - - /** - * Create a new document - * - * @param {String} entryPath The entry path - * @param {String} contents The markdown-formatted contents - * @param {Object} author The author user object - * @return {Promise} True on success, false on failure - */ - create(entryPath, contents, author) { - let self = this - - return self.exists(entryPath).then((docExists) => { - if (!docExists) { - return self.makePersistent(entryPath, contents, author).then(() => { - return self.updateCache(entryPath).then(entry => { - return wiki.search.add(entry) - }) - }) - } else { - return Promise.reject(new Error(wiki.lang.t('errors:alreadyexists'))) - } - }).catch((err) => { - wiki.logger.error(err) - return Promise.reject(new Error(wiki.lang.t('errors:generic'))) - }) - }, - - /** - * Makes a document persistent to disk and git repository - * - * @param {String} entryPath The entry path - * @param {String} contents The markdown-formatted contents - * @param {Object} author The author user object - * @return {Promise} True on success, false on failure - */ - makePersistent(entryPath, contents, author) { - let fpath = entryHelper.getFullPath(entryPath) - - return fs.outputFileAsync(fpath, contents).then(() => { - return wiki.git.commitDocument(entryPath, author) - }) - }, - - /** - * Move a document - * - * @param {String} entryPath The current entry path - * @param {String} newEntryPath The new entry path - * @param {Object} author The author user object - * @return {Promise} Promise of the operation - */ - move(entryPath, newEntryPath, author) { - let self = this - - if (_.isEmpty(entryPath) || entryPath === 'home') { - return Promise.reject(new Error(wiki.lang.t('errors:invalidpath'))) - } - - return wiki.git.moveDocument(entryPath, newEntryPath).then(() => { - return wiki.git.commitDocument(newEntryPath, author).then(() => { - // Delete old cache version - - let oldEntryCachePath = entryHelper.getCachePath(entryPath) - fs.unlinkAsync(oldEntryCachePath).catch((err) => { return true }) // eslint-disable-line handle-callback-err - - // Delete old index entry - - wiki.search.delete(entryPath) - - // Create cache for new entry - - return Promise.join( - wiki.db.Entry.deleteOne({ _id: entryPath }), - self.updateCache(newEntryPath).then(entry => { - return wiki.search.add(entry) - }) - ) - }) - }) - }, - - /** - * Delete a document - * - * @param {String} entryPath The current entry path - * @param {Object} author The author user object - * @return {Promise} Promise of the operation - */ - remove(entryPath, author) { - if (_.isEmpty(entryPath) || entryPath === 'home') { - return Promise.reject(new Error(wiki.lang.t('errors:invalidpath'))) - } - - return wiki.git.deleteDocument(entryPath, author).then(() => { - // Delete old cache version - - let oldEntryCachePath = entryHelper.getCachePath(entryPath) - fs.unlinkAsync(oldEntryCachePath).catch((err) => { return true }) // eslint-disable-line handle-callback-err - - // Delete old index entry - wiki.search.delete(entryPath) - - // Delete entry - return wiki.db.Entry.deleteOne({ _id: entryPath }) - }) - }, - - /** - * Generate a starter page content based on the entry path - * - * @param {String} entryPath The entry path - * @return {Promise} Starter content - */ - getStarter(entryPath) { - let formattedTitle = _.startCase(_.last(_.split(entryPath, '/'))) - - return fs.readFileAsync(path.join(wiki.SERVERPATH, 'app/content/create.md'), 'utf8').then((contents) => { - return _.replace(contents, new RegExp('{TITLE}', 'g'), formattedTitle) - }) - }, - - /** - * Get all entries from base path - * - * @param {String} basePath Path to list from - * @param {Object} usr Current user - * @return {Promise} List of entries - */ - getFromTree(basePath, usr) { - return wiki.db.Entry.find({ parentPath: basePath }, 'title parentPath isDirectory isEntry').sort({ title: 'asc' }).then(results => { - return _.filter(results, r => { - return wiki.rights.checkRole('/' + r._id, usr.rights, 'read') - }) - }) - }, - - getHistory(entryPath) { - return wiki.db.Entry.findOne({ _id: entryPath, isEntry: true }).then(entry => { - if (!entry) { return false } - return wiki.git.getHistory(entryPath).then(history => { - return { - meta: entry, - history - } - }) - }) - } -} diff --git a/server/modules/git.js b/server/modules/git.js deleted file mode 100644 index 07d8d2a9..00000000 --- a/server/modules/git.js +++ /dev/null @@ -1,306 +0,0 @@ -'use strict' - -/* global wiki */ - -const Git = require('git-wrapper2-promise') -const Promise = require('bluebird') -const path = require('path') -const fs = Promise.promisifyAll(require('fs-extra')) -const _ = require('lodash') -const URL = require('url') -const moment = require('moment') - -const securityHelper = require('../helpers/security') - -/** - * Git Model - */ -module.exports = { - - _git: null, - _url: '', - _repo: { - path: '', - branch: 'master', - exists: false - }, - _signature: { - email: 'wiki@example.com' - }, - _opts: { - clone: {}, - push: {} - }, - onReady: null, - - /** - * Initialize Git model - * - * @return {Object} Git model instance - */ - init() { - let self = this - - // -> Build repository path - - if (_.isEmpty(wiki.config.paths.repo)) { - self._repo.path = path.join(wiki.ROOTPATH, 'repo') - } else { - self._repo.path = wiki.config.paths.repo - } - - // -> Initialize repository - - self.onReady = (wiki.IS_MASTER) ? self._initRepo() : Promise.resolve() - - if (wiki.config.git) { - self._repo.branch = wiki.config.git.branch || 'master' - self._signature.email = wiki.config.git.serverEmail || 'wiki@example.com' - } - - return self - }, - - /** - * Initialize Git repository - * - * @param {Object} wiki.config The application config - * @return {Object} Promise - */ - _initRepo() { - let self = this - - // -> Check if path is accessible - - return fs.mkdirAsync(self._repo.path).catch((err) => { - if (err.code !== 'EEXIST') { - wiki.logger.error('Invalid Git repository path or missing permissions.') - } - }).then(() => { - self._git = new Git({ 'git-dir': self._repo.path }) - - // -> Check if path already contains a git working folder - - return self._git.isRepo().then((isRepo) => { - self._repo.exists = isRepo - return (!isRepo) ? self._git.exec('init') : true - }).catch((err) => { // eslint-disable-line handle-callback-err - self._repo.exists = false - }) - }).then(() => { - if (wiki.config.git.enabled === false) { - wiki.logger.warn('Git Remote Sync: [ DISABLED ]') - return Promise.resolve(true) - } - - // Initialize remote - - let urlObj = URL.parse(wiki.config.git.url) - if (wiki.config.git.auth.type !== 'ssh') { - urlObj.auth = wiki.config.git.auth.username + ':' + wiki.config.git.auth.password - } - self._url = URL.format(urlObj) - - let gitConfigs = [ - () => { return self._git.exec('config', ['--local', 'user.name', 'Wiki']) }, - () => { return self._git.exec('config', ['--local', 'user.email', self._signature.email]) }, - () => { return self._git.exec('config', ['--local', '--bool', 'http.sslVerify', _.toString(wiki.config.git.auth.sslVerify)]) } - ] - - if (wiki.config.git.auth.type === 'ssh') { - gitConfigs.push(() => { - return self._git.exec('config', ['--local', 'core.sshCommand', 'ssh -i "' + wiki.config.git.auth.privateKey + '" -o StrictHostKeyChecking=no']) - }) - } - - return self._git.exec('remote', 'show').then((cProc) => { - let out = cProc.stdout.toString() - return Promise.each(gitConfigs, fn => { return fn() }).then(() => { - if (!_.includes(out, 'origin')) { - return self._git.exec('remote', ['add', 'origin', self._url]) - } else { - return self._git.exec('remote', ['set-url', 'origin', self._url]) - } - }).catch(err => { - wiki.logger.error(err) - }) - }) - }).catch((err) => { - wiki.logger.error('Git remote error!') - throw err - }).then(() => { - wiki.logger.info('Git Repository: [ OK ]') - return true - }) - }, - - /** - * Gets the repo path. - * - * @return {String} The repo path. - */ - getRepoPath() { - return this._repo.path || path.join(wiki.ROOTPATH, 'repo') - }, - - /** - * Sync with the remote repository - * - * @return {Promise} Resolve on sync success - */ - resync() { - let self = this - - // Is git remote disabled? - - if (wiki.config.git === false) { - return Promise.resolve(true) - } - - // Fetch - - wiki.logger.info('Performing pull from remote Git repository...') - return self._git.pull('origin', self._repo.branch).then((cProc) => { - wiki.logger.info('Git Pull completed.') - }) - .catch((err) => { - wiki.logger.error('Unable to fetch from git origin!') - throw err - }) - .then(() => { - // Check for changes - - return self._git.exec('log', 'origin/' + self._repo.branch + '..HEAD').then((cProc) => { - let out = cProc.stdout.toString() - - if (_.includes(out, 'commit')) { - wiki.logger.info('Performing push to remote Git repository...') - return self._git.push('origin', self._repo.branch).then(() => { - return wiki.logger.info('Git Push completed.') - }) - } else { - wiki.logger.info('Git Push skipped. Repository is already in sync.') - } - - return true - }) - }) - .catch((err) => { - wiki.logger.error('Unable to push changes to remote Git repository!') - throw err - }) - }, - - /** - * Commits a document. - * - * @param {String} entryPath The entry path - * @return {Promise} Resolve on commit success - */ - commitDocument(entryPath, author) { - let self = this - let gitFilePath = entryPath + '.md' - let commitMsg = '' - - return self._git.exec('ls-files', gitFilePath).then((cProc) => { - let out = cProc.stdout.toString() - return _.includes(out, gitFilePath) - }).then((isTracked) => { - commitMsg = (isTracked) ? wiki.lang.t('git:updated', { path: gitFilePath }) : wiki.lang.t('git:added', { path: gitFilePath }) - return self._git.add(gitFilePath) - }).then(() => { - let commitUsr = securityHelper.sanitizeCommitUser(author) - return self._git.exec('commit', ['-m', commitMsg, '--author="' + commitUsr.name + ' <' + commitUsr.email + '>"']).catch((err) => { - if (_.includes(err.stdout, 'nothing to commit')) { return true } - }) - }) - }, - - /** - * Move a document. - * - * @param {String} entryPath The current entry path - * @param {String} newEntryPath The new entry path - * @return {Promise} Resolve on success - */ - moveDocument(entryPath, newEntryPath) { - let self = this - let gitFilePath = entryPath + '.md' - let gitNewFilePath = newEntryPath + '.md' - let destPathObj = path.parse(this.getRepoPath() + '/' + gitNewFilePath) - - return fs.ensureDir(destPathObj.dir).then(() => { - return self._git.exec('mv', [gitFilePath, gitNewFilePath]).then((cProc) => { - let out = cProc.stdout.toString() - if (_.includes(out, 'fatal')) { - let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ','))) - throw new Error(errorMsg) - } - return true - }) - }) - }, - - /** - * Commits uploads changes. - * - * @param {String} msg The commit message - * @return {Promise} Resolve on commit success - */ - commitUploads(msg) { - let self = this - msg = msg || 'Uploads repository sync' - - return self._git.add('uploads').then(() => { - return self._git.commit(msg).catch((err) => { - if (_.includes(err.stdout, 'nothing to commit')) { return true } - }) - }) - }, - - getHistory(entryPath) { - let self = this - let gitFilePath = entryPath + '.md' - - return self._git.exec('log', ['--max-count=25', '--skip=1', '--format=format:%H %h %cI %cE %cN', '--', gitFilePath]).then((cProc) => { - let out = cProc.stdout.toString() - if (_.includes(out, 'fatal')) { - let errorMsg = _.capitalize(_.head(_.split(_.replace(out, 'fatal: ', ''), ','))) - throw new Error(errorMsg) - } - let hist = _.chain(out).split('\n').map(h => { - let hParts = h.split(' ', 4) - let hDate = moment(hParts[2]) - return { - commit: hParts[0], - commitAbbr: hParts[1], - date: hParts[2], - dateFull: hDate.format('LLLL'), - dateCalendar: hDate.calendar(null, { sameElse: 'llll' }), - email: hParts[3], - name: hParts[4] - } - }).value() - return hist - }) - }, - - getHistoryDiff(path, commit, comparewith) { - let self = this - if (!comparewith) { - comparewith = 'HEAD' - } - - return self._git.exec('diff', ['--no-color', `${commit}:${path}.md`, `${comparewith}:${path}.md`]).then((cProc) => { - let out = cProc.stdout.toString() - if (_.startsWith(out, 'fatal: ')) { - throw new Error(out) - } else if (!_.includes(out, 'diff')) { - throw new Error('Unable to query diff data.') - } else { - return out - } - }) - } - -} diff --git a/server/extensions/logging/bugsnag.js b/server/modules/logging/bugsnag.js similarity index 100% rename from server/extensions/logging/bugsnag.js rename to server/modules/logging/bugsnag.js diff --git a/server/extensions/logging/console.js b/server/modules/logging/console.js similarity index 100% rename from server/extensions/logging/console.js rename to server/modules/logging/console.js diff --git a/server/extensions/logging/loggly.js b/server/modules/logging/loggly.js similarity index 100% rename from server/extensions/logging/loggly.js rename to server/modules/logging/loggly.js diff --git a/server/extensions/logging/papertrail.js b/server/modules/logging/papertrail.js similarity index 100% rename from server/extensions/logging/papertrail.js rename to server/modules/logging/papertrail.js diff --git a/server/extensions/logging/rollbar.js b/server/modules/logging/rollbar.js similarity index 100% rename from server/extensions/logging/rollbar.js rename to server/modules/logging/rollbar.js diff --git a/server/extensions/logging/sentry.js b/server/modules/logging/sentry.js similarity index 100% rename from server/extensions/logging/sentry.js rename to server/modules/logging/sentry.js diff --git a/server/modules/markdown.js b/server/modules/markdown.js deleted file mode 100644 index 7e258948..00000000 --- a/server/modules/markdown.js +++ /dev/null @@ -1,417 +0,0 @@ -/* global wiki */ - -const Promise = require('bluebird') -const md = require('markdown-it') -const mdEmoji = require('markdown-it-emoji') -const mdTaskLists = require('markdown-it-task-lists') -const mdAbbr = require('markdown-it-abbr') -const mdAnchor = require('markdown-it-anchor') -const mdFootnote = require('markdown-it-footnote') -const mdExternalLinks = require('markdown-it-external-links') -const mdExpandTabs = require('markdown-it-expand-tabs') -const mdAttrs = require('markdown-it-attrs') -const mdMathjax = require('markdown-it-mathjax')() -const mathjax = require('mathjax-node') -const hljs = require('highlight.js') -const cheerio = require('cheerio') -const _ = require('lodash') -const mdRemove = require('remove-markdown') - -// Load plugins - -var mkdown = md({ - html: true, - // breaks: wiki.config.features.linebreaks, - breaks: true, - linkify: true, - typography: true, - highlight(str, lang) { - if (wiki.config.theme.code.colorize && lang && hljs.getLanguage(lang)) { - try { - return '
' + hljs.highlight(lang, str, true).value + '
' - } catch (err) { - return '
' + _.escape(str) + '
' - } - } - return '
' + _.escape(str) + '
' - } -}) - .use(mdEmoji) - .use(mdTaskLists) - .use(mdAbbr) - .use(mdAnchor, { - slugify: _.kebabCase, - permalink: true, - permalinkClass: 'toc-anchor nc-icon-outline location_bookmark-add', - permalinkSymbol: '', - permalinkBefore: true - }) - .use(mdFootnote) - .use(mdExternalLinks, { - externalClassName: 'external-link', - internalClassName: 'internal-link' - }) - .use(mdExpandTabs, { - tabWidth: 4 - }) - .use(mdAttrs) - -if (wiki.config.features.mathjax) { - mkdown.use(mdMathjax) -} - -// Rendering rules - -mkdown.renderer.rules.emoji = function (token, idx) { - return '' -} - -// Video rules - -const videoRules = [ - { - selector: 'a.youtube', - regexp: new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|&v(?:i)?=))([^#&?]*).*/i), - output: '' - }, - { - selector: 'a.vimeo', - regexp: new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/i), - output: '' - }, - { - selector: 'a.dailymotion', - regexp: new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/i), - output: '' - }, - { - selector: 'a.video', - regexp: false, - output: '' - } -] - -// Regex - -const textRegex = new RegExp('\\b[a-z0-9-.,' + wiki.data.regex.cjk + wiki.data.regex.arabic + ']+\\b', 'g') -const mathRegex = [ - { - format: 'TeX', - regex: /\\\[([\s\S]*?)\\\]/g - }, - { - format: 'inline-TeX', - regex: /\\\((.*?)\\\)/g - }, - { - format: 'MathML', - regex: //g - } -] - -// MathJax - -mathjax.config({ - MathJax: { - jax: ['input/TeX', 'input/MathML', 'output/SVG'], - extensions: ['tex2jax.js', 'mml2jax.js'], - TeX: { - extensions: ['AMSmath.js', 'AMSsymbols.js', 'noErrors.js', 'noUndefined.js'] - }, - SVG: { - scale: 120, - font: 'STIX-Web' - } - } -}) - -/** - * Parse markdown content and build TOC tree - * - * @param {(Function|string)} content Markdown content - * @return {Array} TOC tree - */ -const parseTree = (content) => { - content = content.replace(//g, '') - let tokens = md().parse(content, {}) - let tocArray = [] - - // -> Extract headings and their respective levels - - for (let i = 0; i < tokens.length; i++) { - if (tokens[i].type !== 'heading_close') { - continue - } - - const heading = tokens[i - 1] - const headingclose = tokens[i] - - if (heading.type === 'inline') { - let content = '' - let anchor = '' - if (heading.children && heading.children.length > 0 && heading.children[0].type === 'link_open') { - content = mdRemove(heading.children[1].content) - anchor = _.kebabCase(content) - } else { - content = mdRemove(heading.content) - anchor = _.kebabCase(heading.children.reduce((acc, t) => acc + t.content, '')) - } - - tocArray.push({ - content, - anchor, - level: +headingclose.tag.substr(1, 1) - }) - } - } - - // -> Exclude levels deeper than 2 - - _.remove(tocArray, (n) => { return n.level > 2 }) - - // -> Build tree from flat array - - return _.reduce(tocArray, (tree, v) => { - let treeLength = tree.length - 1 - if (v.level < 2) { - tree.push({ - content: v.content, - anchor: v.anchor, - nodes: [] - }) - } else { - let lastNodeLevel = 1 - let GetNodePath = (startPos) => { - lastNodeLevel++ - if (_.isEmpty(startPos)) { - startPos = 'nodes' - } - if (lastNodeLevel === v.level) { - return startPos - } else { - return GetNodePath(startPos + '[' + (_.at(tree[treeLength], startPos).length - 1) + '].nodes') - } - } - let lastNodePath = GetNodePath() - let lastNode = _.get(tree[treeLength], lastNodePath) - if (lastNode) { - lastNode.push({ - content: v.content, - anchor: v.anchor, - nodes: [] - }) - _.set(tree[treeLength], lastNodePath, lastNode) - } - } - return tree - }, []) -} - -/** - * Parse markdown content to HTML - * - * @param {String} content Markdown content - * @return {Promise} Promise - */ -const parseContent = (content) => { - let cr = cheerio.load(mkdown.render(content)) - - if (cr.root().children().length < 1) { - return '' - } - - // -> Check for empty first element - - let firstElm = cr.root().children().first()[0] - if (firstElm.type === 'tag' && firstElm.name === 'p') { - let firstElmChildren = firstElm.children - if (firstElmChildren.length < 1) { - firstElm.remove() - } else if (firstElmChildren.length === 1 && firstElmChildren[0].type === 'tag' && firstElmChildren[0].name === 'img') { - cr(firstElm).addClass('is-gapless') - } - } - - // -> Remove links in headers - - cr('h1 > a:not(.toc-anchor), h2 > a:not(.toc-anchor), h3 > a:not(.toc-anchor)').each((i, elm) => { - let txtLink = cr(elm).text() - cr(elm).replaceWith(txtLink) - }) - - // -> Re-attach blockquote styling classes to their parents - - cr('blockquote').each((i, elm) => { - if (cr(elm).children().length > 0) { - let bqLastChild = cr(elm).children().last()[0] - let bqLastChildClasses = cr(bqLastChild).attr('class') - if (bqLastChildClasses && bqLastChildClasses.length > 0) { - cr(bqLastChild).removeAttr('class') - cr(elm).addClass(bqLastChildClasses) - } - } - }) - - // -> Enclose content below headers - - cr('h2').each((i, elm) => { - let subH2Content = cr(elm).nextUntil('h1, h2') - cr(elm).after('
') - let subH2Container = cr(elm).next('.indent-h2') - _.forEach(subH2Content, (ch) => { - cr(subH2Container).append(ch) - }) - }) - - cr('h3').each((i, elm) => { - let subH3Content = cr(elm).nextUntil('h1, h2, h3') - cr(elm).after('
') - let subH3Container = cr(elm).next('.indent-h3') - _.forEach(subH3Content, (ch) => { - cr(subH3Container).append(ch) - }) - }) - - // Replace video links with embeds - - _.forEach(videoRules, (vrule) => { - cr(vrule.selector).each((i, elm) => { - let originLink = cr(elm).attr('href') - if (vrule.regexp) { - let vidMatches = originLink.match(vrule.regexp) - if ((vidMatches && _.isArray(vidMatches))) { - vidMatches = _.filter(vidMatches, (f) => { - return f && _.isString(f) - }) - originLink = _.last(vidMatches) - } - } - let processedLink = _.replace(vrule.output, '{0}', originLink) - cr(elm).replaceWith(processedLink) - }) - }) - - // Apply align-center to parent - - cr('img.align-center').each((i, elm) => { - cr(elm).parent().addClass('align-center') - cr(elm).removeClass('align-center') - }) - - // Mathjax Post-processor - - if (wiki.config.features.mathjax) { - return processMathjax(cr.html()) - } else { - return Promise.resolve(cr.html()) - } -} - -/** - * Process MathJax expressions - * - * @param {String} content HTML content - * @returns {Promise} Promise - */ -const processMathjax = (content) => { - let matchStack = [] - let replaceStack = [] - let currentMatch - let mathjaxState = {} - - _.forEach(mathRegex, mode => { - do { - currentMatch = mode.regex.exec(content) - if (currentMatch) { - matchStack.push(currentMatch[0]) - replaceStack.push( - new Promise((resolve, reject) => { - mathjax.typeset({ - math: (mode.format === 'MathML') ? currentMatch[0] : currentMatch[1], - format: mode.format, - speakText: false, - svg: true, - state: mathjaxState, - timeout: 30 * 1000 - }, result => { - if (!result.errors) { - resolve(result.svg) - } else { - resolve(currentMatch[0]) - wiki.logger.warn(result.errors.join(', ')) - } - }) - }) - ) - } - } while (currentMatch) - }) - - return (matchStack.length > 0) ? Promise.all(replaceStack).then(results => { - _.forEach(matchStack, (repMatch, idx) => { - content = content.replace(repMatch, results[idx]) - }) - return content - }) : Promise.resolve(content) -} - -/** - * Parse meta-data tags from content - * - * @param {String} content Markdown content - * @return {Object} Properties found in the content and their values - */ -const parseMeta = (content) => { - let commentMeta = new RegExp('', 'g') - let results = {} - let match - while ((match = commentMeta.exec(content)) !== null) { - results[_.toLower(match[1])] = _.trim(match[2]) - } - - return results -} - -/** - * Strips non-text elements from Markdown content - * - * @param {String} content Markdown-formatted content - * @return {String} Text-only version - */ -const removeMarkdown = (content) => { - return _.join(mdRemove(_.chain(content) - .replace(//g, '') - .replace(/```([^`]|`)+?```/g, '') - .replace(/`[^`]+`/g, '') - .replace(new RegExp('(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?', 'g'), '') - .deburr() - .toLower() - .value() - ).replace(/\r?\n|\r/g, ' ').match(textRegex), ' ') -} - -module.exports = { - - /** - * Parse content and return all data - * - * @param {String} content Markdown-formatted content - * @return {Object} Object containing meta, html and tree data - */ - parse(content) { - return parseContent(content).then(html => { - return { - meta: parseMeta(content), - html, - tree: parseTree(content) - } - }) - }, - - parseContent, - parseMeta, - parseTree, - - removeMarkdown - -} diff --git a/server/extensions/renderer/common/mathjax.js b/server/modules/renderer/common/mathjax.js similarity index 100% rename from server/extensions/renderer/common/mathjax.js rename to server/modules/renderer/common/mathjax.js diff --git a/server/extensions/renderer/markdown/abbreviations.js b/server/modules/renderer/markdown/abbreviations.js similarity index 100% rename from server/extensions/renderer/markdown/abbreviations.js rename to server/modules/renderer/markdown/abbreviations.js diff --git a/server/extensions/renderer/markdown/emoji.js b/server/modules/renderer/markdown/emoji.js similarity index 100% rename from server/extensions/renderer/markdown/emoji.js rename to server/modules/renderer/markdown/emoji.js diff --git a/server/extensions/renderer/markdown/expand-tabs.js b/server/modules/renderer/markdown/expand-tabs.js similarity index 100% rename from server/extensions/renderer/markdown/expand-tabs.js rename to server/modules/renderer/markdown/expand-tabs.js diff --git a/server/extensions/renderer/markdown/footnotes.js b/server/modules/renderer/markdown/footnotes.js similarity index 100% rename from server/extensions/renderer/markdown/footnotes.js rename to server/modules/renderer/markdown/footnotes.js diff --git a/server/extensions/renderer/markdown/mathjax.js b/server/modules/renderer/markdown/mathjax.js similarity index 100% rename from server/extensions/renderer/markdown/mathjax.js rename to server/modules/renderer/markdown/mathjax.js diff --git a/server/extensions/renderer/markdown/tasks-lists.js b/server/modules/renderer/markdown/tasks-lists.js similarity index 100% rename from server/extensions/renderer/markdown/tasks-lists.js rename to server/modules/renderer/markdown/tasks-lists.js diff --git a/server/modules/rights.js b/server/modules/rights.js deleted file mode 100644 index d06de758..00000000 --- a/server/modules/rights.js +++ /dev/null @@ -1,122 +0,0 @@ -'use strict' - -/* global wiki */ - -const _ = require('lodash') - -/** - * Rights - */ -module.exports = { - - guest: { - provider: 'local', - email: 'guest', - name: 'Guest', - password: '', - rights: [ - { - role: 'read', - path: '/', - deny: false, - exact: false - } - ] - }, - - /** - * Initialize Rights module - * - * @return {void} Void - */ - init () { - let self = this - - wiki.db.onReady.then(() => { - wiki.db.User.findOne({ provider: 'local', email: 'guest' }).then((u) => { - if (u) { - self.guest = u - } - }) - }) - }, - - /** - * Check user permissions for this request - * - * @param {object} req The request object - * @return {object} List of permissions for this request - */ - check (req) { - let self = this - - let perm = { - read: false, - write: false, - manage: false - } - let rt = [] - let p = _.chain(req.originalUrl).toLower().trim().value() - - // Load user rights - - if (_.isArray(req.user.rights)) { - rt = req.user.rights - } - - // Check rights - - if (self.checkRole(p, rt, 'admin')) { - perm.read = true - perm.write = true - perm.manage = true - } else if (self.checkRole(p, rt, 'write')) { - perm.read = true - perm.write = true - } else if (self.checkRole(p, rt, 'read')) { - perm.read = true - } - - return perm - }, - - /** - * Check for a specific role based on list of user rights - * - * @param {String} p Base path - * @param {array} rt The user rights - * @param {string} role The minimum role required - * @return {boolean} True if authorized - */ - checkRole (p, rt, role) { - if (_.find(rt, { role: 'admin' })) { return true } - - // Check specific role on path - - let filteredRights = _.filter(rt, (r) => { - if (r.role === role || (r.role === 'write' && role === 'read')) { - if ((!r.exact && _.startsWith(p, r.path)) || (r.exact && p === r.path)) { - return true - } - } - return false - }) - - // Check for deny scenario - - let isValid = false - - if (filteredRights.length > 1) { - isValid = !_.chain(filteredRights).sortBy((r) => { - return r.path.length + ((r.deny) ? 0.5 : 0) - }).last().get('deny').value() - } else if (filteredRights.length === 1 && filteredRights[0].deny === false) { - isValid = true - } - - // Deny by default - - return isValid - } - -} diff --git a/server/modules/search.js b/server/modules/search.js deleted file mode 100644 index 26c17c6a..00000000 --- a/server/modules/search.js +++ /dev/null @@ -1,211 +0,0 @@ -'use strict' - -/* global wiki */ - -const Promise = require('bluebird') -const _ = require('lodash') -// const searchIndex = require('./search-index') -// const stopWord = require('stopword') -const streamToPromise = require('stream-to-promise') -const searchAllowedChars = new RegExp('[^a-z0-9' + wiki.data.regex.cjk + wiki.data.regex.arabic + ' ]', 'g') - -module.exports = { - - _si: null, - _isReady: false, - - /** - * Initialize search index - * - * @return {undefined} Void - */ - init () { - let self = this - self._isReady = new Promise((resolve, reject) => { - /* searchIndex({ - deletable: true, - fieldedSearch: true, - indexPath: 'wiki', - logLevel: 'error', - stopwords: _.get(stopWord, wiki.config.lang, []) - }, (err, si) => { - if (err) { - wiki.logger.error('Failed to initialize search index.', err) - reject(err) - } else { - self._si = Promise.promisifyAll(si) - self._si.flushAsync().then(() => { - wiki.logger.info('Search index flushed and ready.') - resolve(true) - }) - } - }) */ - }) - - return self - }, - - /** - * Add a document to the index - * - * @param {Object} content Document content - * @return {Promise} Promise of the add operation - */ - add (content) { - let self = this - - if (!content.isEntry) { - return Promise.resolve(true) - } - - return self._isReady.then(() => { - return self.delete(content._id).then(() => { - return self._si.concurrentAddAsync({ - fieldOptions: [{ - fieldName: 'entryPath', - searchable: true, - weight: 2 - }, - { - fieldName: 'title', - nGramLength: [1, 2], - searchable: true, - weight: 3 - }, - { - fieldName: 'subtitle', - searchable: true, - weight: 1, - storeable: false - }, - { - fieldName: 'parent', - searchable: false - }, - { - fieldName: 'content', - searchable: true, - weight: 0, - storeable: false - }] - }, [{ - entryPath: content._id, - title: content.title, - subtitle: content.subtitle || '', - parent: content.parent || '', - content: content.text || '' - }]).then(() => { - wiki.logger.log('verbose', 'Entry ' + content._id + ' added/updated to search index.') - return true - }).catch((err) => { - wiki.logger.error(err) - }) - }).catch((err) => { - wiki.logger.error(err) - }) - }) - }, - - /** - * Delete an entry from the index - * - * @param {String} The entry path - * @return {Promise} Promise of the operation - */ - delete (entryPath) { - let self = this - - return self._isReady.then(() => { - return streamToPromise(self._si.search({ - query: [{ - AND: { 'entryPath': [entryPath] } - }] - })).then((results) => { - if (results && results.length > 0) { - let delIds = _.map(results, 'id') - return self._si.delAsync(delIds) - } else { - return true - } - }).catch((err) => { - if (err.type === 'NotFoundError') { - return true - } else { - wiki.logger.error(err) - } - }) - }) - }, - - /** - * Flush the index - * - * @returns {Promise} Promise of the flush operation - */ - flush () { - let self = this - return self._isReady.then(() => { - return self._si.flushAsync() - }) - }, - - /** - * Search the index - * - * @param {Array} terms - * @returns {Promise} Hits and suggestions - */ - find (terms) { - let self = this - terms = _.chain(terms) - .deburr() - .toLower() - .trim() - .replace(searchAllowedChars, ' ') - .value() - let arrTerms = _.chain(terms) - .split(' ') - .filter((f) => { return !_.isEmpty(f) }) - .value() - - return streamToPromise(self._si.search({ - query: [{ - AND: { '*': arrTerms } - }], - pageSize: 10 - })).then((hits) => { - if (hits.length > 0) { - hits = _.map(_.sortBy(hits, ['score']), h => { - return h.document - }) - } - if (hits.length < 5) { - return streamToPromise(self._si.match({ - beginsWith: terms, - threshold: 3, - limit: 5, - type: 'simple' - })).then((matches) => { - return { - match: hits, - suggest: matches - } - }) - } else { - return { - match: hits, - suggest: [] - } - } - }).catch((err) => { - if (err.type === 'NotFoundError') { - return { - match: [], - suggest: [] - } - } else { - wiki.logger.error(err) - } - }) - } -} diff --git a/server/extensions/storage/disk.js b/server/modules/storage/disk.js similarity index 100% rename from server/extensions/storage/disk.js rename to server/modules/storage/disk.js diff --git a/server/extensions/storage/git.js b/server/modules/storage/git.js similarity index 100% rename from server/extensions/storage/git.js rename to server/modules/storage/git.js diff --git a/server/modules/uploads-agent.js b/server/modules/uploads-agent.js deleted file mode 100644 index 6b861733..00000000 --- a/server/modules/uploads-agent.js +++ /dev/null @@ -1,252 +0,0 @@ -'use strict' - -/* global wiki */ - -const path = require('path') -const Promise = require('bluebird') -const fs = Promise.promisifyAll(require('fs-extra')) -const readChunk = require('read-chunk') -const fileType = require('file-type') -const mime = require('mime-types') -const crypto = require('crypto') -const chokidar = require('chokidar') -const jimp = require('jimp') -const imageSize = Promise.promisify(require('image-size')) -const _ = require('lodash') - -/** - * Uploads - Agent - */ -module.exports = { - - _uploadsPath: './repo/uploads', - _uploadsThumbsPath: './data/thumbs', - - _watcher: null, - - /** - * Initialize Uploads model - * - * @return {Object} Uploads model instance - */ - init () { - let self = this - - self._uploadsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, 'uploads') - self._uploadsThumbsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'thumbs') - - return self - }, - - /** - * Watch the uploads folder for changes - * - * @return {Void} Void - */ - watch () { - let self = this - - self._watcher = chokidar.watch(self._uploadsPath, { - persistent: true, - ignoreInitial: true, - cwd: self._uploadsPath, - depth: 1, - awaitWriteFinish: true - }) - - // -> Add new upload file - - self._watcher.on('add', (p) => { - let pInfo = self.parseUploadsRelPath(p) - return self.processFile(pInfo.folder, pInfo.filename).then((mData) => { - return wiki.db.UplFile.findByIdAndUpdate(mData._id, mData, { upsert: true }) - }).then(() => { - return wiki.git.commitUploads(wiki.lang.t('git:uploaded', { path: p })) - }) - }) - - // -> Remove upload file - - self._watcher.on('unlink', (p) => { - return wiki.git.commitUploads(wiki.lang.t('git:deleted', { path: p })) - }) - }, - - /** - * Initial Uploads scan - * - * @return {Promise} Promise of the scan operation - */ - initialScan () { - let self = this - - return fs.readdirAsync(self._uploadsPath).then((ls) => { - // Get all folders - - return Promise.map(ls, (f) => { - return fs.statAsync(path.join(self._uploadsPath, f)).then((s) => { return { filename: f, stat: s } }) - }).filter((s) => { return s.stat.isDirectory() }).then((arrDirs) => { - let folderNames = _.map(arrDirs, 'filename') - folderNames.unshift('') - - // Add folders to DB - - return wiki.db.UplFolder.remove({}).then(() => { - return wiki.db.UplFolder.insertMany(_.map(folderNames, (f) => { - return { - _id: 'f:' + f, - name: f - } - })) - }).then(() => { - // Travel each directory and scan files - - let allFiles = [] - - return Promise.map(folderNames, (fldName) => { - let fldPath = path.join(self._uploadsPath, fldName) - return fs.readdirAsync(fldPath).then((fList) => { - return Promise.map(fList, (f) => { - return wiki.upl.processFile(fldName, f).then((mData) => { - if (mData) { - allFiles.push(mData) - } - return true - }) - }, {concurrency: 3}) - }) - }, {concurrency: 1}).finally(() => { - // Add files to DB - - return wiki.db.UplFile.remove({}).then(() => { - if (_.isArray(allFiles) && allFiles.length > 0) { - return wiki.db.UplFile.insertMany(allFiles) - } else { - return true - } - }) - }) - }) - }) - }).then(() => { - // Watch for new changes - - return wiki.upl.watch() - }) - }, - - /** - * Parse relative Uploads path - * - * @param {String} f Relative Uploads path - * @return {Object} Parsed path (folder and filename) - */ - parseUploadsRelPath (f) { - let fObj = path.parse(f) - return { - folder: fObj.dir, - filename: fObj.base - } - }, - - /** - * Get metadata from file and generate thumbnails if necessary - * - * @param {String} fldName The folder name - * @param {String} f The filename - * @return {Promise} Promise of the file metadata - */ - processFile (fldName, f) { - let self = this - - let fldPath = path.join(self._uploadsPath, fldName) - let fPath = path.join(fldPath, f) - let fPathObj = path.parse(fPath) - let fUid = crypto.createHash('md5').update(fldName + '/' + f).digest('hex') - - return fs.statAsync(fPath).then((s) => { - if (!s.isFile()) { return false } - - // Get MIME info - - let mimeInfo = fileType(readChunk.sync(fPath, 0, 262)) - if (_.isNil(mimeInfo)) { - mimeInfo = { - mime: mime.lookup(fPathObj.ext) || 'application/octet-stream' - } - } - - // Images - - if (s.size < 3145728) { // ignore files larger than 3MB - if (_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/bmp'], mimeInfo.mime)) { - return self.getImageSize(fPath).then((mImgSize) => { - let cacheThumbnailPath = path.parse(path.join(self._uploadsThumbsPath, fUid + '.png')) - let cacheThumbnailPathStr = path.format(cacheThumbnailPath) - - let mData = { - _id: fUid, - category: 'image', - mime: mimeInfo.mime, - extra: mImgSize, - folder: 'f:' + fldName, - filename: f, - basename: fPathObj.name, - filesize: s.size - } - - // Generate thumbnail - - return fs.statAsync(cacheThumbnailPathStr).then((st) => { - return st.isFile() - }).catch((err) => { // eslint-disable-line handle-callback-err - return false - }).then((thumbExists) => { - return (thumbExists) ? mData : fs.ensureDirAsync(cacheThumbnailPath.dir).then(() => { - return self.generateThumbnail(fPath, cacheThumbnailPathStr) - }).return(mData) - }) - }) - } - } - - // Other Files - - return { - _id: fUid, - category: 'binary', - mime: mimeInfo.mime, - folder: 'f:' + fldName, - filename: f, - basename: fPathObj.name, - filesize: s.size - } - }) - }, - - /** - * Generate thumbnail of image - * - * @param {String} sourcePath The source path - * @param {String} destPath The destination path - * @return {Promise} Promise returning the resized image info - */ - generateThumbnail (sourcePath, destPath) { - return jimp.read(sourcePath).then(img => { - return img - .contain(150, 150) - .write(destPath) - }) - }, - - /** - * Gets the image dimensions. - * - * @param {String} sourcePath The source path - * @return {Object} The image dimensions. - */ - getImageSize (sourcePath) { - return imageSize(sourcePath) - } - -} diff --git a/server/modules/uploads.js b/server/modules/uploads.js deleted file mode 100644 index f56ecab3..00000000 --- a/server/modules/uploads.js +++ /dev/null @@ -1,281 +0,0 @@ -'use strict' - -/* global wiki */ - -const path = require('path') -const Promise = require('bluebird') -const fs = Promise.promisifyAll(require('fs-extra')) -const request = require('request') -const url = require('url') -const crypto = require('crypto') -const _ = require('lodash') - -var regFolderName = new RegExp('^[a-z0-9][a-z0-9-]*[a-z0-9]$') -const maxDownloadFileSize = 3145728 // 3 MB - -/** - * Uploads - */ -module.exports = { - - _uploadsPath: './repo/uploads', - _uploadsThumbsPath: './data/thumbs', - - /** - * Initialize Local Data Storage model - * - * @return {Object} Uploads model instance - */ - init () { - this._uploadsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.repo, 'uploads') - this._uploadsThumbsPath = path.resolve(wiki.ROOTPATH, wiki.config.paths.data, 'thumbs') - - return this - }, - - /** - * Gets the thumbnails folder path. - * - * @return {String} The thumbs path. - */ - getThumbsPath () { - return this._uploadsThumbsPath - }, - - /** - * Gets the uploads folders. - * - * @return {Array} The uploads folders. - */ - getUploadsFolders () { - return wiki.db.Folder.find({}, 'name').sort('name').exec().then((results) => { - return (results) ? _.map(results, 'name') : [{ name: '' }] - }) - }, - - /** - * Creates an uploads folder. - * - * @param {String} folderName The folder name - * @return {Promise} Promise of the operation - */ - createUploadsFolder (folderName) { - let self = this - - folderName = _.kebabCase(_.trim(folderName)) - - if (_.isEmpty(folderName) || !regFolderName.test(folderName)) { - return Promise.resolve(self.getUploadsFolders()) - } - - return fs.ensureDirAsync(path.join(self._uploadsPath, folderName)).then(() => { - return wiki.db.UplFolder.findOneAndUpdate({ - _id: 'f:' + folderName - }, { - name: folderName - }, { - upsert: true - }) - }).then(() => { - return self.getUploadsFolders() - }) - }, - - /** - * Check if folder is valid and exists - * - * @param {String} folderName The folder name - * @return {Boolean} True if valid - */ - validateUploadsFolder (folderName) { - return wiki.db.UplFolder.findOne({ name: folderName }).then((f) => { - return (f) ? path.resolve(this._uploadsPath, folderName) : false - }) - }, - - /** - * Adds one or more uploads files. - * - * @param {Array} arrFiles The uploads files - * @return {Void} Void - */ - addUploadsFiles (arrFiles) { - if (_.isArray(arrFiles) || _.isPlainObject(arrFiles)) { - // this._uploadswiki.Db.Files.insert(arrFiles); - } - }, - - /** - * Gets the uploads files. - * - * @param {String} cat Category type - * @param {String} fld Folder - * @return {Array} The files matching the query - */ - getUploadsFiles (cat, fld) { - return wiki.db.UplFile.find({ - category: cat, - folder: 'f:' + fld - }).sort('filename').exec() - }, - - /** - * Deletes an uploads file. - * - * @param {string} uid The file unique ID - * @return {Promise} Promise of the operation - */ - deleteUploadsFile (uid) { - let self = this - - return wiki.db.UplFile.findOneAndRemove({ _id: uid }).then((f) => { - if (f) { - return self.deleteUploadsFileTry(f, 0) - } - return true - }) - }, - - deleteUploadsFileTry (f, attempt) { - let self = this - - let fFolder = (f.folder && f.folder !== 'f:') ? f.folder.slice(2) : './' - - return Promise.join( - fs.removeAsync(path.join(self._uploadsThumbsPath, f._id + '.png')), - fs.removeAsync(path.resolve(self._uploadsPath, fFolder, f.filename)) - ).catch((err) => { - if (err.code === 'EBUSY' && attempt < 5) { - return Promise.delay(100).then(() => { - return self.deleteUploadsFileTry(f, attempt + 1) - }) - } else { - wiki.logger.warn('Unable to delete uploads file ' + f.filename + '. File is locked by another process and multiple attempts failed.') - return true - } - }) - }, - - /** - * Downloads a file from url. - * - * @param {String} fFolder The folder - * @param {String} fUrl The full URL - * @return {Promise} Promise of the operation - */ - downloadFromUrl (fFolder, fUrl) { - let fUrlObj = url.parse(fUrl) - let fUrlFilename = _.last(_.split(fUrlObj.pathname, '/')) - let destFolder = _.chain(fFolder).trim().toLower().value() - - return wiki.upl.validateUploadsFolder(destFolder).then((destFolderPath) => { - if (!destFolderPath) { - return Promise.reject(new Error(wiki.lang.t('errors:invalidfolder'))) - } - - return wiki.disk.validateUploadsFilename(fUrlFilename, destFolder).then((destFilename) => { - let destFilePath = path.resolve(destFolderPath, destFilename) - - return new Promise((resolve, reject) => { - let rq = request({ - url: fUrl, - method: 'GET', - followRedirect: true, - maxRedirects: 5, - timeout: 10000 - }) - - let destFileStream = fs.createWriteStream(destFilePath) - let curFileSize = 0 - - rq.on('data', (data) => { - curFileSize += data.length - if (curFileSize > maxDownloadFileSize) { - rq.abort() - destFileStream.destroy() - fs.remove(destFilePath) - reject(new Error(wiki.lang.t('errors:remotetoolarge'))) - } - }).on('error', (err) => { - destFileStream.destroy() - fs.remove(destFilePath) - reject(err) - }) - - destFileStream.on('finish', () => { - resolve(true) - }) - - rq.pipe(destFileStream) - }) - }) - }) - }, - - /** - * Move/Rename a file - * - * @param {String} uid The file ID - * @param {String} fld The destination folder - * @param {String} nFilename The new filename (optional) - * @return {Promise} Promise of the operation - */ - moveUploadsFile (uid, fld, nFilename) { - let self = this - - return wiki.db.UplFolder.finwiki.dById('f:' + fld).then((folder) => { - if (folder) { - return wiki.db.UplFile.finwiki.dById(uid).then((originFile) => { - // -> Check if rename is valid - - let nameCheck = null - if (nFilename) { - let originFileObj = path.parse(originFile.filename) - nameCheck = wiki.disk.validateUploadsFilename(nFilename + originFileObj.ext, folder.name) - } else { - nameCheck = Promise.resolve(originFile.filename) - } - - return nameCheck.then((destFilename) => { - let originFolder = (originFile.folder && originFile.folder !== 'f:') ? originFile.folder.slice(2) : './' - let sourceFilePath = path.resolve(self._uploadsPath, originFolder, originFile.filename) - let destFilePath = path.resolve(self._uploadsPath, folder.name, destFilename) - let preMoveOps = [] - - // -> Check for invalid operations - - if (sourceFilePath === destFilePath) { - return Promise.reject(new Error(wiki.lang.t('errors:invalidoperation'))) - } - - // -> Delete wiki.DB entry - - preMoveOps.push(wiki.db.UplFile.finwiki.dByIdAndRemove(uid)) - - // -> Move thumbnail ahead to avoid re-generation - - if (originFile.category === 'image') { - let fUid = crypto.createHash('md5').update(folder.name + '/' + destFilename).digest('hex') - let sourceThumbPath = path.resolve(self._uploadsThumbsPath, originFile._id + '.png') - let destThumbPath = path.resolve(self._uploadsThumbsPath, fUid + '.png') - preMoveOps.push(fs.moveAsync(sourceThumbPath, destThumbPath)) - } else { - preMoveOps.push(Promise.resolve(true)) - } - - // -> Proceed to move actual file - - return Promise.all(preMoveOps).then(() => { - return fs.moveAsync(sourceFilePath, destFilePath, { - clobber: false - }) - }) - }) - }) - } else { - return Promise.reject(new Error(wiki.lang.t('errors:invaliddestfolder'))) - } - }) - } - -} diff --git a/server/setup.js b/server/setup.js index 82366382..e022cad7 100644 --- a/server/setup.js +++ b/server/setup.js @@ -8,7 +8,7 @@ module.exports = () => { title: 'Wiki.js' } - wiki.system = require('./modules/system') + wiki.system = require('./core/system') // ---------------------------------------- // Load modules @@ -62,13 +62,8 @@ module.exports = () => { // ---------------------------------------- if (global.DEV) { - const webpackDevMiddleware = require('webpack-dev-middleware') - const webpackHotMiddleware = require('webpack-hot-middleware') - app.use(webpackDevMiddleware(global.WP, { - publicPath: global.WPCONFIG.output.publicPath, - logger: wiki.logger - })) - app.use(webpackHotMiddleware(global.WP)) + app.use(global.WP_DEV.devMiddleware) + app.use(global.WP_DEV.hotMiddleware) } // ---------------------------------------- @@ -77,7 +72,7 @@ module.exports = () => { app.get('*', async (req, res) => { let packageObj = await fs.readJson(path.join(wiki.ROOTPATH, 'package.json')) - res.render('pages/setup', { + res.render('main/setup', { packageObj, telemetryClientID: wiki.telemetry.cid }) diff --git a/server/views/pages/admin/_layout.pug b/server/views/admin/_layout.pug similarity index 100% rename from server/views/pages/admin/_layout.pug rename to server/views/admin/_layout.pug diff --git a/server/views/pages/admin/profile.pug b/server/views/admin/profile.pug similarity index 100% rename from server/views/pages/admin/profile.pug rename to server/views/admin/profile.pug diff --git a/server/views/pages/admin/settings.pug b/server/views/admin/settings.pug similarity index 100% rename from server/views/pages/admin/settings.pug rename to server/views/admin/settings.pug diff --git a/server/views/pages/admin/stats.pug b/server/views/admin/stats.pug similarity index 100% rename from server/views/pages/admin/stats.pug rename to server/views/admin/stats.pug diff --git a/server/views/pages/admin/system.pug b/server/views/admin/system.pug similarity index 100% rename from server/views/pages/admin/system.pug rename to server/views/admin/system.pug diff --git a/server/views/pages/admin/theme.pug b/server/views/admin/theme.pug similarity index 100% rename from server/views/pages/admin/theme.pug rename to server/views/admin/theme.pug diff --git a/server/views/pages/admin/users-edit.pug b/server/views/admin/users-edit.pug similarity index 100% rename from server/views/pages/admin/users-edit.pug rename to server/views/admin/users-edit.pug diff --git a/server/views/pages/admin/users.pug b/server/views/admin/users.pug similarity index 100% rename from server/views/pages/admin/users.pug rename to server/views/admin/users.pug diff --git a/server/views/error-forbidden.pug b/server/views/errors/error-forbidden.pug similarity index 100% rename from server/views/error-forbidden.pug rename to server/views/errors/error-forbidden.pug diff --git a/server/views/error-notexist.pug b/server/views/errors/error-notexist.pug similarity index 100% rename from server/views/error-notexist.pug rename to server/views/errors/error-notexist.pug diff --git a/server/views/layout.pug b/server/views/layout.pug index 1d704f7e..21fbfcc6 100644 --- a/server/views/layout.pug +++ b/server/views/layout.pug @@ -2,11 +2,10 @@ extends ./master.pug block body body - #app.has-stickynav(class=['is-primary-' + appconfig.theme.primary, 'is-alternate-' + appconfig.theme.alt]) - include ./common/header.pug - alert + #app + navigator main block content - include ./common/footer.pug + footer block outside diff --git a/server/views/pages/all.pug b/server/views/main/all.pug similarity index 100% rename from server/views/pages/all.pug rename to server/views/main/all.pug diff --git a/server/views/pages/create.pug b/server/views/main/create.pug similarity index 100% rename from server/views/pages/create.pug rename to server/views/main/create.pug diff --git a/server/views/pages/edit.pug b/server/views/main/edit.pug similarity index 100% rename from server/views/pages/edit.pug rename to server/views/main/edit.pug diff --git a/server/views/pages/history.pug b/server/views/main/history.pug similarity index 100% rename from server/views/pages/history.pug rename to server/views/main/history.pug diff --git a/server/views/pages/login.pug b/server/views/main/login.pug similarity index 100% rename from server/views/pages/login.pug rename to server/views/main/login.pug diff --git a/server/views/pages/setup.pug b/server/views/main/setup.pug similarity index 100% rename from server/views/pages/setup.pug rename to server/views/main/setup.pug diff --git a/server/views/pages/source.pug b/server/views/main/source.pug similarity index 100% rename from server/views/pages/source.pug rename to server/views/main/source.pug diff --git a/server/views/pages/view.pug b/server/views/main/view.pug similarity index 100% rename from server/views/pages/view.pug rename to server/views/main/view.pug diff --git a/server/views/main/welcome.pug b/server/views/main/welcome.pug new file mode 100644 index 00000000..b6fffa00 --- /dev/null +++ b/server/views/main/welcome.pug @@ -0,0 +1,10 @@ +extends ../master.pug + +block body + body + #app.is-fullscreen + .onboarding + img(src='/svg/logo-wikijs.svg', alt='Wiki.js') + h1= t('welcome.title') + h2= t('welcome.subtitle') + a.button.is-blue(href='/create/home')= t('welcome.createhome') diff --git a/server/views/pages/welcome.pug b/server/views/pages/welcome.pug deleted file mode 100644 index 86b9395b..00000000 --- a/server/views/pages/welcome.pug +++ /dev/null @@ -1,11 +0,0 @@ -extends ../layout.pug - -block rootNavCenter - -block content - .container - .welcome - img(src='/images/logo.png', alt='Wiki.js') - h1= t('welcome.title') - h2= t('welcome.subtitle') - a.button.is-indigo(href='/create/home')= t('welcome.createhome') diff --git a/server/worker.js b/server/worker.js index 2c25522e..42c8b24f 100644 --- a/server/worker.js +++ b/server/worker.js @@ -1,7 +1,7 @@ -/* global wiki */ - const Promise = require('bluebird') +/* global wiki */ + module.exports = Promise.join( wiki.db.onReady, wiki.configSvc.loadFromDb(['features', 'git', 'logging', 'site', 'uploads']) @@ -16,11 +16,7 @@ module.exports = Promise.join( // Load global modules // ---------------------------------------- - // wiki.upl = require('./modules/uploads-agent').init() - // wiki.git = require('./modules/git').init() - // wiki.entries = require('./modules/entries').init() wiki.lang = require('i18next') - wiki.mark = require('./modules/markdown') // ---------------------------------------- // Localization Engine @@ -36,7 +32,7 @@ module.exports = Promise.join( lng: wiki.config.lang, fallbackLng: 'en', backend: { - loadPath: path.join(wiki.SERVERPATH, 'locales/{{lng}}/{{ns}}.json') + loadPath: path.join(wiki.SERVERPATH, 'locales/{{lng}}/{{ns}}.yml') } }) diff --git a/yarn.lock b/yarn.lock index a707fa5c..ad70d711 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2101,12 +2101,6 @@ child-process-promise@2.2.1: node-version "^1.0.0" promise-polyfill "^6.0.1" -child-process-promise@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/child-process-promise/-/child-process-promise-1.1.0.tgz#131e01a705f15ed4a05d554dd5e032e52612cf30" - dependencies: - q "^1.1.2" - chokidar@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.0.tgz#6686313c541d3274b2a5c01233342037948c911b" @@ -3086,10 +3080,6 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - detect-newline@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" @@ -4171,16 +4161,6 @@ fs-extra@5.0.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@~0.26.2: - version "0.26.7" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.26.7.tgz#9ae1fdd94897798edab76d0918cf42d0c3184fa9" - dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - klaw "^1.0.0" - path-is-absolute "^1.0.0" - rimraf "^2.2.8" - fs-readdir-recursive@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz#8cd1745c8b4f8a29c8caec392476921ba195f560" @@ -4310,12 +4290,6 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -git-wrapper2-promise@0.2.9: - version "0.2.9" - resolved "https://registry.yarnpkg.com/git-wrapper2-promise/-/git-wrapper2-promise-0.2.9.tgz#2c781e26a16246b05eba45fa17df687403bbfd7d" - dependencies: - child-process-promise "^1.1.0" - "gkt@https://tgz.pm2.io/gkt-1.0.0.tgz": version "1.0.0" resolved "https://tgz.pm2.io/gkt-1.0.0.tgz#405502b007f319c3f47175c4474527300f2ab5ad" @@ -4594,7 +4568,7 @@ hashring@0.0.x: bisection "" simple-lru-cache "0.0.x" -hawk@3.1.3, hawk@~3.1.3: +hawk@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" dependencies: @@ -5791,12 +5765,6 @@ json5@^0.5.0, json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" -jsonfile@^2.1.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" - optionalDependencies: - graceful-fs "^4.1.6" - jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -5895,12 +5863,6 @@ klaw@2.1.1: dependencies: graceful-fs "^4.1.9" -klaw@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" - optionalDependencies: - graceful-fs "^4.1.9" - lazy-cache@^0.2.3: version "0.2.7" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65" @@ -6646,10 +6608,6 @@ name-all-modules-plugin@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/name-all-modules-plugin/-/name-all-modules-plugin-1.0.1.tgz#0abfb6ad835718b9fb4def0674e06657a954375c" -nan@^2.2.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" - nan@^2.3.0, nan@^2.3.2, nan@^2.3.3: version "2.6.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45" @@ -6711,7 +6669,7 @@ node-fingerprint@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/node-fingerprint/-/node-fingerprint-0.0.2.tgz#31cbabeb71a67ae7dd5a7dc042e51c3c75868501" -node-gyp@^3.3.1, node-gyp@^3.5.0: +node-gyp@^3.3.1: version "3.6.2" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.6.2.tgz#9bfbe54562286284838e750eac05295853fa1c60" dependencies: @@ -6784,22 +6742,6 @@ node-pre-gyp@^0.6.36: tar "^2.2.1" tar-pack "^3.4.0" -node-pre-gyp@~0.6.32: - version "0.6.39" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" - dependencies: - detect-libc "^1.0.2" - hawk "3.1.3" - mkdirp "^0.5.1" - nopt "^4.0.1" - npmlog "^4.0.2" - rc "^1.1.7" - request "2.81.0" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^2.2.1" - tar-pack "^3.4.0" - node-sass@4.7.2: version "4.7.2" resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.7.2.tgz#9366778ba1469eb01438a9e8592f4262bcb6794e" @@ -6828,23 +6770,6 @@ node-version@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-version/-/node-version-1.0.0.tgz#1b9b9584a9a7f7a6123f215cd14a652bf21ab19e" -nodegit-promise@~4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/nodegit-promise/-/nodegit-promise-4.0.0.tgz#5722b184f2df7327161064a791d2e842c9167b34" - dependencies: - asap "~2.0.3" - -nodegit@0.20.3: - version "0.20.3" - resolved "https://registry.yarnpkg.com/nodegit/-/nodegit-0.20.3.tgz#a44172db08c833cdcb1ab9bbc2c832ce05898815" - dependencies: - fs-extra "~0.26.2" - lodash "^4.13.1" - nan "^2.2.0" - node-gyp "^3.5.0" - node-pre-gyp "~0.6.32" - promisify-node "~0.3.0" - nopt@1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" @@ -8215,12 +8140,6 @@ promise@7.x, promise@^7.0.1: dependencies: asap "~2.0.3" -promisify-node@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/promisify-node/-/promisify-node-0.3.0.tgz#b4b55acf90faa7d2b8b90ca396899086c03060cf" - dependencies: - nodegit-promise "~4.0.0" - promptly@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/promptly/-/promptly-2.2.0.tgz#2a13fa063688a2a5983b161fff0108a07d26fc74" @@ -8772,7 +8691,7 @@ request-promise@4.2.2: stealthy-require "^1.1.0" tough-cookie ">=2.3.3" -request@2, request@2.81.0, request@^2.67.0, request@^2.79.0, request@^2.81.0: +request@2, request@^2.67.0, request@^2.79.0, request@^2.81.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" dependencies: