Commit 3471a7a6 authored by NGPixel's avatar NGPixel

refactor: project cleanup + onboarding page

parent 1d9f057f
......@@ -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(() => {
......
......@@ -30,6 +30,8 @@
@import 'components/editor';
@import 'pages/welcome';
@import 'layout/_header';
@import 'layout/_loader';
@import 'layout/_rtl';
......
......@@ -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 {
......
.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;
}
}
......@@ -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",
......
......@@ -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) {
......
'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
......@@ -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) {
......
/* 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 = {
/**
......
/* 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
}
}
'use strict'
/* global wiki */
const gqlTools = require('graphql-tools')
const _ = require('lodash')
const fs = require('fs')
const gqlTools = require('graphql-tools')
const path = require('path')
const _ = require('lodash')
/* global wiki */
const typeDefs = fs.readFileSync(path.join(wiki.SERVERPATH, 'schemas/types.graphql'), 'utf8')
......
const _ = require('lodash')
const cluster = require('cluster')
const Promise = require('bluebird')
const _ = require('lodash')
/* global wiki */
......
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)
......
/* 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
......
'use strict'
/* global wiki */
const Bull = require('bull')
const Promise = require('bluebird')
/* global wiki */
module.exports = {
init() {
wiki.data.queues.forEach(queueName => {
......
'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)
}
}
}
/* 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 = {
/**
......
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 */
......
......@@ -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
......
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'))
......@@ -98,12 +92,17 @@ module.exports = async () => {
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 = {}
......
'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
})
}
}
'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
}
})
}
}
'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
}
}
'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)
}
})
}
}
'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)
}
}
'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')))
}
})
}
}
......@@ -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
})
......
......@@ -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
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')
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')
/* 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')
}
})
......
This diff was suppressed by a .gitattributes entry.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment