refactor: project cleanup + onboarding page

This commit is contained in:
NGPixel committed 2018-02-04 00:53:13 -05:00
1 parent 1d9f057f38
commit 3471a7a6f9
80 files changed
+174 -2698

No files matched your search

+6 -5
View File
@@ -353,6 +353,7 @@
/* global siteConfig */ /* global siteConfig */
import axios from 'axios' import axios from 'axios'
import _ from 'lodash'
export default { export default {
props: { props: {
@@ -491,7 +492,7 @@ export default {
results: [] results: []
} }
this.$helpers._.delay(() => { _.delay(() => {
axios.post('/syscheck', self.conf).then(resp => { axios.post('/syscheck', self.conf).then(resp => {
if (resp.data.ok === true) { if (resp.data.ok === true) {
self.syscheck.ok = true self.syscheck.ok = true
@@ -517,9 +518,9 @@ export default {
}, },
proceedToConsiderations: function (ev) { proceedToConsiderations: function (ev) {
this.considerations = { this.considerations = {
https: !this.$helpers._.startsWith(this.conf.host, 'https'), https: false,
port: false, // TODO port: false, // TODO
localhost: this.$helpers._.includes(this.conf.host, 'localhost') localhost: false
} }
this.state = 'considerations' this.state = 'considerations'
this.loading = false this.loading = false
@@ -542,7 +543,7 @@ export default {
error: '' error: ''
} }
this.$helpers._.delay(() => { _.delay(() => {
axios.post('/gitcheck', self.conf).then(resp => { axios.post('/gitcheck', self.conf).then(resp => {
if (resp.data.ok === true) { if (resp.data.ok === true) {
self.gitcheck.ok = true self.gitcheck.ok = true
@@ -587,7 +588,7 @@ export default {
redirectUrl: '' redirectUrl: ''
} }
this.$helpers._.delay(() => { _.delay(() => {
axios.post('/finalize', self.conf).then(resp => { axios.post('/finalize', self.conf).then(resp => {
if (resp.data.ok === true) { if (resp.data.ok === true) {
self.$helpers._.delay(() => { self.$helpers._.delay(() => {
+2
View File
@@ -30,6 +30,8 @@
@import 'components/editor'; @import 'components/editor';
@import 'pages/welcome';
@import 'layout/_header'; @import 'layout/_header';
@import 'layout/_loader'; @import 'layout/_loader';
@import 'layout/_rtl'; @import 'layout/_rtl';
+4 -22
View File
@@ -16,7 +16,6 @@ #app {
min-height: 100%; min-height: 100%;
&.is-fullscreen { &.is-fullscreen {
//width: 100vw;
height: 100vh; height: 100vh;
} }
} }
@@ -27,7 +26,10 @@ body {
} }
main { 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 { a {
@@ -42,28 +44,8 @@ a {
// Container // Container
.has-stickynav {
padding-top: 50px;
}
.container { .container {
position: relative; position: relative;
@include desktop {
margin: 0 auto;
max-width: 960px;
// Modifiers
&.is-fluid {
margin: 0;
max-width: none;
}
}
@include widescreen {
max-width: 1200px;
}
} }
.content { .content {
+42
View File
@@ -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;
}
}
-2
View File
@@ -65,7 +65,6 @@
"filesize.js": "1.0.2", "filesize.js": "1.0.2",
"follow-redirects": "1.4.1", "follow-redirects": "1.4.1",
"fs-extra": "5.0.0", "fs-extra": "5.0.0",
"git-wrapper2-promise": "0.2.9",
"graphql": "0.12.3", "graphql": "0.12.3",
"graphql-tools": "2.19.0", "graphql-tools": "2.19.0",
"highlight.js": "9.12.0", "highlight.js": "9.12.0",
@@ -96,7 +95,6 @@
"mongodb": "3.0.1", "mongodb": "3.0.1",
"multer": "1.3.0", "multer": "1.3.0",
"node-2fa": "1.1.2", "node-2fa": "1.1.2",
"nodegit": "0.20.3",
"ora": "1.3.0", "ora": "1.3.0",
"passport": "0.4.0", "passport": "0.4.0",
"passport-auth0": "0.6.1", "passport-auth0": "0.6.1",
+1 -1
View File
@@ -34,7 +34,7 @@ const bruteforce = new ExpressBrute(EBstore, {
* Login form * Login form
*/ */
router.get('/login', function (req, res, next) { router.get('/login', function (req, res, next) {
res.render('pages/login') res.render('main/login')
}) })
router.post('/login', bruteforce.prevent, function (req, res, next) { router.post('/login', bruteforce.prevent, function (req, res, next) {
+1 -306
View File
@@ -1,316 +1,11 @@
'use strict'
/* global entries, git, lang, winston */
const express = require('express') const express = require('express')
const router = express.Router() 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 * View document
*/ */
router.get('/*', (req, res, next) => { router.get('/*', (req, res, next) => {
let safePath = entryHelper.parsePath(req.path) res.render('main/welcome')
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
})
})
}) })
module.exports = router module.exports = router
@@ -33,7 +33,7 @@ module.exports = {
_.forOwn(_.omitBy(wiki.config.auth.strategies, s => s.enabled === false), (strategyConfig, strategyKey) => { _.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` 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 { try {
strategy.init(passport, strategyConfig) strategy.init(passport, strategyConfig)
} catch (err) { } catch (err) {
@@ -1,10 +1,10 @@
/* global wiki */
const fs = require('fs')
const yaml = require('js-yaml')
const _ = require('lodash') const _ = require('lodash')
const path = require('path')
const cfgHelper = require('../helpers/config') const cfgHelper = require('../helpers/config')
const fs = require('fs')
const path = require('path')
const yaml = require('js-yaml')
/* global wiki */
module.exports = { module.exports = {
/** /**
+41 -45
View File
@@ -1,54 +1,52 @@
/* global wiki */ const _ = require('lodash')
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const _ = require('lodash')
const Promise = require('bluebird') const Promise = require('bluebird')
const Sequelize = require('sequelize') const Sequelize = require('sequelize')
const Op = Sequelize.Op
/* global wiki */
const operatorsAliases = { const operatorsAliases = {
$eq: Op.eq, $eq: Sequelize.Op.eq,
$ne: Op.ne, $ne: Sequelize.Op.ne,
$gte: Op.gte, $gte: Sequelize.Op.gte,
$gt: Op.gt, $gt: Sequelize.Op.gt,
$lte: Op.lte, $lte: Sequelize.Op.lte,
$lt: Op.lt, $lt: Sequelize.Op.lt,
$not: Op.not, $not: Sequelize.Op.not,
$in: Op.in, $in: Sequelize.Op.in,
$notIn: Op.notIn, $notIn: Sequelize.Op.notIn,
$is: Op.is, $is: Sequelize.Op.is,
$like: Op.like, $like: Sequelize.Op.like,
$notLike: Op.notLike, $notLike: Sequelize.Op.notLike,
$iLike: Op.iLike, $iLike: Sequelize.Op.iLike,
$notILike: Op.notILike, $notILike: Sequelize.Op.notILike,
$regexp: Op.regexp, $regexp: Sequelize.Op.regexp,
$notRegexp: Op.notRegexp, $notRegexp: Sequelize.Op.notRegexp,
$iRegexp: Op.iRegexp, $iRegexp: Sequelize.Op.iRegexp,
$notIRegexp: Op.notIRegexp, $notIRegexp: Sequelize.Op.notIRegexp,
$between: Op.between, $between: Sequelize.Op.between,
$notBetween: Op.notBetween, $notBetween: Sequelize.Op.notBetween,
$overlap: Op.overlap, $overlap: Sequelize.Op.overlap,
$contains: Op.contains, $contains: Sequelize.Op.contains,
$contained: Op.contained, $contained: Sequelize.Op.contained,
$adjacent: Op.adjacent, $adjacent: Sequelize.Op.adjacent,
$strictLeft: Op.strictLeft, $strictLeft: Sequelize.Op.strictLeft,
$strictRight: Op.strictRight, $strictRight: Sequelize.Op.strictRight,
$noExtendRight: Op.noExtendRight, $noExtendRight: Sequelize.Op.noExtendRight,
$noExtendLeft: Op.noExtendLeft, $noExtendLeft: Sequelize.Op.noExtendLeft,
$and: Op.and, $and: Sequelize.Op.and,
$or: Op.or, $or: Sequelize.Op.or,
$any: Op.any, $any: Sequelize.Op.any,
$all: Op.all, $all: Sequelize.Op.all,
$values: Op.values, $values: Sequelize.Op.values,
$col: Op.col $col: Sequelize.Op.col
} }
/** /**
* PostgreSQL DB module * PostgreSQL DB module
*/ */
module.exports = { module.exports = {
Sequelize, Sequelize,
Op: Sequelize.Op, Op: Sequelize.Op,
@@ -59,12 +57,11 @@ module.exports = {
*/ */
init() { init() {
let self = this let self = this
let dbModelsPath = path.join(wiki.SERVERPATH, 'models') let dbModelsPath = path.join(wiki.SERVERPATH, 'models')
// Define Sequelize instance // 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, host: wiki.config.db.host,
port: wiki.config.db.port, port: wiki.config.db.port,
dialect: 'postgres', dialect: 'postgres',
@@ -79,7 +76,7 @@ module.exports = {
// Attempt to connect and authenticate to DB // Attempt to connect and authenticate to DB
self.inst.authenticate().then(() => { this.inst.authenticate().then(() => {
wiki.logger.info('Database (PostgreSQL) connection: [ OK ]') wiki.logger.info('Database (PostgreSQL) connection: [ OK ]')
}).catch(err => { }).catch(err => {
wiki.logger.error('Failed to connect to PostgreSQL instance.') wiki.logger.error('Failed to connect to PostgreSQL instance.')
@@ -128,9 +125,8 @@ module.exports = {
// Perform init tasks // 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
} }
} }
@@ -1,12 +1,10 @@
'use strict' const _ = require('lodash')
const fs = require('fs')
const gqlTools = require('graphql-tools')
const path = require('path')
/* global wiki */ /* 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 typeDefs = fs.readFileSync(path.join(wiki.SERVERPATH, 'schemas/types.graphql'), 'utf8')
const DateScalar = require('../schemas/scalar-date') const DateScalar = require('../schemas/scalar-date')
@@ -1,6 +1,6 @@
const _ = require('lodash')
const cluster = require('cluster') const cluster = require('cluster')
const Promise = require('bluebird') const Promise = require('bluebird')
const _ = require('lodash')
/* global wiki */ /* global wiki */
@@ -1,6 +1,7 @@
const _ = require('lodash') const _ = require('lodash')
const dotize = require('dotize') const dotize = require('dotize')
const i18nBackend = require('i18next-node-fs-backend') const i18nBackend = require('i18next-node-fs-backend')
const i18nMW = require('i18next-express-middleware')
const i18next = require('i18next') const i18next = require('i18next')
const path = require('path') const path = require('path')
const Promise = require('bluebird') const Promise = require('bluebird')
@@ -27,6 +28,9 @@ module.exports = {
}) })
return this return this
}, },
attachMiddleware (app) {
app.use(i18nMW.handle(this.engine))
},
async getByNamespace(locale, namespace) { async getByNamespace(locale, namespace) {
if (this.engine.hasResourceBundle(locale, namespace)) { if (this.engine.hasResourceBundle(locale, namespace)) {
let data = this.engine.getResourceBundle(locale, namespace) let data = this.engine.getResourceBundle(locale, namespace)
@@ -1,10 +1,10 @@
/* global wiki */
const cluster = require('cluster')
const _ = require('lodash') const _ = require('lodash')
const cluster = require('cluster')
const fs = require('fs-extra') const fs = require('fs-extra')
const path = require('path') const path = require('path')
/* global wiki */
module.exports = { module.exports = {
loggers: {}, loggers: {},
init() { init() {
@@ -21,7 +21,7 @@ module.exports = {
}) })
_.forOwn(_.omitBy(wiki.config.logging.loggers, s => s.enabled === false), (loggerConfig, loggerKey) => { _.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) loggerModule.init(logger, loggerConfig)
fs.readFile(path.join(wiki.ROOTPATH, `assets/svg/auth-icon-${loggerKey}.svg`), 'utf8').then(iconData => { fs.readFile(path.join(wiki.ROOTPATH, `assets/svg/auth-icon-${loggerKey}.svg`), 'utf8').then(iconData => {
logger.icon = iconData logger.icon = iconData
@@ -1,10 +1,8 @@
'use strict'
/* global wiki */
const Bull = require('bull') const Bull = require('bull')
const Promise = require('bluebird') const Promise = require('bluebird')
/* global wiki */
module.exports = { module.exports = {
init() { init() {
wiki.data.queues.forEach(queueName => { wiki.data.queues.forEach(queueName => {
@@ -1,22 +1,9 @@
'use strict'
/* global wiki */
const Redis = require('ioredis') const Redis = require('ioredis')
const { isPlainObject } = require('lodash') const { isPlainObject } = require('lodash')
/** /* global wiki */
* Redis module
*
* @return {Object} Redis client wrapper instance
*/
module.exports = {
/** module.exports = {
* Initialize Redis client
*
* @return {Object} Redis client instance
*/
init() { init() {
if (isPlainObject(wiki.config.redis)) { if (isPlainObject(wiki.config.redis)) {
let red = new Redis(wiki.config.redis) let red = new Redis(wiki.config.redis)
@@ -33,5 +20,4 @@ module.exports = {
process.exit(1) process.exit(1)
} }
} }
} }
@@ -1,9 +1,8 @@
/* global wiki */
const Promise = require('bluebird')
// const pm2 = Promise.promisifyAll(require('pm2'))
const _ = require('lodash') const _ = require('lodash')
const cfgHelper = require('../helpers/config') const cfgHelper = require('../helpers/config')
const Promise = require('bluebird')
/* global wiki */
module.exports = { module.exports = {
/** /**
@@ -1,8 +1,8 @@
const _ = require('lodash')
const axios = require('axios') const axios = require('axios')
const bugsnag = require('bugsnag') const bugsnag = require('bugsnag')
const path = require('path') const path = require('path')
const uuid = require('uuid/v4') const uuid = require('uuid/v4')
const _ = require('lodash')
/* global wiki */ /* global wiki */
+5 -5
View File
@@ -12,8 +12,8 @@ let wiki = {
ROOTPATH: process.cwd(), ROOTPATH: process.cwd(),
SERVERPATH: path.join(process.cwd(), 'server'), SERVERPATH: path.join(process.cwd(), 'server'),
Error: require('./helpers/error'), Error: require('./helpers/error'),
configSvc: require('./modules/config'), configSvc: require('./core/config'),
kernel: require('./modules/kernel') kernel: require('./core/kernel')
} }
global.wiki = wiki global.wiki = wiki
@@ -27,13 +27,13 @@ wiki.configSvc.init()
// Init Logger // Init Logger
// ---------------------------------------- // ----------------------------------------
wiki.logger = require('./modules/logger').init() wiki.logger = require('./core/logger').init()
// ---------------------------------------- // ----------------------------------------
// Init Telemetry // Init Telemetry
// ---------------------------------------- // ----------------------------------------
wiki.telemetry = require('./modules/telemetry').init() wiki.telemetry = require('./core/telemetry').init()
process.on('unhandledRejection', (err) => { process.on('unhandledRejection', (err) => {
wiki.telemetry.sendError(err) wiki.telemetry.sendError(err)
@@ -46,7 +46,7 @@ process.on('uncaughtException', (err) => {
// Init DB // Init DB
// ---------------------------------------- // ----------------------------------------
wiki.db = require('./modules/db').init() wiki.db = require('./core/db').init()
// ---------------------------------------- // ----------------------------------------
// Start Kernel // Start Kernel
+25 -26
View File
@@ -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 */ /* global wiki */
module.exports = async () => { module.exports = async () => {
// ---------------------------------------- // ----------------------------------------
// Load global modules // Load core modules
// ---------------------------------------- // ----------------------------------------
wiki.auth = require('./modules/auth').init() wiki.auth = require('./core/auth').init()
wiki.disk = require('./modules/disk').init() wiki.lang = require('./core/localization').init()
wiki.docs = require('./modules/documents').init()
wiki.git = require('./modules/git').init(false)
wiki.lang = require('./modules/localization').init()
// wiki.mark = require('./modules/markdown')
// wiki.search = require('./modules/search').init()
// wiki.upl = require('./modules/uploads').init()
// ---------------------------------------- // ----------------------------------------
// Load modules // 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 mw = autoload(path.join(wiki.SERVERPATH, '/middlewares'))
var ctrl = autoload(path.join(wiki.SERVERPATH, '/controllers')) var ctrl = autoload(path.join(wiki.SERVERPATH, '/controllers'))
@@ -97,13 +91,18 @@ module.exports = async () => {
app.use(bodyParser.json({ limit: '1mb' })) app.use(bodyParser.json({ limit: '1mb' }))
app.use(bodyParser.urlencoded({ extended: false, limit: '1mb' })) app.use(bodyParser.urlencoded({ extended: false, limit: '1mb' }))
// ----------------------------------------
// Localization
// ----------------------------------------
wiki.lang.attachMiddleware(app)
// ---------------------------------------- // ----------------------------------------
// View accessible data // View accessible data
// ---------------------------------------- // ----------------------------------------
app.locals.basedir = wiki.ROOTPATH app.locals.basedir = wiki.ROOTPATH
app.locals._ = require('lodash') app.locals._ = require('lodash')
app.locals.t = wiki.lang.engine.t.bind(wiki.lang)
app.locals.moment = require('moment') app.locals.moment = require('moment')
app.locals.moment.locale(wiki.config.site.lang) app.locals.moment.locale(wiki.config.site.lang)
app.locals.config = wiki.config app.locals.config = wiki.config
@@ -157,7 +156,7 @@ module.exports = async () => {
}) })
// ---------------------------------------- // ----------------------------------------
// Start HTTP server // HTTP server
// ---------------------------------------- // ----------------------------------------
let srvConnections = {} let srvConnections = {}
-169
View File
@@ -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<String>} 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
})
}
}
-455
View File
@@ -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<Boolean>} 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<Object>} 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<Object>} 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<Object|False>} 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<Boolean>} 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<Boolean>} 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<Boolean>} 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<Boolean>} 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<String>} 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<Array>} 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
}
})
})
}
}
-306
View File
@@ -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<Boolean>} 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
}
})
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
-417
View File
@@ -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 '<pre class="hljs"><code>' + hljs.highlight(lang, str, true).value + '</code></pre>'
} catch (err) {
return '<pre><code>' + _.escape(str) + '</code></pre>'
}
}
return '<pre><code>' + _.escape(str) + '</code></pre>'
}
})
.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 '<i class="twa twa-' + _.replace(token[idx].markup, /_/g, '-') + '"></i>'
}
// Video rules
const videoRules = [
{
selector: 'a.youtube',
regexp: new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|&v(?:i)?=))([^#&?]*).*/i),
output: '<iframe width="640" height="360" src="https://www.youtube.com/embed/{0}?rel=0" frameborder="0" allowfullscreen></iframe>'
},
{
selector: 'a.vimeo',
regexp: new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/i),
output: '<iframe src="https://player.vimeo.com/video/{0}" width="640" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>'
},
{
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: '<iframe width="640" height="360" src="//www.dailymotion.com/embed/video/{0}?endscreen-enable=false" frameborder="0" allowfullscreen></iframe>'
},
{
selector: 'a.video',
regexp: false,
output: '<video width="640" height="360" controls preload="metadata"><source src="{0}" type="video/mp4"></video>'
}
]
// 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: /<math([\s\S]*?)<\/math>/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(/<!--(.|\t|\n|\r)*?-->/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<String>} 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('<div class="indent-h2"></div>')
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('<div class="indent-h3"></div>')
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<String>} 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('<!-- ?([a-zA-Z]+):(.*)-->', '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(/<!-- ?([a-zA-Z]+):(.*)-->/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
}
-122
View File
@@ -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<object>} 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
}
}
-211
View File
@@ -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<String>} terms
* @returns {Promise<Object>} 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)
}
})
}
}
File renamed without changes.
File renamed without changes.
-252
View File
@@ -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<Void>} 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<Object>} 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<Object>} 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)
}
}
-281
View File
@@ -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<String>} 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<Object>} 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<Object>} 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')))
}
})
}
}
+4 -9
View File
@@ -8,7 +8,7 @@ module.exports = () => {
title: 'Wiki.js' title: 'Wiki.js'
} }
wiki.system = require('./modules/system') wiki.system = require('./core/system')
// ---------------------------------------- // ----------------------------------------
// Load modules // Load modules
@@ -62,13 +62,8 @@ module.exports = () => {
// ---------------------------------------- // ----------------------------------------
if (global.DEV) { if (global.DEV) {
const webpackDevMiddleware = require('webpack-dev-middleware') app.use(global.WP_DEV.devMiddleware)
const webpackHotMiddleware = require('webpack-hot-middleware') app.use(global.WP_DEV.hotMiddleware)
app.use(webpackDevMiddleware(global.WP, {
publicPath: global.WPCONFIG.output.publicPath,
logger: wiki.logger
}))
app.use(webpackHotMiddleware(global.WP))
} }
// ---------------------------------------- // ----------------------------------------
@@ -77,7 +72,7 @@ module.exports = () => {
app.get('*', async (req, res) => { app.get('*', async (req, res) => {
let packageObj = await fs.readJson(path.join(wiki.ROOTPATH, 'package.json')) let packageObj = await fs.readJson(path.join(wiki.ROOTPATH, 'package.json'))
res.render('pages/setup', { res.render('main/setup', {
packageObj, packageObj,
telemetryClientID: wiki.telemetry.cid telemetryClientID: wiki.telemetry.cid
}) })
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+3 -4
View File
@@ -2,11 +2,10 @@ extends ./master.pug
block body block body
body body
#app.has-stickynav(class=['is-primary-' + appconfig.theme.primary, 'is-alternate-' + appconfig.theme.alt]) #app
include ./common/header.pug navigator
alert
main main
block content block content
include ./common/footer.pug footer
block outside block outside
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+10
View File
@@ -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')
-11
View File
@@ -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')
+3 -7
View File
@@ -1,7 +1,7 @@
/* global wiki */
const Promise = require('bluebird') const Promise = require('bluebird')
/* global wiki */
module.exports = Promise.join( module.exports = Promise.join(
wiki.db.onReady, wiki.db.onReady,
wiki.configSvc.loadFromDb(['features', 'git', 'logging', 'site', 'uploads']) wiki.configSvc.loadFromDb(['features', 'git', 'logging', 'site', 'uploads'])
@@ -16,11 +16,7 @@ module.exports = Promise.join(
// Load global modules // 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.lang = require('i18next')
wiki.mark = require('./modules/markdown')
// ---------------------------------------- // ----------------------------------------
// Localization Engine // Localization Engine
@@ -36,7 +32,7 @@ module.exports = Promise.join(
lng: wiki.config.lang, lng: wiki.config.lang,
fallbackLng: 'en', fallbackLng: 'en',
backend: { backend: {
loadPath: path.join(wiki.SERVERPATH, 'locales/{{lng}}/{{ns}}.json') loadPath: path.join(wiki.SERVERPATH, 'locales/{{lng}}/{{ns}}.yml')
} }
}) })
BIN
View File
Binary file not shown.