refactor: convert to esm (wip)

pull/6775/head
NGPixel 2 years ago
parent fc820eb1b6
commit e9e93eff42
No known key found for this signature in database
GPG Key ID: B755FB6870B30F63

@ -1,6 +1,4 @@
'use strict'
module.exports = {
export default {
arabic: '\u0600-\u06ff\u0750-\u077f\ufb50-\ufc3f\ufe70-\ufefc',
cjk: '\u4E00-\u9FBF\u3040-\u309F\u30A0-\u30FFㄱ-ㅎ가-힣ㅏ-ㅣ',
youtube: /(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|&v(?:i)?=))([^#&?]*).*/,

@ -1,158 +0,0 @@
/* global WIKI */
const express = require('express')
const ExpressBrute = require('express-brute')
const BruteKnex = require('../helpers/brute-knex')
const router = express.Router()
const moment = require('moment')
const _ = require('lodash')
const path = require('path')
const bruteforce = new ExpressBrute(new BruteKnex({
createTable: true,
knex: WIKI.db.knex
}), {
freeRetries: 5,
minWait: 5 * 60 * 1000, // 5 minutes
maxWait: 60 * 60 * 1000, // 1 hour
failCallback: (req, res, next) => {
res.status(401).send('Too many failed attempts. Try again later.')
}
})
/**
* Login form
*/
router.get('/login', async (req, res, next) => {
// -> Bypass Login
if (WIKI.config.auth.autoLogin && !req.query.all) {
const stg = await WIKI.db.authentication.query().orderBy('order').first()
const stgInfo = _.find(WIKI.data.authentication, ['key', stg.strategyKey])
if (!stgInfo.useForm) {
return res.redirect(`/login/${stg.key}`)
}
}
// -> Show Login
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
})
/**
* Social Strategies Login
*/
router.get('/login/:strategy', async (req, res, next) => {
try {
await WIKI.db.users.login({
strategy: req.params.strategy
}, { req, res })
} catch (err) {
next(err)
}
})
/**
* Social Strategies Callback
*/
router.all('/login/:strategy/callback', async (req, res, next) => {
if (req.method !== 'GET' && req.method !== 'POST') { return next() }
try {
const authResult = await WIKI.db.users.login({
strategy: req.params.strategy
}, { req, res })
res.cookie('jwt', authResult.jwt, { expires: moment().add(1, 'y').toDate() })
const loginRedirect = req.cookies['loginRedirect']
if (loginRedirect === '/' && authResult.redirect) {
res.clearCookie('loginRedirect')
res.redirect(authResult.redirect)
} else if (loginRedirect) {
res.clearCookie('loginRedirect')
res.redirect(loginRedirect)
} else if (authResult.redirect) {
res.redirect(authResult.redirect)
} else {
res.redirect('/')
}
} catch (err) {
next(err)
}
})
/**
* Logout
*/
router.get('/logout', async (req, res, next) => {
const redirURL = await WIKI.db.users.logout({ req, res })
req.logout((err) => {
if (err) { return next(err) }
res.clearCookie('jwt')
res.redirect(redirURL)
})
})
/**
* Register form
*/
router.get('/register', async (req, res, next) => {
_.set(res.locals, 'pageMeta.title', 'Register')
const localStrg = await WIKI.db.authentication.getStrategy('local')
if (localStrg.selfRegistration) {
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
} else {
next(new WIKI.Error.AuthRegistrationDisabled())
}
})
/**
* Verify
*/
router.get('/verify/:token', bruteforce.prevent, async (req, res, next) => {
try {
const usr = await WIKI.db.userKeys.validateToken({ kind: 'verify', token: req.params.token })
await WIKI.db.users.query().patch({ isVerified: true }).where('id', usr.id)
req.brute.reset()
if (WIKI.config.auth.enforce2FA) {
res.redirect('/login')
} else {
const result = await WIKI.db.users.refreshToken(usr)
res.cookie('jwt', result.token, { expires: moment().add(1, 'years').toDate() })
res.redirect('/')
}
} catch (err) {
next(err)
}
})
/**
* Reset Password
*/
router.get('/login-reset/:token', bruteforce.prevent, async (req, res, next) => {
try {
const usr = await WIKI.db.userKeys.validateToken({ kind: 'resetPwd', token: req.params.token })
if (!usr) {
throw new Error('Invalid Token')
}
req.brute.reset()
const changePwdContinuationToken = await WIKI.db.userKeys.generateToken({
userId: usr.id,
kind: 'changePwd'
})
const bgUrl = !_.isEmpty(WIKI.config.auth.loginBgUrl) ? WIKI.config.auth.loginBgUrl : '/_assets/img/splash/1.jpg'
res.render('login', { bgUrl, hideLocal: WIKI.config.auth.hideLocal, changePwdContinuationToken })
} catch (err) {
next(err)
}
})
/**
* JWT Public Endpoints
*/
router.get('/.well-known/jwk.json', function (req, res, next) {
res.json(WIKI.config.certs.jwk)
})
router.get('/.well-known/jwk.pem', function (req, res, next) {
res.send(WIKI.config.certs.public)
})
module.exports = router

@ -0,0 +1,161 @@
/* global WIKI */
import express from 'express'
import ExpressBrute from 'express-brute'
import BruteKnex from '../helpers/brute-knex.mjs'
import { find, isEmpty, set } from 'lodash-es'
import path from 'node:path'
import { DateTime } from 'luxon'
export default function () {
const router = express.Router()
const bruteforce = new ExpressBrute(new BruteKnex({
createTable: true,
knex: WIKI.db.knex
}), {
freeRetries: 5,
minWait: 5 * 60 * 1000, // 5 minutes
maxWait: 60 * 60 * 1000, // 1 hour
failCallback: (req, res, next) => {
res.status(401).send('Too many failed attempts. Try again later.')
}
})
/**
* Login form
*/
router.get('/login', async (req, res, next) => {
// -> Bypass Login
if (WIKI.config.auth.autoLogin && !req.query.all) {
const stg = await WIKI.db.authentication.query().orderBy('order').first()
const stgInfo = find(WIKI.data.authentication, ['key', stg.strategyKey])
if (!stgInfo.useForm) {
return res.redirect(`/login/${stg.key}`)
}
}
// -> Show Login
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
})
/**
* Social Strategies Login
*/
router.get('/login/:strategy', async (req, res, next) => {
try {
await WIKI.db.users.login({
strategy: req.params.strategy
}, { req, res })
} catch (err) {
next(err)
}
})
/**
* Social Strategies Callback
*/
router.all('/login/:strategy/callback', async (req, res, next) => {
if (req.method !== 'GET' && req.method !== 'POST') { return next() }
try {
const authResult = await WIKI.db.users.login({
strategy: req.params.strategy
}, { req, res })
res.cookie('jwt', authResult.jwt, { expires: DateTime.now().plus({ years: 1 }).toJSDate() })
const loginRedirect = req.cookies['loginRedirect']
if (loginRedirect === '/' && authResult.redirect) {
res.clearCookie('loginRedirect')
res.redirect(authResult.redirect)
} else if (loginRedirect) {
res.clearCookie('loginRedirect')
res.redirect(loginRedirect)
} else if (authResult.redirect) {
res.redirect(authResult.redirect)
} else {
res.redirect('/')
}
} catch (err) {
next(err)
}
})
/**
* Logout
*/
router.get('/logout', async (req, res, next) => {
const redirURL = await WIKI.db.users.logout({ req, res })
req.logout((err) => {
if (err) { return next(err) }
res.clearCookie('jwt')
res.redirect(redirURL)
})
})
/**
* Register form
*/
router.get('/register', async (req, res, next) => {
set(res.locals, 'pageMeta.title', 'Register')
const localStrg = await WIKI.db.authentication.getStrategy('local')
if (localStrg.selfRegistration) {
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
} else {
next(new WIKI.Error.AuthRegistrationDisabled())
}
})
/**
* Verify
*/
router.get('/verify/:token', bruteforce.prevent, async (req, res, next) => {
try {
const usr = await WIKI.db.userKeys.validateToken({ kind: 'verify', token: req.params.token })
await WIKI.db.users.query().patch({ isVerified: true }).where('id', usr.id)
req.brute.reset()
if (WIKI.config.auth.enforce2FA) {
res.redirect('/login')
} else {
const result = await WIKI.db.users.refreshToken(usr)
res.cookie('jwt', result.token, { expires: DateTime.now().plus({ years: 1 }).toJSDate() })
res.redirect('/')
}
} catch (err) {
next(err)
}
})
/**
* Reset Password
*/
router.get('/login-reset/:token', bruteforce.prevent, async (req, res, next) => {
try {
const usr = await WIKI.db.userKeys.validateToken({ kind: 'resetPwd', token: req.params.token })
if (!usr) {
throw new Error('Invalid Token')
}
req.brute.reset()
const changePwdContinuationToken = await WIKI.db.userKeys.generateToken({
userId: usr.id,
kind: 'changePwd'
})
const bgUrl = !isEmpty(WIKI.config.auth.loginBgUrl) ? WIKI.config.auth.loginBgUrl : '/_assets/img/splash/1.jpg'
res.render('login', { bgUrl, hideLocal: WIKI.config.auth.hideLocal, changePwdContinuationToken })
} catch (err) {
next(err)
}
})
/**
* JWT Public Endpoints
*/
router.get('/.well-known/jwk.json', function (req, res, next) {
res.json(WIKI.config.certs.jwk)
})
router.get('/.well-known/jwk.pem', function (req, res, next) {
res.send(WIKI.config.certs.public)
})
return router
}

@ -1,533 +0,0 @@
const express = require('express')
const router = express.Router()
const pageHelper = require('../helpers/page')
const _ = require('lodash')
const CleanCSS = require('clean-css')
const moment = require('moment')
const path = require('path')
const siteAssetsPath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'assets')
/**
* Robots.txt
*/
router.get('/robots.txt', (req, res, next) => {
res.type('text/plain')
if (_.includes(WIKI.config.seo.robots, 'noindex')) {
res.send('User-agent: *\nDisallow: /')
} else {
res.status(200).end()
}
})
/**
* Health Endpoint
*/
router.get('/healthz', (req, res, next) => {
if (WIKI.db.knex.client.pool.numFree() < 1 && WIKI.db.knex.client.pool.numUsed() < 1) {
res.status(503).json({ ok: false }).end()
} else {
res.status(200).json({ ok: true }).end()
}
})
/**
* Site Asset
*/
router.get('/_site/:siteId?/:resource', async (req, res, next) => {
const site = req.params.siteId ? WIKI.sites[req.params.siteId] : await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname })
if (!site) {
return res.status(404).send('Site Not Found')
}
switch (req.params.resource) {
case 'logo': {
if (site.config.assets.logo) {
// TODO: Fetch from db if not in disk cache
res.sendFile(path.join(siteAssetsPath, `logo-${site.id}.${site.config.assets.logoExt}`))
} else {
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/_assets/logo-wikijs.svg'))
}
break
}
case 'favicon': {
if (site.config.assets.favicon) {
// TODO: Fetch from db if not in disk cache
res.sendFile(path.join(siteAssetsPath, `favicon-${site.id}.${site.config.assets.faviconExt}`))
} else {
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/_assets/logo-wikijs.svg'))
}
break
}
case 'loginbg': {
if (site.config.assets.loginBg) {
// TODO: Fetch from db if not in disk cache
res.sendFile(path.join(siteAssetsPath, `loginbg-${site.id}.jpg`))
} else {
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/_assets/bg/login.jpg'))
}
break
}
default: {
return res.status(404).send('Invalid Site Resource')
}
}
})
/**
* Asset Thumbnails / Download
*/
router.get('/_thumb/:id.webp', async (req, res, next) => {
const thumb = await WIKI.db.assets.getThumbnail({
id: req.params.id
})
if (thumb) {
// TODO: Check permissions
switch (thumb.previewState) {
case 'pending': {
res.redirect('/_assets/illustrations/fileman-pending.svg')
break
}
case 'ready': {
res.set('Content-Type', 'image/webp')
res.send(thumb.preview)
break
}
case 'failed': {
res.redirect('/_assets/illustrations/fileman-failed.svg')
break
}
default: {
return res.status(500).send('Invalid Thumbnail Preview State')
}
}
} else {
return res.sendStatus(404)
}
})
// router.get(['/_admin', '/_admin/*'], (req, res, next) => {
// if (!WIKI.auth.checkAccess(req.user, [
// 'manage:system',
// 'write:users',
// 'manage:users',
// 'write:groups',
// 'manage:groups',
// 'manage:navigation',
// 'manage:theme',
// 'manage:api'
// ])) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.status(403).render('unauthorized', { action: 'view' })
// }
// _.set(res.locals, 'pageMeta.title', 'Admin')
// res.render('admin')
// })
// /**
// * Download Page / Version
// */
// router.get(['/d', '/d/*'], async (req, res, next) => {
// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
// const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0
// const page = await WIKI.db.pages.getPageFromDb({
// path: pageArgs.path,
// locale: pageArgs.locale,
// userId: req.user.id,
// isPrivate: false
// })
// pageArgs.tags = _.get(page, 'tags', [])
// if (versionId > 0) {
// if (!WIKI.auth.checkAccess(req.user, ['read:history'], pageArgs)) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.render('unauthorized', { action: 'downloadVersion' })
// }
// } else {
// if (!WIKI.auth.checkAccess(req.user, ['read:source'], pageArgs)) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.render('unauthorized', { action: 'download' })
// }
// }
// if (page) {
// const fileName = _.last(page.path.split('/')) + '.' + pageHelper.getFileExtension(page.contentType)
// res.attachment(fileName)
// if (versionId > 0) {
// const pageVersion = await WIKI.db.pageHistory.getVersion({ pageId: page.id, versionId })
// res.send(pageHelper.injectPageMetadata(pageVersion))
// } else {
// res.send(pageHelper.injectPageMetadata(page))
// }
// } else {
// res.status(404).end()
// }
// })
// /**
// * Create/Edit document
// */
// router.get(['/_edit', '/_edit/*'], async (req, res, next) => {
// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
// const site = await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname })
// if (!site) {
// throw new Error('INVALID_SITE')
// }
// if (pageArgs.path === '') {
// return res.redirect(`/_edit/home`)
// }
// // if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
// // return res.redirect(`/_edit/${pageArgs.locale}/${pageArgs.path}`)
// // }
// // req.i18n.changeLanguage(pageArgs.locale)
// // -> Set Editor Lang
// _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
// // _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
// // -> Check for reserved path
// if (pageHelper.isReservedPath(pageArgs.path)) {
// return next(new Error('Cannot create this page because it starts with a system reserved path.'))
// }
// // -> Get page data from DB
// let page = await WIKI.db.pages.getPageFromDb({
// siteId: site.id,
// path: pageArgs.path,
// locale: pageArgs.locale,
// userId: req.user.id
// })
// pageArgs.tags = _.get(page, 'tags', [])
// // -> Effective Permissions
// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
// const injectCode = {
// css: '', // WIKI.config.theming.injectCSS,
// head: '', // WIKI.config.theming.injectHead,
// body: '' // WIKI.config.theming.injectBody
// }
// if (page) {
// // -> EDIT MODE
// if (!(effectivePermissions.pages.write || effectivePermissions.pages.manage)) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.render('unauthorized', { action: 'edit' })
// }
// // -> Get page tags
// await page.$relatedQuery('tags')
// page.tags = _.map(page.tags, 'tag')
// // Handle missing extra field
// page.extra = page.extra || { css: '', js: '' }
// // -> Beautify Script CSS
// if (!_.isEmpty(page.extra.css)) {
// page.extra.css = new CleanCSS({ format: 'beautify' }).minify(page.extra.css).styles
// }
// _.set(res.locals, 'pageMeta.title', `Edit ${page.title}`)
// _.set(res.locals, 'pageMeta.description', page.description)
// page.mode = 'update'
// page.isPublished = (page.isPublished === true || page.isPublished === 1) ? 'true' : 'false'
// page.content = Buffer.from(page.content).toString('base64')
// } else {
// // -> CREATE MODE
// if (!effectivePermissions.pages.write) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.render('unauthorized', { action: 'create' })
// }
// _.set(res.locals, 'pageMeta.title', `New Page`)
// page = {
// path: pageArgs.path,
// localeCode: pageArgs.locale,
// editorKey: null,
// mode: 'create',
// content: null,
// title: null,
// description: null,
// updatedAt: new Date().toISOString(),
// extra: {
// css: '',
// js: ''
// }
// }
// }
// res.render('editor', { page, injectCode, effectivePermissions })
// })
// /**
// * History
// */
// router.get(['/h', '/h/*'], async (req, res, next) => {
// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
// if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
// return res.redirect(`/h/${pageArgs.locale}/${pageArgs.path}`)
// }
// req.i18n.changeLanguage(pageArgs.locale)
// _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
// _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
// const page = await WIKI.db.pages.getPageFromDb({
// path: pageArgs.path,
// locale: pageArgs.locale,
// userId: req.user.id,
// isPrivate: false
// })
// if (!page) {
// _.set(res.locals, 'pageMeta.title', 'Page Not Found')
// return res.status(404).render('notfound', { action: 'history' })
// }
// pageArgs.tags = _.get(page, 'tags', [])
// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
// if (!effectivePermissions.history.read) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.render('unauthorized', { action: 'history' })
// }
// if (page) {
// _.set(res.locals, 'pageMeta.title', page.title)
// _.set(res.locals, 'pageMeta.description', page.description)
// res.render('history', { page, effectivePermissions })
// } else {
// res.redirect(`/${pageArgs.path}`)
// }
// })
// /**
// * Page ID redirection
// */
// router.get(['/i', '/i/:id'], async (req, res, next) => {
// const pageId = _.toSafeInteger(req.params.id)
// if (pageId <= 0) {
// return res.redirect('/')
// }
// const page = await WIKI.db.pages.query().column(['path', 'localeCode', 'isPrivate', 'privateNS']).findById(pageId)
// if (!page) {
// _.set(res.locals, 'pageMeta.title', 'Page Not Found')
// return res.status(404).render('notfound', { action: 'view' })
// }
// if (!WIKI.auth.checkAccess(req.user, ['read:pages'], {
// locale: page.localeCode,
// path: page.path,
// private: page.isPrivate,
// privateNS: page.privateNS,
// explicitLocale: false,
// tags: page.tags
// })) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.render('unauthorized', { action: 'view' })
// }
// if (WIKI.config.lang.namespacing) {
// return res.redirect(`/${page.localeCode}/${page.path}`)
// } else {
// return res.redirect(`/${page.path}`)
// }
// })
// /**
// * Source
// */
// router.get(['/s', '/s/*'], async (req, res, next) => {
// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
// const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0
// const page = await WIKI.db.pages.getPageFromDb({
// path: pageArgs.path,
// locale: pageArgs.locale,
// userId: req.user.id,
// isPrivate: false
// })
// pageArgs.tags = _.get(page, 'tags', [])
// if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
// return res.redirect(`/s/${pageArgs.locale}/${pageArgs.path}`)
// }
// // -> Effective Permissions
// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
// _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
// _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
// if (versionId > 0) {
// if (!effectivePermissions.history.read) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.render('unauthorized', { action: 'sourceVersion' })
// }
// } else {
// if (!effectivePermissions.source.read) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.render('unauthorized', { action: 'source' })
// }
// }
// if (page) {
// if (versionId > 0) {
// const pageVersion = await WIKI.db.pageHistory.getVersion({ pageId: page.id, versionId })
// _.set(res.locals, 'pageMeta.title', pageVersion.title)
// _.set(res.locals, 'pageMeta.description', pageVersion.description)
// res.render('source', {
// page: {
// ...page,
// ...pageVersion
// },
// effectivePermissions
// })
// } else {
// _.set(res.locals, 'pageMeta.title', page.title)
// _.set(res.locals, 'pageMeta.description', page.description)
// res.render('source', { page, effectivePermissions })
// }
// } else {
// res.redirect(`/${pageArgs.path}`)
// }
// })
// /**
// * Tags
// */
// router.get(['/t', '/t/*'], (req, res, next) => {
// _.set(res.locals, 'pageMeta.title', 'Tags')
// res.render('tags')
// })
/**
* User Avatar
*/
router.get('/_user/:uid/avatar', async (req, res, next) => {
if (!WIKI.auth.checkAccess(req.user, ['read:pages'])) {
return res.sendStatus(403)
}
const av = await WIKI.db.users.getUserAvatarData(req.params.uid)
if (av) {
res.set('Content-Type', 'image/jpeg')
return res.send(av)
}
return res.sendStatus(404)
})
// /**
// * View document / asset
// */
// router.get('/*', async (req, res, next) => {
// const stripExt = _.some(WIKI.data.pageExtensions, ext => _.endsWith(req.path, `.${ext}`))
// const pageArgs = pageHelper.parsePath(req.path, { stripExt })
// const isPage = (stripExt || pageArgs.path.indexOf('.') === -1)
// const site = await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname })
// if (!site) {
// throw new Error('INVALID_SITE')
// }
// if (isPage) {
// // if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
// // return res.redirect(`/${pageArgs.locale}/${pageArgs.path}`)
// // }
// // req.i18n.changeLanguage(pageArgs.locale)
// try {
// // -> Get Page from cache
// const page = await WIKI.db.pages.getPage({
// siteId: site.id,
// path: pageArgs.path,
// locale: pageArgs.locale,
// userId: req.user.id
// })
// pageArgs.tags = _.get(page, 'tags', [])
// // -> Effective Permissions
// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
// // -> Check User Access
// if (!effectivePermissions.pages.read) {
// if (req.user.id === WIKI.auth.guest.id) {
// res.cookie('loginRedirect', req.path, {
// maxAge: 15 * 60 * 1000
// })
// }
// if (pageArgs.path === 'home' && req.user.id === WIKI.auth.guest.id) {
// return res.redirect('/login')
// }
// return res.redirect(`/_error/unauthorized?from=${req.path}`)
// }
// _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
// // _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
// if (page) {
// _.set(res.locals, 'pageMeta.title', page.title)
// _.set(res.locals, 'pageMeta.description', page.description)
// // -> Check Publishing State
// let pageIsPublished = page.isPublished
// if (pageIsPublished && !_.isEmpty(page.publishStartDate)) {
// pageIsPublished = moment(page.publishStartDate).isSameOrBefore()
// }
// if (pageIsPublished && !_.isEmpty(page.publishEndDate)) {
// pageIsPublished = moment(page.publishEndDate).isSameOrAfter()
// }
// if (!pageIsPublished && !effectivePermissions.pages.write) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.status(403).render('unauthorized', {
// action: 'view'
// })
// }
// // -> Render view
// res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
// } else if (pageArgs.path === 'home') {
// res.redirect('/_welcome')
// } else {
// _.set(res.locals, 'pageMeta.title', 'Page Not Found')
// if (effectivePermissions.pages.write) {
// res.status(404).render('new', { path: pageArgs.path, locale: pageArgs.locale })
// } else {
// res.status(404).render('notfound', { action: 'view' })
// }
// }
// } catch (err) {
// next(err)
// }
// } else {
// if (!WIKI.auth.checkAccess(req.user, ['read:assets'], pageArgs)) {
// return res.sendStatus(403)
// }
// await WIKI.db.assets.getAsset(pageArgs.path, res)
// }
// })
router.get('/*', (req, res, next) => {
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
})
module.exports = router

@ -0,0 +1,534 @@
import express from 'express'
// import pageHelper from '../helpers/page.mjs'
// import CleanCSS from 'clean-css'
import path from 'node:path'
export default function () {
const router = express.Router()
const siteAssetsPath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'assets')
/**
* Robots.txt
*/
router.get('/robots.txt', (req, res, next) => {
res.type('text/plain')
if (WIKI.config.seo.robots.includes('noindex')) {
res.send('User-agent: *\nDisallow: /')
} else {
res.status(200).end()
}
})
/**
* Health Endpoint
*/
router.get('/healthz', (req, res, next) => {
if (WIKI.db.knex.client.pool.numFree() < 1 && WIKI.db.knex.client.pool.numUsed() < 1) {
res.status(503).json({ ok: false }).end()
} else {
res.status(200).json({ ok: true }).end()
}
})
/**
* Site Asset
*/
router.get('/_site/:siteId?/:resource', async (req, res, next) => {
const site = req.params.siteId ? WIKI.sites[req.params.siteId] : await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname })
if (!site) {
return res.status(404).send('Site Not Found')
}
switch (req.params.resource) {
case 'logo': {
if (site.config.assets.logo) {
// TODO: Fetch from db if not in disk cache
res.sendFile(path.join(siteAssetsPath, `logo-${site.id}.${site.config.assets.logoExt}`))
} else {
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/_assets/logo-wikijs.svg'))
}
break
}
case 'favicon': {
if (site.config.assets.favicon) {
// TODO: Fetch from db if not in disk cache
res.sendFile(path.join(siteAssetsPath, `favicon-${site.id}.${site.config.assets.faviconExt}`))
} else {
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/_assets/logo-wikijs.svg'))
}
break
}
case 'loginbg': {
if (site.config.assets.loginBg) {
// TODO: Fetch from db if not in disk cache
res.sendFile(path.join(siteAssetsPath, `loginbg-${site.id}.jpg`))
} else {
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/_assets/bg/login.jpg'))
}
break
}
default: {
return res.status(404).send('Invalid Site Resource')
}
}
})
/**
* Asset Thumbnails / Download
*/
router.get('/_thumb/:id.webp', async (req, res, next) => {
const thumb = await WIKI.db.assets.getThumbnail({
id: req.params.id
})
if (thumb) {
// TODO: Check permissions
switch (thumb.previewState) {
case 'pending': {
res.redirect('/_assets/illustrations/fileman-pending.svg')
break
}
case 'ready': {
res.set('Content-Type', 'image/webp')
res.send(thumb.preview)
break
}
case 'failed': {
res.redirect('/_assets/illustrations/fileman-failed.svg')
break
}
default: {
return res.status(500).send('Invalid Thumbnail Preview State')
}
}
} else {
return res.sendStatus(404)
}
})
// router.get(['/_admin', '/_admin/*'], (req, res, next) => {
// if (!WIKI.auth.checkAccess(req.user, [
// 'manage:system',
// 'write:users',
// 'manage:users',
// 'write:groups',
// 'manage:groups',
// 'manage:navigation',
// 'manage:theme',
// 'manage:api'
// ])) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.status(403).render('unauthorized', { action: 'view' })
// }
// _.set(res.locals, 'pageMeta.title', 'Admin')
// res.render('admin')
// })
// /**
// * Download Page / Version
// */
// router.get(['/d', '/d/*'], async (req, res, next) => {
// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
// const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0
// const page = await WIKI.db.pages.getPageFromDb({
// path: pageArgs.path,
// locale: pageArgs.locale,
// userId: req.user.id,
// isPrivate: false
// })
// pageArgs.tags = _.get(page, 'tags', [])
// if (versionId > 0) {
// if (!WIKI.auth.checkAccess(req.user, ['read:history'], pageArgs)) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.render('unauthorized', { action: 'downloadVersion' })
// }
// } else {
// if (!WIKI.auth.checkAccess(req.user, ['read:source'], pageArgs)) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.render('unauthorized', { action: 'download' })
// }
// }
// if (page) {
// const fileName = _.last(page.path.split('/')) + '.' + pageHelper.getFileExtension(page.contentType)
// res.attachment(fileName)
// if (versionId > 0) {
// const pageVersion = await WIKI.db.pageHistory.getVersion({ pageId: page.id, versionId })
// res.send(pageHelper.injectPageMetadata(pageVersion))
// } else {
// res.send(pageHelper.injectPageMetadata(page))
// }
// } else {
// res.status(404).end()
// }
// })
// /**
// * Create/Edit document
// */
// router.get(['/_edit', '/_edit/*'], async (req, res, next) => {
// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
// const site = await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname })
// if (!site) {
// throw new Error('INVALID_SITE')
// }
// if (pageArgs.path === '') {
// return res.redirect(`/_edit/home`)
// }
// // if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
// // return res.redirect(`/_edit/${pageArgs.locale}/${pageArgs.path}`)
// // }
// // req.i18n.changeLanguage(pageArgs.locale)
// // -> Set Editor Lang
// _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
// // _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
// // -> Check for reserved path
// if (pageHelper.isReservedPath(pageArgs.path)) {
// return next(new Error('Cannot create this page because it starts with a system reserved path.'))
// }
// // -> Get page data from DB
// let page = await WIKI.db.pages.getPageFromDb({
// siteId: site.id,
// path: pageArgs.path,
// locale: pageArgs.locale,
// userId: req.user.id
// })
// pageArgs.tags = _.get(page, 'tags', [])
// // -> Effective Permissions
// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
// const injectCode = {
// css: '', // WIKI.config.theming.injectCSS,
// head: '', // WIKI.config.theming.injectHead,
// body: '' // WIKI.config.theming.injectBody
// }
// if (page) {
// // -> EDIT MODE
// if (!(effectivePermissions.pages.write || effectivePermissions.pages.manage)) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.render('unauthorized', { action: 'edit' })
// }
// // -> Get page tags
// await page.$relatedQuery('tags')
// page.tags = _.map(page.tags, 'tag')
// // Handle missing extra field
// page.extra = page.extra || { css: '', js: '' }
// // -> Beautify Script CSS
// if (!_.isEmpty(page.extra.css)) {
// page.extra.css = new CleanCSS({ format: 'beautify' }).minify(page.extra.css).styles
// }
// _.set(res.locals, 'pageMeta.title', `Edit ${page.title}`)
// _.set(res.locals, 'pageMeta.description', page.description)
// page.mode = 'update'
// page.isPublished = (page.isPublished === true || page.isPublished === 1) ? 'true' : 'false'
// page.content = Buffer.from(page.content).toString('base64')
// } else {
// // -> CREATE MODE
// if (!effectivePermissions.pages.write) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.render('unauthorized', { action: 'create' })
// }
// _.set(res.locals, 'pageMeta.title', `New Page`)
// page = {
// path: pageArgs.path,
// localeCode: pageArgs.locale,
// editorKey: null,
// mode: 'create',
// content: null,
// title: null,
// description: null,
// updatedAt: new Date().toISOString(),
// extra: {
// css: '',
// js: ''
// }
// }
// }
// res.render('editor', { page, injectCode, effectivePermissions })
// })
// /**
// * History
// */
// router.get(['/h', '/h/*'], async (req, res, next) => {
// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
// if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
// return res.redirect(`/h/${pageArgs.locale}/${pageArgs.path}`)
// }
// req.i18n.changeLanguage(pageArgs.locale)
// _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
// _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
// const page = await WIKI.db.pages.getPageFromDb({
// path: pageArgs.path,
// locale: pageArgs.locale,
// userId: req.user.id,
// isPrivate: false
// })
// if (!page) {
// _.set(res.locals, 'pageMeta.title', 'Page Not Found')
// return res.status(404).render('notfound', { action: 'history' })
// }
// pageArgs.tags = _.get(page, 'tags', [])
// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
// if (!effectivePermissions.history.read) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.render('unauthorized', { action: 'history' })
// }
// if (page) {
// _.set(res.locals, 'pageMeta.title', page.title)
// _.set(res.locals, 'pageMeta.description', page.description)
// res.render('history', { page, effectivePermissions })
// } else {
// res.redirect(`/${pageArgs.path}`)
// }
// })
// /**
// * Page ID redirection
// */
// router.get(['/i', '/i/:id'], async (req, res, next) => {
// const pageId = _.toSafeInteger(req.params.id)
// if (pageId <= 0) {
// return res.redirect('/')
// }
// const page = await WIKI.db.pages.query().column(['path', 'localeCode', 'isPrivate', 'privateNS']).findById(pageId)
// if (!page) {
// _.set(res.locals, 'pageMeta.title', 'Page Not Found')
// return res.status(404).render('notfound', { action: 'view' })
// }
// if (!WIKI.auth.checkAccess(req.user, ['read:pages'], {
// locale: page.localeCode,
// path: page.path,
// private: page.isPrivate,
// privateNS: page.privateNS,
// explicitLocale: false,
// tags: page.tags
// })) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.render('unauthorized', { action: 'view' })
// }
// if (WIKI.config.lang.namespacing) {
// return res.redirect(`/${page.localeCode}/${page.path}`)
// } else {
// return res.redirect(`/${page.path}`)
// }
// })
// /**
// * Source
// */
// router.get(['/s', '/s/*'], async (req, res, next) => {
// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
// const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0
// const page = await WIKI.db.pages.getPageFromDb({
// path: pageArgs.path,
// locale: pageArgs.locale,
// userId: req.user.id,
// isPrivate: false
// })
// pageArgs.tags = _.get(page, 'tags', [])
// if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
// return res.redirect(`/s/${pageArgs.locale}/${pageArgs.path}`)
// }
// // -> Effective Permissions
// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
// _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
// _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
// if (versionId > 0) {
// if (!effectivePermissions.history.read) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.render('unauthorized', { action: 'sourceVersion' })
// }
// } else {
// if (!effectivePermissions.source.read) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.render('unauthorized', { action: 'source' })
// }
// }
// if (page) {
// if (versionId > 0) {
// const pageVersion = await WIKI.db.pageHistory.getVersion({ pageId: page.id, versionId })
// _.set(res.locals, 'pageMeta.title', pageVersion.title)
// _.set(res.locals, 'pageMeta.description', pageVersion.description)
// res.render('source', {
// page: {
// ...page,
// ...pageVersion
// },
// effectivePermissions
// })
// } else {
// _.set(res.locals, 'pageMeta.title', page.title)
// _.set(res.locals, 'pageMeta.description', page.description)
// res.render('source', { page, effectivePermissions })
// }
// } else {
// res.redirect(`/${pageArgs.path}`)
// }
// })
// /**
// * Tags
// */
// router.get(['/t', '/t/*'], (req, res, next) => {
// _.set(res.locals, 'pageMeta.title', 'Tags')
// res.render('tags')
// })
/**
* User Avatar
*/
router.get('/_user/:uid/avatar', async (req, res, next) => {
if (!WIKI.auth.checkAccess(req.user, ['read:pages'])) {
return res.sendStatus(403)
}
const av = await WIKI.db.users.getUserAvatarData(req.params.uid)
if (av) {
res.set('Content-Type', 'image/jpeg')
return res.send(av)
}
return res.sendStatus(404)
})
// /**
// * View document / asset
// */
// router.get('/*', async (req, res, next) => {
// const stripExt = _.some(WIKI.data.pageExtensions, ext => _.endsWith(req.path, `.${ext}`))
// const pageArgs = pageHelper.parsePath(req.path, { stripExt })
// const isPage = (stripExt || pageArgs.path.indexOf('.') === -1)
// const site = await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname })
// if (!site) {
// throw new Error('INVALID_SITE')
// }
// if (isPage) {
// // if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
// // return res.redirect(`/${pageArgs.locale}/${pageArgs.path}`)
// // }
// // req.i18n.changeLanguage(pageArgs.locale)
// try {
// // -> Get Page from cache
// const page = await WIKI.db.pages.getPage({
// siteId: site.id,
// path: pageArgs.path,
// locale: pageArgs.locale,
// userId: req.user.id
// })
// pageArgs.tags = _.get(page, 'tags', [])
// // -> Effective Permissions
// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
// // -> Check User Access
// if (!effectivePermissions.pages.read) {
// if (req.user.id === WIKI.auth.guest.id) {
// res.cookie('loginRedirect', req.path, {
// maxAge: 15 * 60 * 1000
// })
// }
// if (pageArgs.path === 'home' && req.user.id === WIKI.auth.guest.id) {
// return res.redirect('/login')
// }
// return res.redirect(`/_error/unauthorized?from=${req.path}`)
// }
// _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
// // _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
// if (page) {
// _.set(res.locals, 'pageMeta.title', page.title)
// _.set(res.locals, 'pageMeta.description', page.description)
// // -> Check Publishing State
// let pageIsPublished = page.isPublished
// if (pageIsPublished && !_.isEmpty(page.publishStartDate)) {
// pageIsPublished = moment(page.publishStartDate).isSameOrBefore()
// }
// if (pageIsPublished && !_.isEmpty(page.publishEndDate)) {
// pageIsPublished = moment(page.publishEndDate).isSameOrAfter()
// }
// if (!pageIsPublished && !effectivePermissions.pages.write) {
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
// return res.status(403).render('unauthorized', {
// action: 'view'
// })
// }
// // -> Render view
// res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
// } else if (pageArgs.path === 'home') {
// res.redirect('/_welcome')
// } else {
// _.set(res.locals, 'pageMeta.title', 'Page Not Found')
// if (effectivePermissions.pages.write) {
// res.status(404).render('new', { path: pageArgs.path, locale: pageArgs.locale })
// } else {
// res.status(404).render('notfound', { action: 'view' })
// }
// }
// } catch (err) {
// next(err)
// }
// } else {
// if (!WIKI.auth.checkAccess(req.user, ['read:assets'], pageArgs)) {
// return res.sendStatus(403)
// }
// await WIKI.db.assets.getAsset(pageArgs.path, res)
// }
// })
router.get('/*', (req, res, next) => {
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
})
return router
}

@ -1,36 +0,0 @@
const express = require('express')
const router = express.Router()
const _ = require('lodash')
const qs = require('querystring')
/**
* Let's Encrypt Challenge
*/
router.get('/.well-known/acme-challenge/:token', (req, res, next) => {
res.type('text/plain')
if (_.get(WIKI.config, 'letsencrypt.challenge', false)) {
if (WIKI.config.letsencrypt.challenge.token === req.params.token) {
res.send(WIKI.config.letsencrypt.challenge.keyAuthorization)
WIKI.logger.info(`(LETSENCRYPT) Received valid challenge request. [ ACCEPTED ]`)
} else {
res.status(406).send('Invalid Challenge Token!')
WIKI.logger.warn(`(LETSENCRYPT) Received invalid challenge request. [ REJECTED ]`)
}
} else {
res.status(418).end()
}
})
/**
* Redirect to HTTPS if HTTP Redirection is enabled
*/
// router.all('/*', (req, res, next) => {
// if (WIKI.config.server.sslRedir && !req.secure && WIKI.servers.servers.https) {
// let query = (!_.isEmpty(req.query)) ? `?${qs.stringify(req.query)}` : ``
// return res.redirect(`https://${req.hostname}${req.originalUrl}${query}`)
// } else {
// next()
// }
// })
module.exports = router

@ -0,0 +1,39 @@
import express from 'express'
import { get } from 'lodash-es'
import qs from 'querystring'
export default function () {
const router = express.Router()
/**
* Let's Encrypt Challenge
*/
router.get('/.well-known/acme-challenge/:token', (req, res, next) => {
res.type('text/plain')
if (get(WIKI.config, 'letsencrypt.challenge', false)) {
if (WIKI.config.letsencrypt.challenge.token === req.params.token) {
res.send(WIKI.config.letsencrypt.challenge.keyAuthorization)
WIKI.logger.info(`(LETSENCRYPT) Received valid challenge request. [ ACCEPTED ]`)
} else {
res.status(406).send('Invalid Challenge Token!')
WIKI.logger.warn(`(LETSENCRYPT) Received invalid challenge request. [ REJECTED ]`)
}
} else {
res.status(418).end()
}
})
/**
* Redirect to HTTPS if HTTP Redirection is enabled
*/
// router.all('/*', (req, res, next) => {
// if (WIKI.config.server.sslRedir && !req.secure && WIKI.servers.servers.https) {
// let query = (!_.isEmpty(req.query)) ? `?${qs.stringify(req.query)}` : ``
// return res.redirect(`https://${req.hostname}${req.originalUrl}${query}`)
// } else {
// next()
// }
// })
return router
}

@ -1,105 +0,0 @@
const express = require('express')
const router = express.Router()
const _ = require('lodash')
const multer = require('multer')
const path = require('path')
const sanitize = require('sanitize-filename')
/**
* Upload files
*/
router.post('/u', (req, res, next) => {
multer({
dest: path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'uploads'),
limits: {
fileSize: WIKI.config.uploads.maxFileSize,
files: WIKI.config.uploads.maxFiles
}
}).array('mediaUpload')(req, res, next)
}, async (req, res, next) => {
if (!_.some(req.user.permissions, pm => _.includes(['write:assets', 'manage:system'], pm))) {
return res.status(403).json({
succeeded: false,
message: 'You are not authorized to upload files.'
})
} else if (req.files.length < 1) {
return res.status(400).json({
succeeded: false,
message: 'Missing upload payload.'
})
} else if (req.files.length > 1) {
return res.status(400).json({
succeeded: false,
message: 'You cannot upload multiple files within the same request.'
})
}
const fileMeta = _.get(req, 'files[0]', false)
if (!fileMeta) {
return res.status(500).json({
succeeded: false,
message: 'Missing upload file metadata.'
})
}
// Get folder Id
let folderId = null
try {
const folderRaw = _.get(req, 'body.mediaUpload', false)
if (folderRaw) {
folderId = _.get(JSON.parse(folderRaw), 'folderId', null)
if (folderId === 0) {
folderId = null
}
} else {
throw new Error('Missing File Metadata')
}
} catch (err) {
return res.status(400).json({
succeeded: false,
message: 'Missing upload folder metadata.'
})
}
// Build folder hierarchy
let hierarchy = []
if (folderId) {
try {
hierarchy = await WIKI.db.assetFolders.getHierarchy(folderId)
} catch (err) {
return res.status(400).json({
succeeded: false,
message: 'Failed to fetch folder hierarchy.'
})
}
}
// Sanitize filename
fileMeta.originalname = sanitize(fileMeta.originalname.toLowerCase().replace(/[\s,;#]+/g, '_'))
// Check if user can upload at path
const assetPath = (folderId) ? hierarchy.map(h => h.slug).join('/') + `/${fileMeta.originalname}` : fileMeta.originalname
if (!WIKI.auth.checkAccess(req.user, ['write:assets'], { path: assetPath })) {
return res.status(403).json({
succeeded: false,
message: 'You are not authorized to upload files to this folder.'
})
}
// Process upload file
await WIKI.db.assets.upload({
...fileMeta,
mode: 'upload',
folderId: folderId,
assetPath,
user: req.user
})
res.send('ok')
})
router.get('/u', async (req, res, next) => {
res.json({
ok: true
})
})
module.exports = router

@ -1,16 +1,16 @@
const chalk = require('chalk')
const os = require('node:os')
import chalk from 'chalk'
import os from 'node:os'
module.exports = () => {
export default function () {
WIKI.servers.ws.on('connection', (socket) => {
// TODO: Validate token + permissions
const token = socket.handshake.auth.token
console.info(token)
// console.info(token)
const listeners = {}
socket.on('server:logs', () => {
socket.emit('server:log', chalk`{greenBright Streaming logs from {bold Wiki.js} instance {yellowBright.bold ${WIKI.INSTANCE_ID}} on host {yellowBright.bold ${os.hostname()}}...}`)
socket.emit('server:log', chalk.greenBright(`Streaming logs from ${chalk.bold('Wiki.js')} instance ${chalk.yellowBright.bold(WIKI.INSTANCE_ID)} on host ${chalk.yellowBright.bold(os.hostname())}...`))
listeners.serverLogs = (msg) => {
socket.emit('server:log', msg)
}

@ -1,20 +1,19 @@
const pickle = require('chromium-pickle-js')
const path = require('path')
const UINT64 = require('cuint').UINT64
const fs = require('fs')
import pickle from 'chromium-pickle-js'
import path from 'node:path'
import { UINT64 } from 'cuint'
import fs from 'node:fs'
/**
* Based of express-serve-asar (https://github.com/toyobayashi/express-serve-asar)
* by Fenglin Li (https://github.com/toyobayashi)
*/
const packages = {
'twemoji': path.join(WIKI.ROOTPATH, `assets-legacy/svg/twemoji.asar`)
}
module.exports = {
export default {
fdCache: {},
async serve (pkgName, req, res, next) {
const packages = {
'twemoji': path.join(WIKI.ROOTPATH, `assets/svg/twemoji.asar`)
}
const file = this.readFilesystemSync(packages[pkgName])
const { filesystem, fd } = file
const info = filesystem.getFile(req.path.substring(1))

@ -1,24 +1,25 @@
const passport = require('passport')
const passportJWT = require('passport-jwt')
const _ = require('lodash')
const jwt = require('jsonwebtoken')
const ms = require('ms')
const { DateTime } = require('luxon')
const util = require('node:util')
const crypto = require('node:crypto')
const randomBytes = util.promisify(crypto.randomBytes)
const pem2jwk = require('pem-jwk').pem2jwk
import passport from 'passport'
import passportJWT from 'passport-jwt'
import _ from 'lodash'
import jwt from 'jsonwebtoken'
import ms from 'ms'
import { DateTime } from 'luxon'
import util from 'node:util'
import crypto from 'node:crypto'
import { pem2jwk } from 'pem-jwk'
import NodeCache from 'node-cache'
import { extractJWT } from '../helpers/security.mjs'
const securityHelper = require('../helpers/security')
const randomBytes = util.promisify(crypto.randomBytes)
module.exports = {
export default {
strategies: {},
guest: {
cacheExpiration: DateTime.utc().minus({ days: 1 })
},
groups: {},
validApiKeys: [],
revocationList: require('./cache').init(),
revocationList: new NodeCache(),
/**
* Initialize the authentication module
@ -64,7 +65,7 @@ module.exports = {
// Load JWT
passport.use('jwt', new passportJWT.Strategy({
jwtFromRequest: securityHelper.extractJWT,
jwtFromRequest: extractJWT,
secretOrKey: WIKI.config.auth.certs.public,
audience: WIKI.config.auth.audience,
issuer: 'urn:wiki.js',
@ -141,7 +142,7 @@ module.exports = {
// Revalidate and renew token
if (mustRevalidate) {
const jwtPayload = jwt.decode(securityHelper.extractJWT(req))
const jwtPayload = jwt.decode(extractJWT(req))
try {
const newToken = await WIKI.db.users.refreshToken(jwtPayload.id, jwtPayload.pvd)
user = newToken.user

@ -1,7 +0,0 @@
const NodeCache = require('node-cache')
module.exports = {
init() {
return new NodeCache()
}
}

@ -1,19 +1,19 @@
const _ = require('lodash')
const chalk = require('chalk')
const cfgHelper = require('../helpers/config')
const fs = require('fs')
const path = require('path')
const yaml = require('js-yaml')
module.exports = {
import { defaultsDeep, get, isPlainObject } from 'lodash-es'
import chalk from 'chalk'
import cfgHelper from '../helpers/config.mjs'
import regexData from '../app/regex.mjs'
import fs from 'node:fs/promises'
import path from 'node:path'
import yaml from 'js-yaml'
export default {
/**
* Load root config from disk
*/
init(silent = false) {
let confPaths = {
async init(silent = false) {
const confPaths = {
config: path.join(WIKI.ROOTPATH, 'config.yml'),
data: path.join(WIKI.SERVERPATH, 'app/data.yml'),
dataRegex: path.join(WIKI.SERVERPATH, 'app/regex.js')
data: path.join(WIKI.SERVERPATH, 'app/data.yml')
}
if (process.env.dockerdev) {
@ -34,11 +34,11 @@ module.exports = {
try {
appconfig = yaml.load(
cfgHelper.parseConfigValue(
fs.readFileSync(confPaths.config, 'utf8')
await fs.readFile(confPaths.config, 'utf8')
)
)
appdata = yaml.load(fs.readFileSync(confPaths.data, 'utf8'))
appdata.regex = require(confPaths.dataRegex)
appdata = yaml.load(await fs.readFile(confPaths.data, 'utf8'))
appdata.regex = regexData
if (!silent) {
console.info(chalk.green.bold(`OK`))
}
@ -52,7 +52,7 @@ module.exports = {
// Merge with defaults
appconfig = _.defaultsDeep(appconfig, appdata.defaults.config)
appconfig = defaultsDeep(appconfig, appdata.defaults.config)
// Override port
@ -66,7 +66,7 @@ module.exports = {
// Load package info
const packageInfo = require(path.join(WIKI.SERVERPATH, 'package.json'))
const packageInfo = JSON.parse(await fs.readFile(path.join(WIKI.SERVERPATH, 'package.json'), 'utf-8'))
// Load DB Password from Docker Secret File
if (process.env.DB_PASS_FILE) {
@ -74,7 +74,7 @@ module.exports = {
console.info(chalk.blue(`DB_PASS_FILE is defined. Will use secret from file.`))
}
try {
appconfig.db.pass = fs.readFileSync(process.env.DB_PASS_FILE, 'utf8').trim()
appconfig.db.pass = await fs.readFile(process.env.DB_PASS_FILE, 'utf8').trim()
} catch (err) {
console.error(chalk.red.bold(`>>> Failed to read Docker Secret File using path defined in DB_PASS_FILE env variable!`))
console.error(err.message)
@ -93,9 +93,9 @@ module.exports = {
* Load config from DB
*/
async loadFromDb() {
let conf = await WIKI.db.settings.getConfig()
const conf = await WIKI.db.settings.getConfig()
if (conf) {
WIKI.config = _.defaultsDeep(conf, WIKI.config)
WIKI.config = defaultsDeep(conf, WIKI.config)
} else {
WIKI.logger.warn('Missing DB Configuration!')
process.exit(1)
@ -110,8 +110,8 @@ module.exports = {
async saveToDb(keys, propagate = true) {
try {
for (let key of keys) {
let value = _.get(WIKI.config, key, null)
if (!_.isPlainObject(value)) {
let value = get(WIKI.config, key, null)
if (!isPlainObject(value)) {
value = { v: value }
}
let affectedRows = await WIKI.db.settings.query().patch({ value }).where('key', key)

@ -1,19 +1,18 @@
const _ = require('lodash')
const autoload = require('auto-load')
const path = require('path')
const Knex = require('knex')
const fs = require('fs')
const Objection = require('objection')
const PGPubSub = require('pg-pubsub')
const migrationSource = require('../db/migrator-source')
const migrateFromLegacy = require('../db/legacy')
const { setTimeout } = require('timers/promises')
import { get, has, isEmpty, isPlainObject } from 'lodash-es'
import path from 'node:path'
import knex from 'knex'
import fs from 'node:fs/promises'
import Objection from 'objection'
import PGPubSub from 'pg-pubsub'
import migrationSource from '../db/migrator-source.mjs'
// const migrateFromLegacy = require('../db/legacy')
import { setTimeout } from 'node:timers/promises'
/**
* ORM DB module
*/
module.exports = {
export default {
Objection,
knex: null,
listener: null,
@ -21,14 +20,14 @@ module.exports = {
/**
* Initialize DB
*/
init(workerMode = false) {
async init (workerMode = false) {
let self = this
WIKI.logger.info('Checking DB configuration...')
// Fetch DB Config
this.config = (!_.isEmpty(process.env.DATABASE_URL)) ? process.env.DATABASE_URL : {
this.config = (!isEmpty(process.env.DATABASE_URL)) ? process.env.DATABASE_URL : {
host: WIKI.config.db.host.toString(),
user: WIKI.config.db.user.toString(),
password: WIKI.config.db.pass.toString(),
@ -40,27 +39,27 @@ module.exports = {
let dbUseSSL = (WIKI.config.db.ssl === true || WIKI.config.db.ssl === 'true' || WIKI.config.db.ssl === 1 || WIKI.config.db.ssl === '1')
let sslOptions = null
if (dbUseSSL && _.isPlainObject(this.config) && _.get(WIKI.config.db, 'sslOptions.auto', null) === false) {
if (dbUseSSL && isPlainObject(this.config) && get(WIKI.config.db, 'sslOptions.auto', null) === false) {
sslOptions = WIKI.config.db.sslOptions
sslOptions.rejectUnauthorized = sslOptions.rejectUnauthorized !== false
if (sslOptions.ca && sslOptions.ca.indexOf('-----') !== 0) {
sslOptions.ca = fs.readFileSync(path.resolve(WIKI.ROOTPATH, sslOptions.ca))
sslOptions.ca = await fs.readFile(path.resolve(WIKI.ROOTPATH, sslOptions.ca), 'utf-8')
}
if (sslOptions.cert) {
sslOptions.cert = fs.readFileSync(path.resolve(WIKI.ROOTPATH, sslOptions.cert))
sslOptions.cert = await fs.readFile(path.resolve(WIKI.ROOTPATH, sslOptions.cert), 'utf-8')
}
if (sslOptions.key) {
sslOptions.key = fs.readFileSync(path.resolve(WIKI.ROOTPATH, sslOptions.key))
sslOptions.key = await fs.readFile(path.resolve(WIKI.ROOTPATH, sslOptions.key), 'utf-8')
}
if (sslOptions.pfx) {
sslOptions.pfx = fs.readFileSync(path.resolve(WIKI.ROOTPATH, sslOptions.pfx))
sslOptions.pfx = await fs.readFile(path.resolve(WIKI.ROOTPATH, sslOptions.pfx), 'utf-8')
}
} else {
sslOptions = true
}
// Handle inline SSL CA Certificate mode
if (!_.isEmpty(process.env.DB_SSL_CA)) {
if (!isEmpty(process.env.DB_SSL_CA)) {
const chunks = []
for (let i = 0, charsLength = process.env.DB_SSL_CA.length; i < charsLength; i += 64) {
chunks.push(process.env.DB_SSL_CA.substring(i, i + 64))
@ -73,12 +72,12 @@ module.exports = {
}
}
if (dbUseSSL && _.isPlainObject(this.config)) {
if (dbUseSSL && isPlainObject(this.config)) {
this.config.ssl = (sslOptions === true) ? { rejectUnauthorized: true } : sslOptions
}
// Initialize Knex
this.knex = Knex({
this.knex = knex({
client: 'pg',
useNullAsDefault: true,
asyncStackTraces: WIKI.IS_DEBUG,
@ -104,11 +103,11 @@ module.exports = {
// Load DB Models
WIKI.logger.info('Loading DB models...')
const models = autoload(path.join(WIKI.SERVERPATH, 'models'))
const models = (await import(path.join(WIKI.SERVERPATH, 'models/index.mjs'))).default
// Set init tasks
let conAttempts = 0
let initTasks = {
const initTasks = {
// -> Attempt initial connection
async connect () {
try {
@ -143,7 +142,7 @@ module.exports = {
},
// -> Migrate DB Schemas from 2.x
async migrateFromLegacy () {
return migrateFromLegacy.migrate(self.knex)
// return migrateFromLegacy.migrate(self.knex)
}
}
@ -184,7 +183,7 @@ module.exports = {
// -> Outbound events handling
this.listener.addChannel('wiki', payload => {
if (_.has(payload, 'event') && payload.source !== WIKI.INSTANCE_ID) {
if (has(payload, 'event') && payload.source !== WIKI.INSTANCE_ID) {
WIKI.logger.info(`Received event ${payload.event} from instance ${payload.source}: [ OK ]`)
WIKI.events.inbound.emit(payload.event, payload.value)
}

@ -1,13 +1,13 @@
const fs = require('fs-extra')
const path = require('path')
import fs from 'node:fs/promises'
import path from 'path'
module.exports = {
export default {
ext: {},
async init () {
const extDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/extensions'))
WIKI.logger.info(`Checking for installed optional extensions...`)
for (let dir of extDirs) {
WIKI.extensions.ext[dir] = require(path.join(WIKI.SERVERPATH, 'modules/extensions', dir, 'ext.js'))
for (const dir of extDirs) {
WIKI.extensions.ext[dir] = (await import(path.join(WIKI.SERVERPATH, 'modules/extensions', dir, 'ext.mjs'))).default
const isInstalled = await WIKI.extensions.ext[dir].check()
if (isInstalled) {
WIKI.logger.info(`Optional extension ${dir} is installed. [ OK ]`)

@ -1,16 +1,24 @@
const _ = require('lodash')
const EventEmitter = require('eventemitter2').EventEmitter2
import { padEnd } from 'lodash-es'
import eventemitter2 from 'eventemitter2'
import NodeCache from 'node-cache'
import asar from './asar.mjs'
import db from './db.mjs'
import extensions from './extensions.mjs'
import scheduler from './scheduler.mjs'
import servers from './servers.mjs'
let isShuttingDown = false
module.exports = {
export default {
async init() {
WIKI.logger.info('=======================================')
WIKI.logger.info(`= Wiki.js ${_.padEnd(WIKI.version + ' ', 29, '=')}`)
WIKI.logger.info(`= Wiki.js ${padEnd(WIKI.version + ' ', 29, '=')}`)
WIKI.logger.info('=======================================')
WIKI.logger.info('Initializing...')
WIKI.logger.info(`Running node.js ${process.version}`)
WIKI.db = require('./db').init()
WIKI.db = await db.init()
try {
await WIKI.db.onReady
@ -31,15 +39,15 @@ module.exports = {
*/
async preBootWeb() {
try {
WIKI.cache = require('./cache').init()
WIKI.scheduler = await require('./scheduler').init()
WIKI.servers = require('./servers')
WIKI.cache = new NodeCache()
WIKI.scheduler = await scheduler.init()
WIKI.servers = servers
WIKI.events = {
inbound: new EventEmitter(),
outbound: new EventEmitter()
inbound: new eventemitter2.EventEmitter2(),
outbound: new eventemitter2.EventEmitter2()
}
WIKI.extensions = require('./extensions')
WIKI.asar = require('./asar')
WIKI.extensions = extensions
WIKI.asar = asar
} catch (err) {
WIKI.logger.error(err)
process.exit(1)
@ -51,7 +59,7 @@ module.exports = {
async bootWeb() {
try {
await this.preBootWeb()
await require('../web')()
await (await import('../web.mjs')).init()
this.postBootWeb()
} catch (err) {
WIKI.logger.error(err)

@ -1,60 +0,0 @@
const chalk = require('chalk')
const EventEmitter = require('events')
const LEVELS = ['error', 'warn', 'info', 'debug']
const LEVELSIGNORED = ['verbose', 'silly']
const LEVELCOLORS = {
error: 'red',
warn: 'yellow',
info: 'green',
debug: 'cyan'
}
class Logger extends EventEmitter {}
const primaryLogger = new Logger()
let ignoreNextLevels = false
primaryLogger.ws = new EventEmitter()
LEVELS.forEach(lvl => {
primaryLogger[lvl] = (...args) => {
primaryLogger.emit(lvl, ...args)
}
if (!ignoreNextLevels) {
primaryLogger.on(lvl, (msg) => {
let formatted = ''
if (WIKI.config.logFormat === 'json') {
formatted = JSON.stringify({
timestamp: new Date().toISOString(),
instance: WIKI.INSTANCE_ID,
level: lvl,
message: msg
})
} else {
if (msg instanceof Error) {
msg = msg.stack
}
formatted = chalk`${new Date().toISOString()} {dim [${WIKI.INSTANCE_ID}]} {${LEVELCOLORS[lvl]}.bold ${lvl}}: ${msg}`
}
console.log(formatted)
primaryLogger.ws.emit('log', formatted)
})
}
if (lvl === WIKI.config.logLevel) {
ignoreNextLevels = true
}
})
LEVELSIGNORED.forEach(lvl => {
primaryLogger[lvl] = () => {}
})
module.exports = {
loggers: {},
init () {
return primaryLogger
}
}

@ -0,0 +1,61 @@
import chalk from 'chalk'
import EventEmitter from 'node:events'
const LEVELS = ['error', 'warn', 'info', 'debug']
const LEVELSIGNORED = ['verbose', 'silly']
const LEVELCOLORS = {
error: 'red',
warn: 'yellow',
info: 'green',
debug: 'cyan'
}
class Logger extends EventEmitter {}
export default {
loggers: {},
init () {
const primaryLogger = new Logger()
let ignoreNextLevels = false
primaryLogger.ws = new EventEmitter()
LEVELS.forEach(lvl => {
primaryLogger[lvl] = (...args) => {
primaryLogger.emit(lvl, ...args)
}
if (!ignoreNextLevels) {
primaryLogger.on(lvl, (msg) => {
let formatted = ''
if (WIKI.config.logFormat === 'json') {
formatted = JSON.stringify({
timestamp: new Date().toISOString(),
instance: WIKI.INSTANCE_ID,
level: lvl,
message: msg
})
} else {
if (msg instanceof Error) {
msg = msg.stack
}
formatted = `${new Date().toISOString()} ${chalk.dim('[' + WIKI.INSTANCE_ID + ']')} ${chalk[LEVELCOLORS[lvl]].bold(lvl)}: ${msg}`
}
console.log(formatted)
primaryLogger.ws.emit('log', formatted)
})
}
if (lvl === WIKI.config.logLevel) {
ignoreNextLevels = true
}
})
LEVELSIGNORED.forEach(lvl => {
primaryLogger[lvl] = () => {}
})
return primaryLogger
}
}

@ -1,13 +1,13 @@
const nodemailer = require('nodemailer')
const _ = require('lodash')
const fs = require('fs-extra')
const path = require('path')
import nodemailer from 'nodemailer'
import { get, has, kebabCase, set, template } from 'lodash-es'
import fs from 'node:fs/promises'
import path from 'node:path'
module.exports = {
export default {
transport: null,
templates: {},
init() {
if (_.get(WIKI.config, 'mail.host', '').length > 2) {
if (get(WIKI.config, 'mail.host', '').length > 2) {
let conf = {
host: WIKI.config.mail.host,
port: WIKI.config.mail.port,
@ -17,7 +17,7 @@ module.exports = {
rejectUnauthorized: !(WIKI.config.mail.verifySSL === false)
}
}
if (_.get(WIKI.config, 'mail.user', '').length > 1) {
if (get(WIKI.config, 'mail.user', '').length > 1) {
conf = {
...conf,
auth: {
@ -26,7 +26,7 @@ module.exports = {
}
}
}
if (_.get(WIKI.config, 'mail.useDKIM', false)) {
if (get(WIKI.config, 'mail.useDKIM', false)) {
conf = {
...conf,
dkim: {
@ -57,7 +57,7 @@ module.exports = {
to: opts.to,
subject: `${opts.subject} - ${WIKI.config.title}`,
text: opts.text,
html: _.get(this.templates, opts.template)({
html: get(this.templates, opts.template)({
logo: (WIKI.config.logoUrl.startsWith('http') ? '' : WIKI.config.host) + WIKI.config.logoUrl,
siteTitle: WIKI.config.title,
copyright: WIKI.config.company.length > 0 ? WIKI.config.company : 'Powered by Wiki.js',
@ -66,11 +66,11 @@ module.exports = {
})
},
async loadTemplate(key) {
if (_.has(this.templates, key)) { return }
const keyKebab = _.kebabCase(key)
if (has(this.templates, key)) { return }
const keyKebab = kebabCase(key)
try {
const rawTmpl = await fs.readFile(path.join(WIKI.SERVERPATH, `templates/${keyKebab}.html`), 'utf8')
_.set(this.templates, key, _.template(rawTmpl))
set(this.templates, key, template(rawTmpl))
} catch (err) {
WIKI.logger.warn(err)
throw new WIKI.Error.MailTemplateFailed()

@ -1,14 +1,14 @@
const { DynamicThreadPool } = require('poolifier')
const os = require('node:os')
const autoload = require('auto-load')
const path = require('node:path')
const cronparser = require('cron-parser')
const { DateTime } = require('luxon')
const { v4: uuid } = require('uuid')
const { createDeferred } = require('../helpers/common')
const _ = require('lodash')
import { DynamicThreadPool } from 'poolifier'
import os from 'node:os'
import fs from 'node:fs/promises'
import path from 'node:path'
import cronparser from 'cron-parser'
import { DateTime } from 'luxon'
import { v4 as uuid } from 'uuid'
import { createDeferred } from '../helpers/common.mjs'
import { find, remove } from 'lodash-es'
module.exports = {
export default {
workerPool: null,
maxWorkers: 1,
activeWorkers: 0,
@ -25,7 +25,11 @@ module.exports = {
exitHandler: () => WIKI.logger.debug('A worker has gone offline.'),
onlineHandler: () => WIKI.logger.debug('New worker is online.')
})
this.tasks = autoload(path.join(WIKI.SERVERPATH, 'tasks/simple'))
this.tasks = {}
for (const f of (await fs.readdir(path.join(WIKI.SERVERPATH, 'tasks/simple')))) {
const taskName = f.replace('.mjs', '')
this.tasks[taskName] = (await import(path.join(WIKI.SERVERPATH, 'tasks/simple', f))).task
}
return this
},
async start () {
@ -43,7 +47,7 @@ module.exports = {
break
}
case 'jobCompleted': {
const jobPromise = _.find(this.completionPromises, ['id', payload.id])
const jobPromise = find(this.completionPromises, ['id', payload.id])
if (jobPromise) {
if (payload.state === 'success') {
jobPromise.resolve()
@ -51,7 +55,7 @@ module.exports = {
jobPromise.reject(new Error(payload.errorMessage))
}
setTimeout(() => {
_.remove(this.completionPromises, ['id', payload.id])
remove(this.completionPromises, ['id', payload.id])
})
}
break

@ -1,13 +1,15 @@
const fs = require('fs-extra')
const http = require('http')
const https = require('https')
const { ApolloServer } = require('apollo-server-express')
const _ = require('lodash')
const io = require('socket.io')
const { ApolloServerPluginLandingPageGraphQLPlayground, ApolloServerPluginLandingPageProductionDefault } = require('apollo-server-core')
const { graphqlUploadExpress } = require('graphql-upload')
import fs from 'node:fs/promises'
import http from 'node:http'
import https from 'node:https'
import { ApolloServer } from 'apollo-server-express'
import { isEmpty } from 'lodash-es'
import { Server as IoServer } from 'socket.io'
import { ApolloServerPluginLandingPageGraphQLPlayground, ApolloServerPluginLandingPageProductionDefault } from 'apollo-server-core'
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'
module.exports = {
import { initSchema } from '../graph/index.mjs'
export default {
graph: null,
http: null,
https: null,
@ -69,15 +71,15 @@ module.exports = {
const tlsOpts = {}
try {
if (WIKI.config.ssl.format === 'pem') {
tlsOpts.key = WIKI.config.ssl.inline ? WIKI.config.ssl.key : fs.readFileSync(WIKI.config.ssl.key)
tlsOpts.cert = WIKI.config.ssl.inline ? WIKI.config.ssl.cert : fs.readFileSync(WIKI.config.ssl.cert)
tlsOpts.key = WIKI.config.ssl.inline ? WIKI.config.ssl.key : await fs.readFile(WIKI.config.ssl.key, 'utf-8')
tlsOpts.cert = WIKI.config.ssl.inline ? WIKI.config.ssl.cert : await fs.readFile(WIKI.config.ssl.cert, 'utf-8')
} else {
tlsOpts.pfx = WIKI.config.ssl.inline ? WIKI.config.ssl.pfx : fs.readFileSync(WIKI.config.ssl.pfx)
tlsOpts.pfx = WIKI.config.ssl.inline ? WIKI.config.ssl.pfx : await fs.readFile(WIKI.config.ssl.pfx, 'utf-8')
}
if (!_.isEmpty(WIKI.config.ssl.passphrase)) {
if (!isEmpty(WIKI.config.ssl.passphrase)) {
tlsOpts.passphrase = WIKI.config.ssl.passphrase
}
if (!_.isEmpty(WIKI.config.ssl.dhparam)) {
if (!isEmpty(WIKI.config.ssl.dhparam)) {
tlsOpts.dhparam = WIKI.config.ssl.dhparam
}
} catch (err) {
@ -127,7 +129,7 @@ module.exports = {
* Start GraphQL Server
*/
async startGraphQL () {
const graphqlSchema = require('../graph')
const graphqlSchema = await initSchema()
this.graph = new ApolloServer({
schema: graphqlSchema,
csrfPrevention: true,
@ -155,13 +157,13 @@ module.exports = {
*/
async initWebSocket() {
if (this.https) {
this.ws = new io.Server(this.https, {
this.ws = new IoServer(this.https, {
path: '/_ws/',
serveClient: false
})
WIKI.logger.info(`WebSocket Server attached to HTTPS Server [ OK ]`)
} else {
this.ws = new io.Server(this.http, {
this.ws = new IoServer(this.http, {
path: '/_ws/',
serveClient: false,
cors: true // TODO: dev only, replace with app settings once stable

@ -1,21 +0,0 @@
const fs = require('fs-extra')
const path = require('path')
module.exports = {
updates: {
channel: 'BETA',
version: WIKI.version,
releaseDate: WIKI.releaseDate,
minimumVersionRequired: '3.0.0-beta.0',
minimumNodeRequired: '18.0.0'
},
init () {
fs.ensureDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'assets'))
fs.ensureDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'uploads'))
// Clear content cache
fs.emptyDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'cache'))
return this
}
}

@ -0,0 +1,21 @@
import fse from 'fs-extra'
import path from 'node:path'
export default {
updates: {
channel: 'BETA',
version: WIKI.version,
releaseDate: WIKI.releaseDate,
minimumVersionRequired: '3.0.0-beta.0',
minimumNodeRequired: '18.0.0'
},
init () {
fse.ensureDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'assets'))
fse.ensureDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'uploads'))
// Clear content cache
fse.emptyDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'cache'))
return this
}
}

@ -1,10 +1,10 @@
const { v4: uuid } = require('uuid')
const bcrypt = require('bcryptjs-then')
const crypto = require('crypto')
const { DateTime } = require('luxon')
const pem2jwk = require('pem-jwk').pem2jwk
import { v4 as uuid } from 'uuid'
import bcrypt from 'bcryptjs-then'
import crypto from 'node:crypto'
import { DateTime } from 'luxon'
import { pem2jwk } from 'pem-jwk'
exports.up = async knex => {
export async function up (knex) {
WIKI.logger.info('Running 3.0.0 database migration...')
// =====================================
@ -778,4 +778,4 @@ exports.up = async knex => {
WIKI.logger.info('Completed 3.0.0 database migration.')
}
exports.down = knex => { }
export function down (knex) { }

@ -1,27 +0,0 @@
const path = require('path')
const fs = require('fs-extra')
const semver = require('semver')
const baseMigrationPath = path.join(WIKI.SERVERPATH, 'db/migrations')
module.exports = {
/**
* Gets the migration names
* @returns Promise<string[]>
*/
async getMigrations() {
const migrationFiles = await fs.readdir(baseMigrationPath)
return migrationFiles.map(m => m.replace('.js', '')).sort(semver.compare).map(m => ({
file: m,
directory: baseMigrationPath
}))
},
getMigrationName(migration) {
return migration.file.indexOf('.js') >= 0 ? migration.file : `${migration.file}.js`
},
getMigration(migration) {
return require(path.join(baseMigrationPath, migration.file))
}
}

@ -0,0 +1,26 @@
import path from 'node:path'
import fse from 'fs-extra'
import semver from 'semver'
export default {
/**
* Gets the migration names
* @returns Promise<string[]>
*/
async getMigrations() {
const baseMigrationPath = path.join(WIKI.SERVERPATH, 'db/migrations')
const migrationFiles = await fse.readdir(baseMigrationPath)
return migrationFiles.map(m => m.replace('.mjs', '')).sort(semver.compare).map(m => ({
file: m,
directory: baseMigrationPath
}))
},
getMigrationName(migration) {
return migration.file.indexOf('.mjs') >= 0 ? migration.file : `${migration.file}.mjs`
},
async getMigration(migration) {
return import(path.join(WIKI.SERVERPATH, 'db/migrations', `${migration.file}.mjs`))
}
}

@ -1,54 +0,0 @@
const _ = require('lodash')
const fs = require('fs')
const path = require('path')
const autoload = require('auto-load')
const { makeExecutableSchema } = require('@graphql-tools/schema')
const { defaultKeyGenerator, rateLimitDirective } = require('graphql-rate-limit-directive')
const { GraphQLUpload } = require('graphql-upload')
// Rate Limiter
const { rateLimitDirectiveTypeDefs, rateLimitDirectiveTransformer } = rateLimitDirective({
keyGenerator: (directiveArgs, source, args, context, info) => `${context.req.ip}:${defaultKeyGenerator(directiveArgs, source, args, context, info)}`
})
// Schemas
WIKI.logger.info(`Loading GraphQL Schema...`)
const typeDefs = [
rateLimitDirectiveTypeDefs
]
const schemas = fs.readdirSync(path.join(WIKI.SERVERPATH, 'graph/schemas'))
schemas.forEach(schema => {
typeDefs.push(fs.readFileSync(path.join(WIKI.SERVERPATH, `graph/schemas/${schema}`), 'utf8'))
})
// Resolvers
WIKI.logger.info(`Loading GraphQL Resolvers...`)
let resolvers = {
Date: require('./scalars/date'),
JSON: require('./scalars/json'),
UUID: require('./scalars/uuid'),
Upload: GraphQLUpload
}
const resolversObj = _.values(autoload(path.join(WIKI.SERVERPATH, 'graph/resolvers')))
resolversObj.forEach(resolver => {
_.merge(resolvers, resolver)
})
// Make executable schema
WIKI.logger.info(`Compiling GraphQL Schema...`)
let schema = makeExecutableSchema({
typeDefs,
resolvers
})
// Apply schema transforms
schema = rateLimitDirectiveTransformer(schema)
WIKI.logger.info(`GraphQL Schema: [ OK ]`)
module.exports = schema

@ -0,0 +1,61 @@
import { merge } from 'lodash-es'
import fs from 'node:fs/promises'
import path from 'node:path'
import { makeExecutableSchema } from '@graphql-tools/schema'
import { defaultKeyGenerator, rateLimitDirective } from 'graphql-rate-limit-directive'
import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs'
import DateScalar from './scalars/date.mjs'
import JSONScalar from './scalars/json.mjs'
import UUIDScalar from './scalars/uuid.mjs'
export async function initSchema () {
// Rate Limiter
const { rateLimitDirectiveTypeDefs, rateLimitDirectiveTransformer } = rateLimitDirective({
keyGenerator: (directiveArgs, source, args, context, info) => `${context.req.ip}:${defaultKeyGenerator(directiveArgs, source, args, context, info)}`
})
// Schemas
WIKI.logger.info(`Loading GraphQL Schema...`)
const typeDefs = [
rateLimitDirectiveTypeDefs
]
const schemaList = await fs.readdir(path.join(WIKI.SERVERPATH, 'graph/schemas'))
for (const schemaFile of schemaList) {
typeDefs.push(await fs.readFile(path.join(WIKI.SERVERPATH, `graph/schemas/${schemaFile}`), 'utf8'))
}
// Resolvers
WIKI.logger.info(`Loading GraphQL Resolvers...`)
let resolvers = {
Date: DateScalar,
JSON: JSONScalar,
UUID: UUIDScalar,
Upload: GraphQLUpload
}
const resolverList = await fs.readdir(path.join(WIKI.SERVERPATH, 'graph/resolvers'))
for (const resolverFile of resolverList) {
const resolver = (await import(path.join(WIKI.SERVERPATH, 'graph/resolvers', resolverFile))).default
merge(resolvers, resolver)
}
// Make executable schema
WIKI.logger.info(`Compiling GraphQL Schema...`)
let schema = makeExecutableSchema({
typeDefs,
resolvers
})
// Apply schema transforms
schema = rateLimitDirectiveTransformer(schema)
WIKI.logger.info(`GraphQL Schema: [ OK ]`)
return schema
}

@ -1,17 +1,17 @@
const _ = require('lodash')
const graphHelper = require('../../helpers/graph')
import { find, get, reduce, set, sortBy, transform } from 'lodash-es'
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
module.exports = {
export default {
Query: {
async analyticsProviders(obj, args, context, info) {
let providers = await WIKI.db.analytics.getProviders(args.isEnabled)
providers = providers.map(stg => {
const providerInfo = _.find(WIKI.data.analytics, ['key', stg.key]) || {}
const providerInfo = find(WIKI.data.analytics, ['key', stg.key]) || {}
return {
...providerInfo,
...stg,
config: _.sortBy(_.transform(stg.config, (res, value, key) => {
const configData = _.get(providerInfo.props, key, {})
config: sortBy(transform(stg.config, (res, value, key) => {
const configData = get(providerInfo.props, key, {})
res.push({
key,
value: JSON.stringify({
@ -31,18 +31,18 @@ module.exports = {
for (let str of args.providers) {
await WIKI.db.analytics.query().patch({
isEnabled: str.isEnabled,
config: _.reduce(str.config, (result, value, key) => {
_.set(result, `${value.key}`, _.get(JSON.parse(value.value), 'v', null))
config: reduce(str.config, (result, value, key) => {
set(result, `${value.key}`, get(JSON.parse(value.value), 'v', null))
return result
}, {})
}).where('key', str.key)
await WIKI.cache.del('analytics')
}
return {
responseResult: graphHelper.generateSuccess('Providers updated successfully')
responseResult: generateSuccess('Providers updated successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
}
}

@ -1,12 +1,12 @@
const _ = require('lodash')
const sanitize = require('sanitize-filename')
const graphHelper = require('../../helpers/graph')
const path = require('node:path')
const fs = require('fs-extra')
const { v4: uuid } = require('uuid')
const { pipeline } = require('node:stream/promises')
module.exports = {
import _ from 'lodash-es'
import sanitize from 'sanitize-filename'
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
import path from 'node:path'
import fs from 'fs-extra'
import { v4 as uuid } from 'uuid'
import { pipeline } from 'node:stream/promises'
export default {
Query: {
async assetById(obj, args, context) {
return null
@ -83,13 +83,13 @@ module.exports = {
})
return {
responseResult: graphHelper.generateSuccess('Asset has been renamed successfully.')
responseResult: generateSuccess('Asset has been renamed successfully.')
}
} else {
throw new WIKI.Error.AssetInvalid()
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -122,13 +122,13 @@ module.exports = {
})
return {
responseResult: graphHelper.generateSuccess('Asset has been deleted successfully.')
responseResult: generateSuccess('Asset has been deleted successfully.')
}
} else {
throw new WIKI.Error.AssetInvalid()
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -357,13 +357,13 @@ module.exports = {
} else {
WIKI.logger.debug('Asset(s) uploaded successfully.')
return {
operation: graphHelper.generateSuccess('Asset(s) uploaded successfully')
operation: generateSuccess('Asset(s) uploaded successfully')
}
}
}
} catch (err) {
WIKI.logger.warn(err)
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -373,10 +373,10 @@ module.exports = {
try {
await WIKI.db.assets.flushTempUploads()
return {
responseResult: graphHelper.generateSuccess('Temporary Uploads have been flushed successfully.')
responseResult: generateSuccess('Temporary Uploads have been flushed successfully.')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
}
}

@ -1,7 +1,7 @@
const _ = require('lodash')
const graphHelper = require('../../helpers/graph')
import _ from 'lodash-es'
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
module.exports = {
export default {
Query: {
/**
* List of API Keys
@ -67,11 +67,11 @@ module.exports = {
WIKI.events.outbound.emit('reloadApiKeys')
return {
key,
operation: graphHelper.generateSuccess('API Key created successfully')
operation: generateSuccess('API Key created successfully')
}
} catch (err) {
WIKI.logger.warn(err)
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -82,7 +82,7 @@ module.exports = {
const authResult = await WIKI.db.users.login(args, context)
return {
...authResult,
operation: graphHelper.generateSuccess('Login success')
operation: generateSuccess('Login success')
}
} catch (err) {
// LDAP Debug Flag
@ -91,7 +91,7 @@ module.exports = {
}
console.error(err)
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -102,10 +102,10 @@ module.exports = {
const authResult = await WIKI.db.users.loginTFA(args, context)
return {
...authResult,
responseResult: graphHelper.generateSuccess('TFA success')
responseResult: generateSuccess('TFA success')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -116,10 +116,10 @@ module.exports = {
const authResult = await WIKI.db.users.loginChangePassword(args, context)
return {
...authResult,
responseResult: graphHelper.generateSuccess('Password changed successfully')
responseResult: generateSuccess('Password changed successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -129,10 +129,10 @@ module.exports = {
try {
await WIKI.db.users.loginForgotPassword(args, context)
return {
responseResult: graphHelper.generateSuccess('Password reset request processed.')
responseResult: generateSuccess('Password reset request processed.')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -142,10 +142,10 @@ module.exports = {
try {
await WIKI.db.users.register({ ...args, verify: true }, context)
return {
responseResult: graphHelper.generateSuccess('Registration success')
responseResult: generateSuccess('Registration success')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -156,10 +156,10 @@ module.exports = {
WIKI.config.api.isEnabled = args.enabled
await WIKI.configSvc.saveToDb(['api'])
return {
operation: graphHelper.generateSuccess('API State changed successfully')
operation: generateSuccess('API State changed successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -173,10 +173,10 @@ module.exports = {
await WIKI.auth.reloadApiKeys()
WIKI.events.outbound.emit('reloadApiKeys')
return {
operation: graphHelper.generateSuccess('API Key revoked successfully')
operation: generateSuccess('API Key revoked successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -226,10 +226,10 @@ module.exports = {
await WIKI.auth.activateStrategies()
WIKI.events.outbound.emit('reloadAuthStrategies')
return {
responseResult: graphHelper.generateSuccess('Strategies updated successfully')
responseResult: generateSuccess('Strategies updated successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -239,10 +239,10 @@ module.exports = {
try {
await WIKI.auth.regenerateCertificates()
return {
responseResult: graphHelper.generateSuccess('Certificates have been regenerated successfully.')
responseResult: generateSuccess('Certificates have been regenerated successfully.')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -252,10 +252,10 @@ module.exports = {
try {
await WIKI.auth.resetGuestUser()
return {
responseResult: graphHelper.generateSuccess('Guest user has been reset successfully.')
responseResult: generateSuccess('Guest user has been reset successfully.')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
}
},

@ -1,7 +1,7 @@
const _ = require('lodash')
const graphHelper = require('../../helpers/graph')
import _ from 'lodash-es'
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
module.exports = {
export default {
Query: {
/**
* Fetch list of Comments Providers
@ -90,11 +90,11 @@ module.exports = {
ip: context.req.ip
})
return {
responseResult: graphHelper.generateSuccess('New comment posted successfully'),
responseResult: generateSuccess('New comment posted successfully'),
id: cmId
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -108,11 +108,11 @@ module.exports = {
ip: context.req.ip
})
return {
responseResult: graphHelper.generateSuccess('Comment updated successfully'),
responseResult: generateSuccess('Comment updated successfully'),
render: cmRender
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -126,10 +126,10 @@ module.exports = {
ip: context.req.ip
})
return {
responseResult: graphHelper.generateSuccess('Comment deleted successfully')
responseResult: generateSuccess('Comment deleted successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -148,10 +148,10 @@ module.exports = {
}
await WIKI.db.commentProviders.initProvider()
return {
responseResult: graphHelper.generateSuccess('Comment Providers updated successfully')
responseResult: generateSuccess('Comment Providers updated successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
}
}

@ -1,9 +1,9 @@
const graphHelper = require('../../helpers/graph')
const safeRegex = require('safe-regex')
const _ = require('lodash')
const { v4: uuid } = require('uuid')
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
import safeRegex from 'safe-regex'
import _ from 'lodash-es'
import { v4 as uuid } from 'uuid'
module.exports = {
export default {
Query: {
/**
* FETCH ALL GROUPS
@ -71,7 +71,7 @@ module.exports = {
WIKI.events.outbound.emit('addAuthRevoke', { id: usr.id, kind: 'u' })
return {
operation: graphHelper.generateSuccess('User has been assigned to group.')
operation: generateSuccess('User has been assigned to group.')
}
},
/**
@ -90,7 +90,7 @@ module.exports = {
await WIKI.auth.reloadGroups()
WIKI.events.outbound.emit('reloadGroups')
return {
operation: graphHelper.generateSuccess('Group created successfully.'),
operation: generateSuccess('Group created successfully.'),
group
}
},
@ -111,7 +111,7 @@ module.exports = {
WIKI.events.outbound.emit('reloadGroups')
return {
operation: graphHelper.generateSuccess('Group has been deleted.')
operation: generateSuccess('Group has been deleted.')
}
},
/**
@ -138,7 +138,7 @@ module.exports = {
WIKI.events.outbound.emit('addAuthRevoke', { id: usr.id, kind: 'u' })
return {
operation: graphHelper.generateSuccess('User has been unassigned from group.')
operation: generateSuccess('User has been unassigned from group.')
}
},
/**
@ -193,7 +193,7 @@ module.exports = {
WIKI.events.outbound.emit('reloadGroups')
return {
operation: graphHelper.generateSuccess('Group has been updated.')
operation: generateSuccess('Group has been updated.')
}
}
},

@ -1,7 +1,7 @@
const graphHelper = require('../../helpers/graph')
const _ = require('lodash')
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
import _ from 'lodash-es'
module.exports = {
export default {
Query: {
async hooks () {
return WIKI.db.hooks.query().orderBy('name')
@ -31,11 +31,11 @@ module.exports = {
WIKI.logger.debug(`New Hook ${newHook.id} created successfully.`)
return {
operation: graphHelper.generateSuccess('Hook created successfully'),
operation: generateSuccess('Hook created successfully'),
hook: newHook
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -63,10 +63,10 @@ module.exports = {
WIKI.logger.debug(`Hook ${args.id} updated successfully.`)
return {
operation: graphHelper.generateSuccess('Hook updated successfully')
operation: generateSuccess('Hook updated successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -77,10 +77,10 @@ module.exports = {
await WIKI.db.hooks.deleteHook(args.id)
WIKI.logger.debug(`Hook ${args.id} deleted successfully.`)
return {
operation: graphHelper.generateSuccess('Hook deleted successfully')
operation: generateSuccess('Hook deleted successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
}
}

@ -1,7 +1,7 @@
const graphHelper = require('../../helpers/graph')
const _ = require('lodash')
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
import _ from 'lodash-es'
module.exports = {
export default {
Query: {
async locales(obj, args, context, info) {
let remoteLocales = await WIKI.cache.get('locales')
@ -29,10 +29,10 @@ module.exports = {
}, args.locale)
await job.finished
return {
responseResult: graphHelper.generateSuccess('Locale downloaded successfully')
responseResult: generateSuccess('Locale downloaded successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
async updateLocale(obj, args, context) {
@ -53,10 +53,10 @@ module.exports = {
await WIKI.cache.del('nav:locales')
return {
responseResult: graphHelper.generateSuccess('Locale config updated')
responseResult: generateSuccess('Locale config updated')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
}
}

@ -1,7 +1,7 @@
const _ = require('lodash')
const graphHelper = require('../../helpers/graph')
import _ from 'lodash-es'
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
module.exports = {
export default {
Query: {
async mailConfig(obj, args, context, info) {
return {
@ -28,10 +28,10 @@ module.exports = {
})
return {
operation: graphHelper.generateSuccess('Test email sent successfully.')
operation: generateSuccess('Test email sent successfully.')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
async updateMailConfig(obj, args, context) {
@ -56,10 +56,10 @@ module.exports = {
WIKI.mail.init()
return {
operation: graphHelper.generateSuccess('Mail configuration updated successfully.')
operation: generateSuccess('Mail configuration updated successfully.')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
}
}

@ -1,6 +1,6 @@
const graphHelper = require('../../helpers/graph')
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
module.exports = {
export default {
Query: {
async navigationTree (obj, args, context, info) {
return WIKI.db.navigation.getTree({ cache: false, locale: 'all', bypassAuth: true })
@ -20,10 +20,10 @@ module.exports = {
}
return {
responseResult: graphHelper.generateSuccess('Navigation updated successfully')
responseResult: generateSuccess('Navigation updated successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
async updateNavigationConfig (obj, args, context) {
@ -34,10 +34,10 @@ module.exports = {
await WIKI.configSvc.saveToDb(['nav'])
return {
responseResult: graphHelper.generateSuccess('Navigation config updated successfully')
responseResult: generateSuccess('Navigation config updated successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
}
}

@ -1,8 +1,8 @@
const _ = require('lodash')
const graphHelper = require('../../helpers/graph')
const pageHelper = require('../../helpers/page')
import _ from 'lodash-es'
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
import { parsePath }from '../../helpers/page.mjs'
module.exports = {
export default {
Query: {
/**
* PAGE HISTORY
@ -169,7 +169,7 @@ module.exports = {
*/
async pageByPath (obj, args, context, info) {
// console.info(info)
const pageArgs = pageHelper.parsePath(args.path)
const pageArgs = parsePath(args.path)
const page = await WIKI.db.pages.getPageFromDb({
...pageArgs,
siteId: args.siteId
@ -393,11 +393,11 @@ module.exports = {
user: context.req.user
})
return {
operation: graphHelper.generateSuccess('Page created successfully.'),
operation: generateSuccess('Page created successfully.'),
page
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -410,11 +410,11 @@ module.exports = {
user: context.req.user
})
return {
operation: graphHelper.generateSuccess('Page has been updated.'),
operation: generateSuccess('Page has been updated.'),
page
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -427,10 +427,10 @@ module.exports = {
user: context.req.user
})
return {
responseResult: graphHelper.generateSuccess('Page has been converted.')
responseResult: generateSuccess('Page has been converted.')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -443,10 +443,10 @@ module.exports = {
user: context.req.user
})
return {
responseResult: graphHelper.generateSuccess('Page has been moved.')
responseResult: generateSuccess('Page has been moved.')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -459,10 +459,10 @@ module.exports = {
user: context.req.user
})
return {
responseResult: graphHelper.generateSuccess('Page has been deleted.')
responseResult: generateSuccess('Page has been deleted.')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -478,10 +478,10 @@ module.exports = {
throw new Error('This tag does not exist.')
}
return {
responseResult: graphHelper.generateSuccess('Tag has been deleted.')
responseResult: generateSuccess('Tag has been deleted.')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -499,10 +499,10 @@ module.exports = {
throw new Error('This tag does not exist.')
}
return {
responseResult: graphHelper.generateSuccess('Tag has been updated successfully.')
responseResult: generateSuccess('Tag has been updated successfully.')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -513,10 +513,10 @@ module.exports = {
await WIKI.db.pages.flushCache()
WIKI.events.outbound.emit('flushCache')
return {
responseResult: graphHelper.generateSuccess('Pages Cache has been flushed successfully.')
responseResult: generateSuccess('Pages Cache has been flushed successfully.')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -526,11 +526,11 @@ module.exports = {
try {
const count = await WIKI.db.pages.migrateToLocale(args)
return {
responseResult: graphHelper.generateSuccess('Migrated content to target locale successfully.'),
responseResult: generateSuccess('Migrated content to target locale successfully.'),
count
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -540,10 +540,10 @@ module.exports = {
try {
await WIKI.db.pages.rebuildTree()
return {
responseResult: graphHelper.generateSuccess('Page tree rebuilt successfully.')
responseResult: generateSuccess('Page tree rebuilt successfully.')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -557,10 +557,10 @@ module.exports = {
}
await WIKI.db.pages.renderPage(page)
return {
responseResult: graphHelper.generateSuccess('Page rendered successfully.')
responseResult: generateSuccess('Page rendered successfully.')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -593,10 +593,10 @@ module.exports = {
})
return {
responseResult: graphHelper.generateSuccess('Page version restored successfully.')
responseResult: generateSuccess('Page version restored successfully.')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -606,10 +606,10 @@ module.exports = {
try {
await WIKI.db.pageHistory.purge(args.olderThan)
return {
responseResult: graphHelper.generateSuccess('Page history purged successfully.')
responseResult: generateSuccess('Page history purged successfully.')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
}
},

@ -1,7 +1,7 @@
const _ = require('lodash')
const graphHelper = require('../../helpers/graph')
import _ from 'lodash-es'
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
module.exports = {
export default {
Query: {
async renderers(obj, args, context, info) {
let renderers = await WIKI.db.renderers.getRenderers()
@ -42,10 +42,10 @@ module.exports = {
}).where('key', rdr.key)
}
return {
responseResult: graphHelper.generateSuccess('Renderers updated successfully')
responseResult: generateSuccess('Renderers updated successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
}
}

@ -1,16 +0,0 @@
const graphHelper = require('../../helpers/graph')
module.exports = {
Mutation: {
async rebuildSearchIndex (obj, args, context) {
try {
await WIKI.data.searchEngine.rebuild()
return {
responseResult: graphHelper.generateSuccess('Index rebuilt successfully')
}
} catch (err) {
return graphHelper.generateError(err)
}
}
}
}

@ -0,0 +1,16 @@
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
export default {
Mutation: {
async rebuildSearchIndex (obj, args, context) {
try {
await WIKI.data.searchEngine.rebuild()
return {
responseResult: generateSuccess('Index rebuilt successfully')
}
} catch (err) {
return generateError(err)
}
}
}
}

@ -1,11 +1,11 @@
const graphHelper = require('../../helpers/graph')
const _ = require('lodash')
const CleanCSS = require('clean-css')
const path = require('path')
const fs = require('fs-extra')
const { v4: uuid } = require('uuid')
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
import _ from 'lodash-es'
import CleanCSS from 'clean-css'
import path from 'node:path'
import fs from 'fs-extra'
import { v4 as uuid } from 'uuid'
module.exports = {
export default {
Query: {
async sites () {
const sites = await WIKI.db.sites.query().orderBy('hostname')
@ -72,12 +72,12 @@ module.exports = {
title: args.title
})
return {
operation: graphHelper.generateSuccess('Site created successfully'),
operation: generateSuccess('Site created successfully'),
site: newSite
}
} catch (err) {
WIKI.logger.warn(err)
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -117,11 +117,11 @@ module.exports = {
})
return {
operation: graphHelper.generateSuccess('Site updated successfully')
operation: generateSuccess('Site updated successfully')
}
} catch (err) {
WIKI.logger.warn(err)
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -137,11 +137,11 @@ module.exports = {
// -> Delete site
await WIKI.db.sites.deleteSite(args.id)
return {
operation: graphHelper.generateSuccess('Site deleted successfully')
operation: generateSuccess('Site deleted successfully')
}
} catch (err) {
WIKI.logger.warn(err)
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -197,11 +197,11 @@ module.exports = {
}).onConflict('id').merge()
WIKI.logger.info('New site logo processed successfully.')
return {
operation: graphHelper.generateSuccess('Site logo uploaded successfully')
operation: generateSuccess('Site logo uploaded successfully')
}
} catch (err) {
WIKI.logger.warn(err)
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -258,11 +258,11 @@ module.exports = {
}).onConflict('id').merge()
WIKI.logger.info('New site favicon processed successfully.')
return {
operation: graphHelper.generateSuccess('Site favicon uploaded successfully')
operation: generateSuccess('Site favicon uploaded successfully')
}
} catch (err) {
WIKI.logger.warn(err)
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -316,11 +316,11 @@ module.exports = {
}).onConflict('id').merge()
WIKI.logger.info('New site login bg processed successfully.')
return {
operation: graphHelper.generateSuccess('Site login bg uploaded successfully')
operation: generateSuccess('Site login bg uploaded successfully')
}
} catch (err) {
WIKI.logger.warn(err)
return graphHelper.generateError(err)
return generateError(err)
}
}
}

@ -1,8 +1,8 @@
const _ = require('lodash')
const graphHelper = require('../../helpers/graph')
const { v4: uuid } = require('uuid')
import _ from 'lodash-es'
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
import { v4 as uuid } from 'uuid'
module.exports = {
export default {
Query: {
async storageTargets (obj, args, context, info) {
const dbTargets = await WIKI.db.storage.getTargets({ siteId: args.siteId })
@ -164,10 +164,10 @@ module.exports = {
}
// await WIKI.db.storage.initTargets()
return {
status: graphHelper.generateSuccess('Storage targets updated successfully')
status: generateSuccess('Storage targets updated successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
async setupStorageTarget (obj, args, context) {
@ -186,11 +186,11 @@ module.exports = {
const result = await WIKI.storage.modules[md.key].setup(args.targetId, args.state)
return {
status: graphHelper.generateSuccess('Storage target setup step succeeded'),
status: generateSuccess('Storage target setup step succeeded'),
state: result
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
async destroyStorageTargetSetup (obj, args, context) {
@ -209,20 +209,20 @@ module.exports = {
await WIKI.storage.modules[md.key].setupDestroy(args.targetId)
return {
status: graphHelper.generateSuccess('Storage target setup configuration destroyed succesfully.')
status: generateSuccess('Storage target setup configuration destroyed succesfully.')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
async executeStorageAction (obj, args, context) {
try {
await WIKI.db.storage.executeAction(args.targetKey, args.handler)
return {
status: graphHelper.generateSuccess('Action completed.')
status: generateSuccess('Action completed.')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
}
}

@ -1,14 +1,16 @@
const _ = require('lodash')
const util = require('node:util')
const getos = util.promisify(require('getos'))
const os = require('node:os')
const filesize = require('filesize')
const path = require('path')
const fs = require('fs-extra')
const { DateTime } = require('luxon')
const graphHelper = require('../../helpers/graph')
import _ from 'lodash-es'
import util from 'node:util'
import getosSync from 'getos'
import os from 'node:os'
import filesize from 'filesize'
import path from 'node:path'
import fs from 'fs-extra'
import { DateTime } from 'luxon'
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
module.exports = {
const getos = util.promisify(getosSync)
export default {
Query: {
systemFlags () {
return WIKI.config.flags
@ -89,18 +91,18 @@ module.exports = {
throw new Error('Job has already entered active state or does not exist.')
}
return {
operation: graphHelper.generateSuccess('Cancelled job successfully.')
operation: generateSuccess('Cancelled job successfully.')
}
} catch (err) {
WIKI.logger.warn(err)
return graphHelper.generateError(err)
return generateError(err)
}
},
async disconnectWS (obj, args, context) {
WIKI.servers.ws.disconnectSockets(true)
WIKI.logger.info('All active websocket connections have been terminated.')
return {
operation: graphHelper.generateSuccess('All websocket connections closed successfully.')
operation: generateSuccess('All websocket connections closed successfully.')
}
},
async installExtension (obj, args, context) {
@ -108,10 +110,10 @@ module.exports = {
await WIKI.extensions.ext[args.key].install()
// TODO: broadcast ext install
return {
operation: graphHelper.generateSuccess('Extension installed successfully')
operation: generateSuccess('Extension installed successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
async retryJob (obj, args, context) {
@ -140,11 +142,11 @@ module.exports = {
})
WIKI.logger.info(`Job ${args.id} has been rescheduled [ OK ]`)
return {
operation: graphHelper.generateSuccess('Job rescheduled successfully.')
operation: generateSuccess('Job rescheduled successfully.')
}
} catch (err) {
WIKI.logger.warn(err)
return graphHelper.generateError(err)
return generateError(err)
}
},
async updateSystemFlags (obj, args, context) {
@ -155,7 +157,7 @@ module.exports = {
await WIKI.configSvc.applyFlags()
await WIKI.configSvc.saveToDb(['flags'])
return {
operation: graphHelper.generateSuccess('System Flags applied successfully')
operation: generateSuccess('System Flags applied successfully')
}
},
async updateSystemSecurity (obj, args, context) {
@ -163,7 +165,7 @@ module.exports = {
// TODO: broadcast config update
await WIKI.configSvc.saveToDb(['security'])
return {
operation: graphHelper.generateSuccess('System Security configuration applied successfully')
operation: generateSuccess('System Security configuration applied successfully')
}
}
},

@ -1,5 +1,5 @@
const _ = require('lodash')
const graphHelper = require('../../helpers/graph')
import _ from 'lodash-es'
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
const typeResolvers = {
folder: 'TreeItemFolder',
@ -10,7 +10,7 @@ const typeResolvers = {
const rePathName = /^[a-z0-9-]+$/
const reTitle = /^[^<>"]+$/
module.exports = {
export default {
Query: {
/**
* FETCH TREE
@ -162,11 +162,11 @@ module.exports = {
await WIKI.db.tree.createFolder(args)
return {
operation: graphHelper.generateSuccess('Folder created successfully')
operation: generateSuccess('Folder created successfully')
}
} catch (err) {
WIKI.logger.debug(`Failed to create folder: ${err.message}`)
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -177,11 +177,11 @@ module.exports = {
await WIKI.db.tree.renameFolder(args)
return {
operation: graphHelper.generateSuccess('Folder renamed successfully')
operation: generateSuccess('Folder renamed successfully')
}
} catch (err) {
WIKI.logger.debug(`Failed to rename folder ${args.folderId}: ${err.message}`)
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -192,11 +192,11 @@ module.exports = {
await WIKI.db.tree.deleteFolder(args.folderId)
return {
operation: graphHelper.generateSuccess('Folder deleted successfully')
operation: generateSuccess('Folder deleted successfully')
}
} catch (err) {
WIKI.logger.debug(`Failed to delete folder ${args.folderId}: ${err.message}`)
return graphHelper.generateError(err)
return generateError(err)
}
}
},

@ -1,9 +1,9 @@
const graphHelper = require('../../helpers/graph')
const _ = require('lodash')
const path = require('node:path')
const fs = require('fs-extra')
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
import _ from 'lodash-es'
import path from 'node:path'
import fs from 'fs-extra'
module.exports = {
export default {
Query: {
/**
* FETCH ALL USERS
@ -93,10 +93,10 @@ module.exports = {
await WIKI.db.users.createNewUser({ ...args, passwordRaw: args.password, isVerified: true })
return {
operation: graphHelper.generateSuccess('User created successfully')
operation: generateSuccess('User created successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
async deleteUser (obj, args) {
@ -110,13 +110,13 @@ module.exports = {
WIKI.events.outbound.emit('addAuthRevoke', { id: args.id, kind: 'u' })
return {
operation: graphHelper.generateSuccess('User deleted successfully')
operation: generateSuccess('User deleted successfully')
}
} catch (err) {
if (err.message.indexOf('foreign') >= 0) {
return graphHelper.generateError(new WIKI.Error.UserDeleteForeignConstraint())
return generateError(new WIKI.Error.UserDeleteForeignConstraint())
} else {
return graphHelper.generateError(err)
return generateError(err)
}
}
},
@ -125,10 +125,10 @@ module.exports = {
await WIKI.db.users.updateUser(args.id, args.patch)
return {
operation: graphHelper.generateSuccess('User updated successfully')
operation: generateSuccess('User updated successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
async verifyUser (obj, args) {
@ -136,10 +136,10 @@ module.exports = {
await WIKI.db.users.query().patch({ isVerified: true }).findById(args.id)
return {
operation: graphHelper.generateSuccess('User verified successfully')
operation: generateSuccess('User verified successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
async activateUser (obj, args) {
@ -147,10 +147,10 @@ module.exports = {
await WIKI.db.users.query().patch({ isActive: true }).findById(args.id)
return {
operation: graphHelper.generateSuccess('User activated successfully')
operation: generateSuccess('User activated successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
async deactivateUser (obj, args) {
@ -164,10 +164,10 @@ module.exports = {
WIKI.events.outbound.emit('addAuthRevoke', { id: args.id, kind: 'u' })
return {
operation: graphHelper.generateSuccess('User deactivated successfully')
operation: generateSuccess('User deactivated successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
async enableUserTFA (obj, args) {
@ -175,10 +175,10 @@ module.exports = {
await WIKI.db.users.query().patch({ tfaIsActive: true, tfaSecret: null }).findById(args.id)
return {
operation: graphHelper.generateSuccess('User 2FA enabled successfully')
operation: generateSuccess('User 2FA enabled successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
async disableUserTFA (obj, args) {
@ -186,10 +186,10 @@ module.exports = {
await WIKI.db.users.query().patch({ tfaIsActive: false, tfaSecret: null }).findById(args.id)
return {
operation: graphHelper.generateSuccess('User 2FA disabled successfully')
operation: generateSuccess('User 2FA disabled successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
resetUserPassword (obj, args) {
@ -235,10 +235,10 @@ module.exports = {
})
return {
operation: graphHelper.generateSuccess('User profile updated successfully')
operation: generateSuccess('User profile updated successfully')
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
async changePassword (obj, args, context) {
@ -270,11 +270,11 @@ module.exports = {
const newToken = await WIKI.db.users.refreshToken(usr)
return {
responseResult: graphHelper.generateSuccess('Password changed successfully'),
responseResult: generateSuccess('Password changed successfully'),
jwt: newToken.token
}
} catch (err) {
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -316,11 +316,11 @@ module.exports = {
}).onConflict('id').merge()
WIKI.logger.debug(`Processed user ${args.id} avatar successfully.`)
return {
operation: graphHelper.generateSuccess('User avatar uploaded successfully')
operation: generateSuccess('User avatar uploaded successfully')
}
} catch (err) {
WIKI.logger.warn(err)
return graphHelper.generateError(err)
return generateError(err)
}
},
/**
@ -333,11 +333,11 @@ module.exports = {
await WIKI.db.knex('userAvatars').where({ id: args.id }).del()
WIKI.logger.debug(`Cleared user ${args.id} avatar successfully.`)
return {
operation: graphHelper.generateSuccess('User avatar cleared successfully')
operation: generateSuccess('User avatar cleared successfully')
}
} catch (err) {
WIKI.logger.warn(err)
return graphHelper.generateError(err)
return generateError(err)
}
}
},

@ -1,12 +1,12 @@
const gql = require('graphql')
const { DateTime } = require('luxon')
import gql from 'graphql'
import { DateTime } from 'luxon'
function parseDateTime (value) {
const nDate = DateTime.fromISO(value)
return nDate.isValid ? nDate : null
}
module.exports = new gql.GraphQLScalarType({
export default new gql.GraphQLScalarType({
name: 'Date',
description: 'ISO date-time string at UTC',
parseValue(value) {

@ -1,4 +1,4 @@
const { Kind, GraphQLScalarType } = require('graphql')
import { Kind, GraphQLScalarType } from 'graphql'
function ensureObject (value) {
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
@ -39,7 +39,7 @@ function parseObject (typeName, ast, variables) {
return value
}
module.exports = new GraphQLScalarType({
export default new GraphQLScalarType({
name: 'JSON',
description:
'The `JSON` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).',

@ -1,4 +1,4 @@
const { Kind, GraphQLScalarType } = require('graphql')
import { Kind, GraphQLScalarType } from 'graphql'
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
const nilUUID = '00000000-0000-0000-0000-000000000000'
@ -7,7 +7,7 @@ function isUUID (value) {
return uuidRegex.test(value) || nilUUID === value
}
module.exports = new GraphQLScalarType({
export default new GraphQLScalarType({
name: 'UUID',
description: 'The `UUID` scalar type represents UUID values as specified by [RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122).',
serialize: (value) => {

@ -1,6 +1,6 @@
const AbstractClientStore = require('express-brute/lib/AbstractClientStore')
import AbstractClientStore from 'express-brute/lib/AbstractClientStore.js'
const KnexStore = module.exports = function (options) {
const KnexStore = function (options) {
options = options || Object.create(null)
AbstractClientStore.apply(this, arguments)
@ -146,3 +146,5 @@ KnexStore.defaultsKnex = {
filename: './brute-knex.sqlite'
}
}
export default KnexStore

@ -1,99 +0,0 @@
const _ = require('lodash')
const crypto = require('node:crypto')
module.exports = {
/* eslint-disable promise/param-names */
createDeferred () {
let result, resolve, reject
return {
resolve: function (value) {
if (resolve) {
resolve(value)
} else {
result = result || new Promise(function (r) { r(value) })
}
},
reject: function (reason) {
if (reject) {
reject(reason)
} else {
result = result || new Promise(function (x, j) { j(reason) })
}
},
promise: new Promise(function (r, j) {
if (result) {
r(result)
} else {
resolve = r
reject = j
}
})
}
},
/**
* Decode a tree path
*
* @param {string} str String to decode
* @returns Decoded tree path
*/
decodeTreePath (str) {
return str.replaceAll('_', '-').replaceAll('.', '/')
},
/**
* Encode a tree path
*
* @param {string} str String to encode
* @returns Encoded tree path
*/
encodeTreePath (str) {
return str?.toLowerCase()?.replaceAll('-', '_')?.replaceAll('/', '.') || ''
},
/**
* Generate SHA-1 Hash of a string
*
* @param {string} str String to hash
* @returns Hashed string
*/
generateHash (str) {
return crypto.createHash('sha1').update(str).digest('hex')
},
/**
* Get default value of type
*
* @param {any} type primitive type name
* @returns Default value
*/
getTypeDefaultValue (type) {
switch (type.toLowerCase()) {
case 'string':
return ''
case 'number':
return 0
case 'boolean':
return false
}
},
parseModuleProps (props) {
return _.transform(props, (result, value, key) => {
let defaultValue = ''
if (_.isPlainObject(value)) {
defaultValue = !_.isNil(value.default) ? value.default : this.getTypeDefaultValue(value.type)
} else {
defaultValue = this.getTypeDefaultValue(value)
}
_.set(result, key, {
default: defaultValue,
type: (value.type || value).toLowerCase(),
title: value.title || _.startCase(key),
hint: value.hint || '',
enum: value.enum || false,
enumDisplay: value.enumDisplay || 'select',
multiline: value.multiline || false,
sensitive: value.sensitive || false,
icon: value.icon || 'rename',
order: value.order || 100
})
return result
}, {})
}
}

@ -0,0 +1,102 @@
import { isNil, isPlainObject, set, startCase, transform } from 'lodash-es'
import crypto from 'node:crypto'
/* eslint-disable promise/param-names */
export function createDeferred () {
let result, resolve, reject
return {
resolve: function (value) {
if (resolve) {
resolve(value)
} else {
result = result || new Promise(function (r) { r(value) })
}
},
reject: function (reason) {
if (reject) {
reject(reason)
} else {
result = result || new Promise(function (x, j) { j(reason) })
}
},
promise: new Promise(function (r, j) {
if (result) {
r(result)
} else {
resolve = r
reject = j
}
})
}
}
/**
* Decode a tree path
*
* @param {string} str String to decode
* @returns Decoded tree path
*/
export function decodeTreePath (str) {
return str.replaceAll('_', '-').replaceAll('.', '/')
}
/**
* Encode a tree path
*
* @param {string} str String to encode
* @returns Encoded tree path
*/
export function encodeTreePath (str) {
return str?.toLowerCase()?.replaceAll('-', '_')?.replaceAll('/', '.') || ''
}
/**
* Generate SHA-1 Hash of a string
*
* @param {string} str String to hash
* @returns Hashed string
*/
export function generateHash (str) {
return crypto.createHash('sha1').update(str).digest('hex')
}
/**
* Get default value of type
*
* @param {any} type primitive type name
* @returns Default value
*/
export function getTypeDefaultValue (type) {
switch (type.toLowerCase()) {
case 'string':
return ''
case 'number':
return 0
case 'boolean':
return false
}
}
export function parseModuleProps (props) {
return transform(props, (result, value, key) => {
let defaultValue = ''
if (isPlainObject(value)) {
defaultValue = !isNil(value.default) ? value.default : this.getTypeDefaultValue(value.type)
} else {
defaultValue = this.getTypeDefaultValue(value)
}
set(result, key, {
default: defaultValue,
type: (value.type || value).toLowerCase(),
title: value.title || startCase(key),
hint: value.hint || '',
enum: value.enum || false,
enumDisplay: value.enumDisplay || 'select',
multiline: value.multiline || false,
sensitive: value.sensitive || false,
icon: value.icon || 'rename',
order: value.order || 100
})
return result
}, {})
}

@ -1,10 +1,8 @@
'use strict'
const _ = require('lodash')
import { replace } from 'lodash-es'
const isoDurationReg = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/
module.exports = {
export default {
/**
* Parse configuration value for environment vars
*
@ -16,7 +14,7 @@ module.exports = {
* @returns Parse configuration value
*/
parseConfigValue (cfg) {
return _.replace(
return replace(
cfg,
/\$\(([A-Z0-9_]+)(?::(.+))?\)/g,
(fm, m, d) => { return process.env[m] || d }

@ -1,6 +1,6 @@
const CustomError = require('custom-error-instance')
import CustomError from 'custom-error-instance'
module.exports = {
export default {
Custom (slug, message) {
return CustomError(slug, { message })
},

@ -1,21 +0,0 @@
const _ = require('lodash')
module.exports = {
generateSuccess (msg) {
return {
succeeded: true,
errorCode: 0,
slug: 'ok',
message: _.defaultTo(msg, 'Operation succeeded.')
}
},
generateError (err, complete = true) {
const error = {
succeeded: false,
errorCode: _.isFinite(err.code) ? err.code : 1,
slug: err.name,
message: err.message || 'An unexpected error occured.'
}
return (complete) ? { operation: error } : error
}
}

@ -0,0 +1,20 @@
import { defaultTo, isFinite } from 'lodash-es'
export function generateSuccess (msg) {
return {
succeeded: true,
errorCode: 0,
slug: 'ok',
message: defaultTo(msg, 'Operation succeeded.')
}
}
export function generateError (err, complete = true) {
const error = {
succeeded: false,
errorCode: isFinite(err.code) ? err.code : 1,
slug: err.name,
message: err.message || 'An unexpected error occured.'
}
return (complete) ? { operation: error } : error
}

@ -1,151 +0,0 @@
const qs = require('querystring')
const _ = require('lodash')
const crypto = require('crypto')
const path = require('path')
const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i
const localeFolderRegex = /^([a-z]{2}(?:-[a-z]{2})?\/)?(.*)/i
// eslint-disable-next-line no-control-regex
const unsafeCharsRegex = /[\x00-\x1f\x80-\x9f\\"|<>:*?]/
const contentToExt = {
markdown: 'md',
html: 'html'
}
const extToContent = _.invert(contentToExt)
module.exports = {
/**
* Parse raw url path and make it safe
*/
parsePath (rawPath, opts = {}) {
let pathObj = {
// TODO: use site base lang
locale: 'en', // WIKI.config.lang.code,
path: 'home',
private: false,
privateNS: '',
explicitLocale: false
}
// Clean Path
rawPath = _.trim(qs.unescape(rawPath))
if (_.startsWith(rawPath, '/')) { rawPath = rawPath.substring(1) }
rawPath = rawPath.replace(unsafeCharsRegex, '')
if (rawPath === '') { rawPath = 'home' }
rawPath = rawPath.replace(/\\/g, '').replace(/\/\//g, '').replace(/\.\.+/ig, '')
// Extract Info
let pathParts = _.filter(_.split(rawPath, '/'), p => {
p = _.trim(p)
return !_.isEmpty(p) && p !== '..' && p !== '.'
})
if (pathParts[0].startsWith('_')) {
pathParts.shift()
}
if (localeSegmentRegex.test(pathParts[0])) {
pathObj.locale = pathParts[0]
pathObj.explicitLocale = true
pathParts.shift()
}
// Strip extension
if (opts.stripExt && pathParts.length > 0) {
const lastPart = _.last(pathParts)
if (lastPart.indexOf('.') > 0) {
pathParts.pop()
const lastPartMeta = path.parse(lastPart)
pathParts.push(lastPartMeta.name)
}
}
pathObj.path = _.join(pathParts, '/')
return pathObj
},
/**
* Generate unique hash from page
*/
generateHash(opts) {
return crypto.createHash('sha1').update(`${opts.locale}|${opts.path}|${opts.privateNS}`).digest('hex')
},
/**
* Inject Page Metadata
*/
injectPageMetadata(page) {
let meta = [
['title', page.title],
['description', page.description],
['published', page.isPublished.toString()],
['date', page.updatedAt],
['tags', page.tags ? page.tags.map(t => t.tag).join(', ') : ''],
['editor', page.editorKey],
['dateCreated', page.createdAt]
]
switch (page.contentType) {
case 'markdown':
return '---\n' + meta.map(mt => `${mt[0]}: ${mt[1]}`).join('\n') + '\n---\n\n' + page.content
case 'html':
return '<!--\n' + meta.map(mt => `${mt[0]}: ${mt[1]}`).join('\n') + '\n-->\n\n' + page.content
case 'json':
return {
...page.content,
_meta: _.fromPairs(meta)
}
default:
return page.content
}
},
/**
* Check if path is a reserved path
*/
isReservedPath(rawPath) {
const firstSection = _.head(rawPath.split('/'))
if (firstSection.length < 1) {
return true
} else if (localeSegmentRegex.test(firstSection)) {
return true
} else if (
_.some(WIKI.data.reservedPaths, p => {
return p === firstSection
})) {
return true
} else {
return false
}
},
/**
* Get file extension from content type
*/
getFileExtension(contentType) {
return _.get(contentToExt, contentType, 'txt')
},
/**
* Get content type from file extension
*/
getContentType (filePath) {
const ext = _.last(filePath.split('.'))
return _.get(extToContent, ext, false)
},
/**
* Get Page Meta object from disk path
*/
getPagePath (filePath) {
let fpath = filePath
if (process.platform === 'win32') {
fpath = filePath.replace(/\\/g, '/')
}
let meta = {
locale: WIKI.config.lang.code,
path: _.initial(fpath.split('.')).join('')
}
const result = localeFolderRegex.exec(meta.path)
if (result[1]) {
meta = {
locale: result[1].replace('/', ''),
path: result[2]
}
}
return meta
}
}

@ -0,0 +1,155 @@
import qs from 'querystring'
import { fromPairs, get, initial, invert, isEmpty, last } from 'lodash-es'
import crypto from 'node:crypto'
import path from 'node:path'
const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i
const localeFolderRegex = /^([a-z]{2}(?:-[a-z]{2})?\/)?(.*)/i
// eslint-disable-next-line no-control-regex
const unsafeCharsRegex = /[\x00-\x1f\x80-\x9f\\"|<>:*?]/
const contentToExt = {
markdown: 'md',
html: 'html'
}
const extToContent = invert(contentToExt)
/**
* Parse raw url path and make it safe
*/
export function parsePath (rawPath, opts = {}) {
const pathObj = {
// TODO: use site base lang
locale: 'en', // WIKI.config.lang.code,
path: 'home',
private: false,
privateNS: '',
explicitLocale: false
}
// Clean Path
rawPath = qs.unescape(rawPath).trim()
if (rawPath.startsWith('/')) { rawPath = rawPath.substring(1) }
rawPath = rawPath.replace(unsafeCharsRegex, '')
if (rawPath === '') { rawPath = 'home' }
rawPath = rawPath.replace(/\\/g, '').replace(/\/\//g, '').replace(/\.\.+/ig, '')
// Extract Info
let pathParts = rawPath.split('/').filter(p => {
p = p.trim()
return !isEmpty(p) && p !== '..' && p !== '.'
})
if (pathParts[0].startsWith('_')) {
pathParts.shift()
}
if (localeSegmentRegex.test(pathParts[0])) {
pathObj.locale = pathParts[0]
pathObj.explicitLocale = true
pathParts.shift()
}
// Strip extension
if (opts.stripExt && pathParts.length > 0) {
const lastPart = last(pathParts)
if (lastPart.indexOf('.') > 0) {
pathParts.pop()
const lastPartMeta = path.parse(lastPart)
pathParts.push(lastPartMeta.name)
}
}
pathObj.path = _.join(pathParts, '/')
return pathObj
}
/**
* Generate unique hash from page
*/
export function generateHash(opts) {
return crypto.createHash('sha1').update(`${opts.locale}|${opts.path}|${opts.privateNS}`).digest('hex')
}
/**
* Inject Page Metadata
*/
export function injectPageMetadata(page) {
const meta = [
['title', page.title],
['description', page.description],
['published', page.isPublished.toString()],
['date', page.updatedAt],
['tags', page.tags ? page.tags.map(t => t.tag).join(', ') : ''],
['editor', page.editorKey],
['dateCreated', page.createdAt]
]
switch (page.contentType) {
case 'markdown':
return '---\n' + meta.map(mt => `${mt[0]}: ${mt[1]}`).join('\n') + '\n---\n\n' + page.content
case 'html':
return '<!--\n' + meta.map(mt => `${mt[0]}: ${mt[1]}`).join('\n') + '\n-->\n\n' + page.content
case 'json':
return {
...page.content,
_meta: fromPairs(meta)
}
default:
return page.content
}
}
/**
* Check if path is a reserved path
*/
export function isReservedPath(rawPath) {
const firstSection = _.head(rawPath.split('/'))
if (firstSection.length < 1) {
return true
} else if (localeSegmentRegex.test(firstSection)) {
return true
} else if (
WIKI.data.reservedPaths.some(p => {
return p === firstSection
})) {
return true
} else {
return false
}
}
/**
* Get file extension from content type
*/
export function getFileExtension(contentType) {
return get(contentToExt, contentType, 'txt')
}
/**
* Get content type from file extension
*/
export function getContentType (filePath) {
const ext = last(filePath.split('.'))
return get(extToContent, ext, false)
}
/**
* Get Page Meta object from disk path
*/
export function getPagePath (filePath) {
let fpath = filePath
if (process.platform === 'win32') {
fpath = filePath.replace(/\\/g, '/')
}
let meta = {
locale: WIKI.config.lang.code,
path: initial(fpath.split('.')).join('')
}
const result = localeFolderRegex.exec(meta.path)
if (result[1]) {
meta = {
locale: result[1].replace('/', ''),
path: result[2]
}
}
return meta
}

@ -1,38 +0,0 @@
const util = require('node:util')
const crypto = require('node:crypto')
const randomBytes = util.promisify(crypto.randomBytes)
const passportJWT = require('passport-jwt')
module.exports = {
sanitizeCommitUser (user) {
// let wlist = new RegExp('[^a-zA-Z0-9-_.\',& ' + appdata.regex.cjk + appdata.regex.arabic + ']', 'g')
// return {
// name: _.chain(user.name).replace(wlist, '').trim().value(),
// email: appconfig.git.showUserEmail ? user.email : appconfig.git.serverEmail
// }
},
/**
* Generate a random token
*
* @param {any} length
* @returns
*/
async generateToken (length) {
return (await randomBytes(length)).toString('hex')
},
extractJWT: passportJWT.ExtractJwt.fromExtractors([
passportJWT.ExtractJwt.fromAuthHeaderAsBearerToken(),
(req) => {
let token = null
if (req && req.cookies) {
token = req.cookies['jwt']
}
// Force uploads to use Auth headers
if (req.path.toLowerCase() === '/u') {
return null
}
return token
}
])
}

@ -0,0 +1,38 @@
import util from 'node:util'
import crypto from 'node:crypto'
import passportJWT from 'passport-jwt'
const randomBytes = util.promisify(crypto.randomBytes)
export function sanitizeCommitUser (user) {
// let wlist = new RegExp('[^a-zA-Z0-9-_.\',& ' + appdata.regex.cjk + appdata.regex.arabic + ']', 'g')
// return {
// name: _.chain(user.name).replace(wlist, '').trim().value(),
// email: appconfig.git.showUserEmail ? user.email : appconfig.git.serverEmail
// }
}
/**
* Generate a random token
*
* @param {any} length
* @returns
*/
export async function generateToken (length) {
return (await randomBytes(length)).toString('hex')
}
export const extractJWT = passportJWT.ExtractJwt.fromExtractors([
passportJWT.ExtractJwt.fromAuthHeaderAsBearerToken(),
(req) => {
let token = null
if (req && req.cookies) {
token = req.cookies['jwt']
}
// Force uploads to use Auth headers
if (req.path.toLowerCase() === '/u') {
return null
}
return token
}
])

@ -3,30 +3,34 @@
// Licensed under AGPLv3
// ===========================================
const path = require('path')
const { DateTime } = require('luxon')
const semver = require('semver')
const nanoid = require('nanoid').customAlphabet('1234567890abcdef', 10)
const fs = require('fs-extra')
import path from 'node:path'
import { DateTime } from 'luxon'
import semver from 'semver'
import { customAlphabet } from 'nanoid'
import fse from 'fs-extra'
import configSvc from './core/config.mjs'
import kernel from './core/kernel.mjs'
import logger from './core/logger.mjs'
const nanoid = customAlphabet('1234567890abcdef', 10)
if (!semver.satisfies(process.version, '>=18')) {
console.error('ERROR: Node.js 18.x or later required!')
process.exit(1)
}
if (fs.pathExistsSync('./package.json')) {
if (fse.pathExistsSync('./package.json')) {
console.error('ERROR: Must run server from the parent directory!')
process.exit(1)
}
let WIKI = {
const WIKI = {
IS_DEBUG: process.env.NODE_ENV === 'development',
ROOTPATH: process.cwd(),
INSTANCE_ID: nanoid(10),
SERVERPATH: path.join(process.cwd(), 'server'),
Error: require('./helpers/error'),
configSvc: require('./core/config'),
kernel: require('./core/kernel'),
configSvc,
kernel,
sites: {},
sitesMappings: {},
startedAt: DateTime.utc(),
@ -37,13 +41,13 @@ let WIKI = {
}
global.WIKI = WIKI
WIKI.configSvc.init()
await WIKI.configSvc.init()
// ----------------------------------------
// Init Logger
// ----------------------------------------
WIKI.logger = require('./core/logger').init()
WIKI.logger = logger.init()
// ----------------------------------------
// Start Kernel

@ -1,20 +0,0 @@
## IMPORTANT
Localization files are not stored into files!
Contact us on Gitter to request access to the translation web service: https://gitter.im/Requarks/wiki
## Development Mode
If you need to add new keys and test them live, simply create a {LANG}.yml file in this folder containing the values you want to test. e.g.:
### en.yml
```yml
admin:
api.title: 'API Access'
auth.title: 'Authentication'
```
The official localization keys will still be loaded first, but your local files will overwrite any existing keys (and add new ones).
Note that you must restart Wiki.js to load any changes made to the files, which happens automatically on save when in dev mode.

@ -1,46 +0,0 @@
/* global WIKI */
/**
* Security Middleware
*
* @param {Express Request} req Express request object
* @param {Express Response} res Express response object
* @param {Function} next next callback function
* @return {any} void
*/
module.exports = function (req, res, next) {
// -> Disable X-Powered-By
req.app.disable('x-powered-by')
// -> Disable Frame Embedding
if (WIKI.config.security.securityIframe) {
res.set('X-Frame-Options', 'deny')
}
// -> Re-enable XSS Fitler if disabled
res.set('X-XSS-Protection', '1; mode=block')
// -> Disable MIME-sniffing
res.set('X-Content-Type-Options', 'nosniff')
// -> Disable IE Compatibility Mode
res.set('X-UA-Compatible', 'IE=edge')
// -> Disables referrer header when navigating to a different origin
if (WIKI.config.security.securityReferrerPolicy) {
res.set('Referrer-Policy', 'same-origin')
}
// -> Enforce HSTS
if (WIKI.config.security.securityHSTS) {
res.set('Strict-Transport-Security', `max-age=${WIKI.config.security.securityHSTSDuration}; includeSubDomains`)
}
// -> Prevent Open Redirect from user provided URL
if (WIKI.config.security.securityOpenRedirect) {
// Strips out all repeating / character in the provided URL
req.url = req.url.replace(/(\/)(?=\/*\1)/g, '')
}
return next()
}

@ -1,19 +0,0 @@
const _ = require('lodash')
/**
* SEO Middleware
*
* @param {Express Request} req Express request object
* @param {Express Response} res Express response object
* @param {Function} next next callback function
* @return {any} void
*/
module.exports = function (req, res, next) {
if (req.path.length > 1 && _.endsWith(req.path, '/')) {
let query = req.url.slice(req.path.length) || ''
res.redirect(301, req.path.slice(0, -1) + query)
} else {
_.set(res.locals, 'pageMeta.url', `${WIKI.config.host}${req.path}`)
return next()
}
}

@ -1,14 +1,14 @@
const Model = require('objection').Model
const fs = require('fs-extra')
const path = require('path')
const _ = require('lodash')
const yaml = require('js-yaml')
const commonHelper = require('../helpers/common')
import { Model } from 'objection'
import fs from 'node:fs/promises'
import path from 'node:path'
import { defaultTo, forOwn, isBoolean, replace, sortBy } from 'lodash-es'
import yaml from 'js-yaml'
import { parseModuleProps } from '../helpers/common.mjs'
/**
* Analytics model
*/
module.exports = class Analytics extends Model {
export class Analytics extends Model {
static get tableName() { return 'analytics' }
static get jsonSchema () {
@ -29,8 +29,8 @@ module.exports = class Analytics extends Model {
}
static async getProviders(isEnabled) {
const providers = await WIKI.db.analytics.query().where(_.isBoolean(isEnabled) ? { isEnabled } : {})
return _.sortBy(providers, ['module'])
const providers = await WIKI.db.analytics.query().where(isBoolean(isEnabled) ? { isEnabled } : {})
return sortBy(providers, ['module'])
}
static async refreshProvidersFromDisk() {
@ -42,7 +42,7 @@ module.exports = class Analytics extends Model {
const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/analytics', dir, 'definition.yml'), 'utf8')
const defParsed = yaml.load(def)
defParsed.key = dir
defParsed.props = commonHelper.parseModuleProps(defParsed.props)
defParsed.props = parseModuleProps(defParsed.props)
WIKI.data.analytics.push(defParsed)
WIKI.logger.debug(`Loaded analytics module definition ${dir}: [ OK ]`)
}
@ -72,14 +72,14 @@ module.exports = class Analytics extends Model {
for (let provider of providers) {
const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/analytics', provider.key, 'code.yml'), 'utf8')
let code = yaml.safeLoad(def)
code.head = _.defaultTo(code.head, '')
code.bodyStart = _.defaultTo(code.bodyStart, '')
code.bodyEnd = _.defaultTo(code.bodyEnd, '')
code.head = defaultTo(code.head, '')
code.bodyStart = defaultTo(code.bodyStart, '')
code.bodyEnd = defaultTo(code.bodyEnd, '')
_.forOwn(provider.config, (value, key) => {
code.head = _.replace(code.head, new RegExp(`{{${key}}}`, 'g'), value)
code.bodyStart = _.replace(code.bodyStart, `{{${key}}}`, value)
code.bodyEnd = _.replace(code.bodyEnd, `{{${key}}}`, value)
forOwn(provider.config, (value, key) => {
code.head = replace(code.head, new RegExp(`{{${key}}}`, 'g'), value)
code.bodyStart = replace(code.bodyStart, `{{${key}}}`, value)
code.bodyEnd = replace(code.bodyEnd, `{{${key}}}`, value)
})
analyticsCode.head += code.head

@ -1,14 +1,14 @@
/* global WIKI */
const Model = require('objection').Model
const { DateTime } = require('luxon')
const ms = require('ms')
const jwt = require('jsonwebtoken')
import { Model } from 'objection'
import { DateTime } from 'luxon'
import ms from 'ms'
import jwt from 'jsonwebtoken'
/**
* Users model
*/
module.exports = class ApiKey extends Model {
export class ApiKey extends Model {
static get tableName() { return 'apiKeys' }
static get jsonSchema () {

@ -1,14 +1,15 @@
const Model = require('objection').Model
const moment = require('moment')
const path = require('path')
const fs = require('fs-extra')
const _ = require('lodash')
const commonHelper = require('../helpers/common')
import { Model } from 'objection'
import path from 'path'
import fse from 'fs-extra'
import { startsWith } from 'lodash-es'
import { generateHash } from '../helpers/common.mjs'
import { User } from './users.mjs'
/**
* Users model
*/
module.exports = class Asset extends Model {
export class Asset extends Model {
static get tableName() { return 'assets' }
static get jsonSchema () {
@ -34,19 +35,11 @@ module.exports = class Asset extends Model {
return {
author: {
relation: Model.BelongsToOneRelation,
modelClass: require('./users'),
modelClass: User,
join: {
from: 'assets.authorId',
to: 'users.id'
}
},
folder: {
relation: Model.BelongsToOneRelation,
modelClass: require('./assetFolders'),
join: {
from: 'assets.folderId',
to: 'assetFolders.id'
}
}
}
}
@ -72,7 +65,7 @@ module.exports = class Asset extends Model {
}
async deleteAssetCache() {
await fs.remove(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${this.hash}.dat`))
await fse.remove(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${this.hash}.dat`))
}
static async upload(opts) {
@ -88,7 +81,7 @@ module.exports = class Asset extends Model {
let assetRow = {
filename: opts.originalname,
ext: fileInfo.ext,
kind: _.startsWith(opts.mimetype, 'image/') ? 'image' : 'binary',
kind: startsWith(opts.mimetype, 'image/') ? 'image' : 'binary',
mime: opts.mimetype,
fileSize: opts.size,
folderId: opts.folderId
@ -112,7 +105,7 @@ module.exports = class Asset extends Model {
// Save asset data
try {
const fileBuffer = await fs.readFile(opts.path)
const fileBuffer = await fse.readFile(opts.path)
if (asset) {
// Patch existing asset
@ -166,7 +159,7 @@ module.exports = class Asset extends Model {
.select('tree.*', 'assets.preview', 'assets.previewState')
.innerJoin('assets', 'tree.id', 'assets.id')
.where(id ? { 'tree.id': id } : {
'tree.hash': commonHelper.generateHash(path),
'tree.hash': generateHash(path),
'tree.localeCode': locale,
'tree.siteId': siteId
})
@ -202,7 +195,7 @@ module.exports = class Asset extends Model {
static async getAssetFromCache(assetPath, cachePath, res) {
try {
await fs.access(cachePath, fs.constants.R_OK)
await fse.access(cachePath, fse.constants.R_OK)
} catch (err) {
return false
}
@ -217,7 +210,7 @@ module.exports = class Asset extends Model {
path: assetPath
}
})
for (let location of _.filter(localLocations, location => Boolean(location.path))) {
for (let location of localLocations.filter(location => Boolean(location.path))) {
const assetExists = await WIKI.db.assets.getAssetFromCache(assetPath, location.path, res)
if (assetExists) {
return true
@ -232,13 +225,13 @@ module.exports = class Asset extends Model {
const assetData = await WIKI.db.knex('assetData').where('id', asset.id).first()
res.type(asset.ext)
res.send(assetData.data)
await fs.outputFile(cachePath, assetData.data)
await fse.outputFile(cachePath, assetData.data)
} else {
res.sendStatus(404)
}
}
static async flushTempUploads() {
return fs.emptyDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `uploads`))
return fse.emptyDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `uploads`))
}
}

@ -1,14 +1,14 @@
const Model = require('objection').Model
const fs = require('fs-extra')
const path = require('path')
const _ = require('lodash')
const yaml = require('js-yaml')
const commonHelper = require('../helpers/common')
import { Model } from 'objection'
import fs from 'node:fs/promises'
import path from 'node:path'
import { get } from 'lodash-es'
import yaml from 'js-yaml'
import { parseModuleProps } from '../helpers/common.mjs'
/**
* Authentication model
*/
module.exports = class Authentication extends Model {
export class Authentication extends Model {
static get tableName() { return 'authentication' }
static get jsonSchema () {
@ -37,8 +37,8 @@ module.exports = class Authentication extends Model {
const strategies = await WIKI.db.authentication.query().where(enabledOnly ? { isEnabled: true } : {})
return strategies.map(str => ({
...str,
domainWhitelist: _.get(str.domainWhitelist, 'v', []),
autoEnrollGroups: _.get(str.autoEnrollGroups, 'v', [])
domainWhitelist: get(str.domainWhitelist, 'v', []),
autoEnrollGroups: get(str.autoEnrollGroups, 'v', [])
}))
}
@ -52,7 +52,7 @@ module.exports = class Authentication extends Model {
const defParsed = yaml.load(def)
if (!defParsed.isAvailable) { continue }
defParsed.key = dir
defParsed.props = commonHelper.parseModuleProps(defParsed.props)
defParsed.props = parseModuleProps(defParsed.props)
WIKI.data.authentication.push(defParsed)
WIKI.logger.debug(`Loaded authentication module definition ${dir}: [ OK ]`)
}

@ -1,14 +1,14 @@
const Model = require('objection').Model
const fs = require('fs-extra')
const path = require('path')
const _ = require('lodash')
const yaml = require('js-yaml')
const commonHelper = require('../helpers/common')
import { Model } from 'objection'
import fs from 'node:fs/promises'
import path from 'node:path'
import { defaultTo, find, forOwn, isBoolean, replace, sortBy } from 'lodash-es'
import yaml from 'js-yaml'
import { parseModuleProps } from '../helpers/common.mjs'
/**
* CommentProvider model
*/
module.exports = class CommentProvider extends Model {
export class CommentProvider extends Model {
static get tableName() { return 'commentProviders' }
static get idColumn() { return 'key' }
@ -33,8 +33,8 @@ module.exports = class CommentProvider extends Model {
}
static async getProviders(isEnabled) {
const providers = await WIKI.db.commentProviders.query().where(_.isBoolean(isEnabled) ? { isEnabled } : {})
return _.sortBy(providers, ['module'])
const providers = await WIKI.db.commentProviders.query().where(isBoolean(isEnabled) ? { isEnabled } : {})
return sortBy(providers, ['module'])
}
static async refreshProvidersFromDisk() {
@ -46,7 +46,7 @@ module.exports = class CommentProvider extends Model {
const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/comments', dir, 'definition.yml'), 'utf8')
const defParsed = yaml.load(def)
defParsed.key = dir
defParsed.props = commonHelper.parseModuleProps(defParsed.props)
defParsed.props = parseModuleProps(defParsed.props)
WIKI.data.commentProviders.push(defParsed)
WIKI.logger.debug(`Loaded comments provider module definition ${dir}: [ OK ]`)
}
@ -62,7 +62,7 @@ module.exports = class CommentProvider extends Model {
const commentProvider = await WIKI.db.commentProviders.query().findOne('isEnabled', true)
if (commentProvider) {
WIKI.data.commentProvider = {
..._.find(WIKI.data.commentProviders, ['key', commentProvider.module]),
...find(WIKI.data.commentProviders, ['key', commentProvider.module]),
head: '',
bodyStart: '',
bodyEnd: '',
@ -72,14 +72,14 @@ module.exports = class CommentProvider extends Model {
if (WIKI.data.commentProvider.codeTemplate) {
const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/comments', commentProvider.key, 'code.yml'), 'utf8')
let code = yaml.safeLoad(def)
code.head = _.defaultTo(code.head, '')
code.body = _.defaultTo(code.body, '')
code.main = _.defaultTo(code.main, '')
code.head = defaultTo(code.head, '')
code.body = defaultTo(code.body, '')
code.main = defaultTo(code.main, '')
_.forOwn(commentProvider.config, (value, key) => {
code.head = _.replace(code.head, new RegExp(`{{${key}}}`, 'g'), value)
code.body = _.replace(code.body, new RegExp(`{{${key}}}`, 'g'), value)
code.main = _.replace(code.main, new RegExp(`{{${key}}}`, 'g'), value)
forOwn(commentProvider.config, (value, key) => {
code.head = replace(code.head, new RegExp(`{{${key}}}`, 'g'), value)
code.body = replace(code.body, new RegExp(`{{${key}}}`, 'g'), value)
code.main = replace(code.main, new RegExp(`{{${key}}}`, 'g'), value)
})
WIKI.data.commentProvider.head = code.head
@ -88,7 +88,7 @@ module.exports = class CommentProvider extends Model {
} else {
WIKI.data.commentProvider = {
...WIKI.data.commentProvider,
...require(`../modules/comments/${commentProvider.key}/comment`),
...(await import(`../modules/comments/${commentProvider.key}/comment.mjs`)),
config: commentProvider.config
}
await WIKI.data.commentProvider.init()

@ -1,11 +1,13 @@
const Model = require('objection').Model
const validate = require('validate.js')
const _ = require('lodash')
import { Model } from 'objection'
import validate from 'validate.js'
import { Page } from './pages.mjs'
import { User } from './users.mjs'
/**
* Comments model
*/
module.exports = class Comment extends Model {
export class Comment extends Model {
static get tableName() { return 'comments' }
static get jsonSchema () {
@ -30,7 +32,7 @@ module.exports = class Comment extends Model {
return {
author: {
relation: Model.BelongsToOneRelation,
modelClass: require('./users'),
modelClass: User,
join: {
from: 'comments.authorId',
to: 'users.id'
@ -38,7 +40,7 @@ module.exports = class Comment extends Model {
},
page: {
relation: Model.BelongsToOneRelation,
modelClass: require('./pages'),
modelClass: Page,
join: {
from: 'comments.pageId',
to: 'pages.id'
@ -62,7 +64,7 @@ module.exports = class Comment extends Model {
// -> Input validation
if (user.id === 2) {
const validation = validate({
email: _.toLower(guestEmail),
email: guestEmail.toLowerCase(),
name: guestName
}, {
email: {
@ -87,7 +89,7 @@ module.exports = class Comment extends Model {
}
}
content = _.trim(content)
content = content.trim()
if (content.length < 2) {
throw new WIKI.Error.CommentContentMissing()
}

@ -1,9 +1,11 @@
const Model = require('objection').Model
import { Model } from 'objection'
import { User } from './users.mjs'
/**
* Groups model
*/
module.exports = class Group extends Model {
export class Group extends Model {
static get tableName() { return 'groups' }
static get jsonSchema () {
@ -30,7 +32,7 @@ module.exports = class Group extends Model {
return {
users: {
relation: Model.ManyToManyRelation,
modelClass: require('./users'),
modelClass: User,
join: {
from: 'groups.id',
through: {

@ -1,9 +1,9 @@
const Model = require('objection').Model
import { Model } from 'objection'
/**
* Hook model
*/
module.exports = class Hook extends Model {
export class Hook extends Model {
static get tableName () { return 'hooks' }
static get jsonAttributes () {

@ -0,0 +1,45 @@
import { Analytics } from './analytics.mjs'
import { ApiKey } from './apiKeys.mjs'
import { Asset } from './assets.mjs'
import { Authentication } from './authentication.mjs'
import { CommentProvider } from './commentProviders.mjs'
import { Comment } from './comments.mjs'
import { Group } from './groups.mjs'
import { Hook } from './hooks.mjs'
import { Locale } from './locales.mjs'
import { Navigation } from './navigation.mjs'
import { PageHistory } from './pageHistory.mjs'
import { PageLink } from './pageLinks.mjs'
import { Page } from './pages.mjs'
import { Renderer } from './renderers.mjs'
import { Setting } from './settings.mjs'
import { Site } from './sites.mjs'
import { Storage } from './storage.mjs'
import { Tag } from './tags.mjs'
import { Tree } from './tree.mjs'
import { UserKey } from './userKeys.mjs'
import { User } from './users.mjs'
export default {
analytics: Analytics,
apiKeys: ApiKey,
assets: Asset,
authentication: Authentication,
commentProviders: CommentProvider,
comments: Comment,
groups: Group,
hooks: Hook,
locales: Locale,
navigation: Navigation,
pageHistory: PageHistory,
pageLinks: PageLink,
pages: Page,
renderers: Renderer,
settings: Setting,
sites: Site,
storage: Storage,
tags: Tag,
tree: Tree,
userKeys: UserKey,
users: User
}

@ -1,9 +1,9 @@
const Model = require('objection').Model
import { Model } from 'objection'
/**
* Locales model
*/
module.exports = class Locale extends Model {
export class Locale extends Model {
static get tableName() { return 'locales' }
static get idColumn() { return 'code' }

@ -1,10 +1,10 @@
const Model = require('objection').Model
const _ = require('lodash')
import { Model } from 'objection'
import { has } from 'lodash-es'
/**
* Navigation model
*/
module.exports = class Navigation extends Model {
export class Navigation extends Model {
static get tableName() { return 'navigation' }
static get idColumn() { return 'key' }
@ -30,7 +30,7 @@ module.exports = class Navigation extends Model {
const navTree = await WIKI.db.navigation.query().findOne('key', `site`)
if (navTree) {
// Check for pre-2.3 format
if (_.has(navTree.config[0], 'kind')) {
if (has(navTree.config[0], 'kind')) {
navTree.config = [{
locale: 'en',
items: navTree.config.map(item => ({
@ -58,7 +58,7 @@ module.exports = class Navigation extends Model {
}
static getAuthorizedItems(tree = [], groups = []) {
return _.filter(tree, leaf => {
return tree.filter(leaf => {
return leaf.visibilityMode === 'all' || _.intersection(leaf.visibilityGroups, groups).length > 0
})
}

@ -1,11 +1,16 @@
const Model = require('objection').Model
const _ = require('lodash')
const { DateTime, Duration } = require('luxon')
import { Model } from 'objection'
import { get, reduce, reverse } from 'lodash-es'
import { DateTime, Duration } from 'luxon'
import { Locale } from './locales.mjs'
import { Page } from './pages.mjs'
import { User } from './users.mjs'
import { Tag } from './tags.mjs'
/**
* Page History model
*/
module.exports = class PageHistory extends Model {
export class PageHistory extends Model {
static get tableName() { return 'pageHistory' }
static get jsonSchema () {
@ -34,7 +39,7 @@ module.exports = class PageHistory extends Model {
return {
tags: {
relation: Model.ManyToManyRelation,
modelClass: require('./tags'),
modelClass: Tag,
join: {
from: 'pageHistory.id',
through: {
@ -46,7 +51,7 @@ module.exports = class PageHistory extends Model {
},
page: {
relation: Model.BelongsToOneRelation,
modelClass: require('./pages'),
modelClass: Page,
join: {
from: 'pageHistory.pageId',
to: 'pages.id'
@ -54,7 +59,7 @@ module.exports = class PageHistory extends Model {
},
author: {
relation: Model.BelongsToOneRelation,
modelClass: require('./users'),
modelClass: User,
join: {
from: 'pageHistory.authorId',
to: 'users.id'
@ -62,7 +67,7 @@ module.exports = class PageHistory extends Model {
},
locale: {
relation: Model.BelongsToOneRelation,
modelClass: require('./locales'),
modelClass: Locale,
join: {
from: 'pageHistory.localeCode',
to: 'locales.code'
@ -189,16 +194,16 @@ module.exports = class PageHistory extends Model {
}
return {
trail: _.reduce(_.reverse(history.results), (res, ph) => {
trail: reduce(reverse(history.results), (res, ph) => {
let actionType = 'edit'
let valueBefore = null
let valueAfter = null
if (!prevPh && history.total < upperLimit) {
actionType = 'initial'
} else if (_.get(prevPh, 'path', '') !== ph.path) {
} else if (get(prevPh, 'path', '') !== ph.path) {
actionType = 'move'
valueBefore = _.get(prevPh, 'path', '')
valueBefore = get(prevPh, 'path', '')
valueAfter = ph.path
}

@ -1,9 +1,11 @@
const Model = require('objection').Model
import { Model } from 'objection'
import { Page } from './pages.mjs'
/**
* Users model
*/
module.exports = class PageLink extends Model {
export class PageLink extends Model {
static get tableName() { return 'pageLinks' }
static get jsonSchema () {
@ -23,7 +25,7 @@ module.exports = class PageLink extends Model {
return {
page: {
relation: Model.BelongsToOneRelation,
modelClass: require('./pages'),
modelClass: Page,
join: {
from: 'pageLinks.pageId',
to: 'pages.id'

@ -1,17 +1,22 @@
const Model = require('objection').Model
const _ = require('lodash')
const JSBinType = require('js-binary').Type
const pageHelper = require('../helpers/page')
const path = require('path')
const fs = require('fs-extra')
const yaml = require('js-yaml')
const striptags = require('striptags')
const emojiRegex = require('emoji-regex')
const he = require('he')
const CleanCSS = require('clean-css')
const TurndownService = require('turndown')
const turndownPluginGfm = require('@joplin/turndown-plugin-gfm').gfm
const cheerio = require('cheerio')
import { Model } from 'objection'
import { find, get, has, isEmpty, isString, pick } from 'lodash-es'
import { Type as JSBinType } from 'js-binary'
import { generateHash, getFileExtension, injectPageMetadata } from '../helpers/page.mjs'
import path from 'node:path'
import fse from 'fs-extra'
import yaml from 'js-yaml'
import striptags from 'striptags'
import emojiRegex from 'emoji-regex'
import he from 'he'
import CleanCSS from 'clean-css'
import TurndownService from 'turndown'
import { gfm as turndownPluginGfm } from '@joplin/turndown-plugin-gfm'
import cheerio from 'cheerio'
import { Locale } from './locales.mjs'
import { PageLink } from './pageLinks.mjs'
import { Tag } from './tags.mjs'
import { User } from './users.mjs'
const pageRegex = /^[a-zA0-90-9-_/]*$/
@ -27,7 +32,7 @@ const punctuationRegex = /[!,:;/\\_+\-=()&#@<>$~%^*[\]{}"'|]+|(\.\s)|(\s\.)/ig
/**
* Pages model
*/
module.exports = class Page extends Model {
export class Page extends Model {
static get tableName() { return 'pages' }
static get jsonSchema () {
@ -62,7 +67,7 @@ module.exports = class Page extends Model {
return {
tags: {
relation: Model.ManyToManyRelation,
modelClass: require('./tags'),
modelClass: Tag,
join: {
from: 'pages.id',
through: {
@ -74,7 +79,7 @@ module.exports = class Page extends Model {
},
links: {
relation: Model.HasManyRelation,
modelClass: require('./pageLinks'),
modelClass: PageLink,
join: {
from: 'pages.id',
to: 'pageLinks.pageId'
@ -82,7 +87,7 @@ module.exports = class Page extends Model {
},
author: {
relation: Model.BelongsToOneRelation,
modelClass: require('./users'),
modelClass: User,
join: {
from: 'pages.authorId',
to: 'users.id'
@ -90,7 +95,7 @@ module.exports = class Page extends Model {
},
creator: {
relation: Model.BelongsToOneRelation,
modelClass: require('./users'),
modelClass: User,
join: {
from: 'pages.creatorId',
to: 'users.id'
@ -98,7 +103,7 @@ module.exports = class Page extends Model {
},
locale: {
relation: Model.BelongsToOneRelation,
modelClass: require('./locales'),
modelClass: Locale,
join: {
from: 'pages.localeCode',
to: 'locales.code'
@ -162,7 +167,7 @@ module.exports = class Page extends Model {
* @returns {string} Page Contents with Injected Metadata
*/
injectMetadata () {
return pageHelper.injectPageMetadata(this)
return injectPageMetadata(this)
}
/**
@ -171,7 +176,7 @@ module.exports = class Page extends Model {
* @returns {string} File Extension
*/
getFileExtension() {
return pageHelper.getFileExtension(this.contentType)
return getFileExtension(this.contentType)
}
/**
@ -312,7 +317,7 @@ module.exports = class Page extends Model {
contentType: WIKI.data.editors[opts.editor]?.contentType ?? 'text',
description: opts.description,
editor: opts.editor,
hash: pageHelper.generateHash({ path: opts.path, locale: opts.locale }),
hash: generateHash({ path: opts.path, locale: opts.locale }),
icon: opts.icon,
isBrowsable: opts.isBrowsable ?? true,
localeCode: opts.locale,
@ -561,7 +566,7 @@ module.exports = class Page extends Model {
})) {
patch.scripts = {
...patch.scripts ?? ogPage.scripts ?? {},
css: !_.isEmpty(opts.patch.scriptCss) ? new CleanCSS({ inline: false }).minify(opts.patch.scriptCss).styles : ''
css: !isEmpty(opts.patch.scriptCss) ? new CleanCSS({ inline: false }).minify(opts.patch.scriptCss).styles : ''
}
historyData.affectedFields.push('scripts.css')
}
@ -698,7 +703,7 @@ module.exports = class Page extends Model {
// -> Check content type
const sourceContentType = ogPage.contentType
const targetContentType = _.get(_.find(WIKI.data.editors, ['key', opts.editor]), `contentType`, 'text')
const targetContentType = get(find(WIKI.data.editors, ['key', opts.editor]), `contentType`, 'text')
const shouldConvert = sourceContentType !== targetContentType
let convertedContent = null
@ -846,7 +851,7 @@ module.exports = class Page extends Model {
*/
static async movePage(opts) {
let page
if (_.has(opts, 'id')) {
if (has(opts, 'id')) {
page = await WIKI.db.pages.query().findById(opts.id)
} else {
page = await WIKI.db.pages.query().findOne({
@ -904,7 +909,7 @@ module.exports = class Page extends Model {
versionDate: page.updatedAt
})
const destinationHash = pageHelper.generateHash({ path: opts.destinationPath, locale: opts.destinationLocale })
const destinationHash = generateHash({ path: opts.destinationPath, locale: opts.destinationLocale })
// -> Move page
const destinationTitle = (page.title === page.path ? opts.destinationPath : page.title)
@ -970,7 +975,7 @@ module.exports = class Page extends Model {
* @returns {Promise} Promise with no value
*/
static async deletePage(opts) {
const page = await WIKI.db.pages.getPageFromDb(_.has(opts, 'id') ? opts.id : opts)
const page = await WIKI.db.pages.getPageFromDb(has(opts, 'id') ? opts.id : opts)
if (!page) {
throw new WIKI.Error.PageNotFound()
}
@ -1209,7 +1214,7 @@ module.exports = class Page extends Model {
*/
static async savePageToCache(page) {
const cachePath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${page.hash}.bin`)
await fs.outputFile(cachePath, WIKI.db.pages.cacheSchema.encode({
await fse.outputFile(cachePath, WIKI.db.pages.cacheSchema.encode({
id: page.id,
authorId: page.authorId,
authorName: page.authorName,
@ -1219,17 +1224,17 @@ module.exports = class Page extends Model {
description: page.description,
editor: page.editor,
extra: {
css: _.get(page, 'extra.css', ''),
js: _.get(page, 'extra.js', '')
css: get(page, 'extra.css', ''),
js: get(page, 'extra.js', '')
},
publishState: page.publishState ?? '',
publishEndDate: page.publishEndDate ?? '',
publishStartDate: page.publishStartDate ?? '',
render: page.render,
siteId: page.siteId,
tags: page.tags.map(t => _.pick(t, ['tag'])),
tags: page.tags.map(t => pick(t, ['tag'])),
title: page.title,
toc: _.isString(page.toc) ? page.toc : JSON.stringify(page.toc),
toc: isString(page.toc) ? page.toc : JSON.stringify(page.toc),
updatedAt: page.updatedAt.toISOString()
}))
}
@ -1241,11 +1246,11 @@ module.exports = class Page extends Model {
* @returns {Promise} Promise of the Page Model Instance
*/
static async getPageFromCache(opts) {
const pageHash = pageHelper.generateHash({ path: opts.path, locale: opts.locale })
const pageHash = generateHash({ path: opts.path, locale: opts.locale })
const cachePath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${pageHash}.bin`)
try {
const pageBuffer = await fs.readFile(cachePath)
const pageBuffer = await fse.readFile(cachePath)
let page = WIKI.db.pages.cacheSchema.decode(pageBuffer)
return {
...page,
@ -1268,14 +1273,14 @@ module.exports = class Page extends Model {
* @returns {Promise} Promise with no value
*/
static async deletePageFromCache(hash) {
return fs.remove(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${hash}.bin`))
return fse.remove(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${hash}.bin`))
}
/**
* Flush the contents of the Cache
*/
static async flushCache() {
return fs.emptyDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache`))
return fse.emptyDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache`))
}
/**

@ -1,15 +1,15 @@
const Model = require('objection').Model
const path = require('path')
const fs = require('fs-extra')
const _ = require('lodash')
const yaml = require('js-yaml')
const DepGraph = require('dependency-graph').DepGraph
const commonHelper = require('../helpers/common')
import { Model } from 'objection'
import path from 'node:path'
import fs from 'fs/promises'
import { clone, filter, find, get, has, reverse, some, transform, union } from 'lodash-es'
import yaml from 'js-yaml'
import { DepGraph } from 'dependency-graph'
import { parseModuleProps } from '../helpers/common.mjs'
/**
* Renderer model
*/
module.exports = class Renderer extends Model {
export class Renderer extends Model {
static get tableName() { return 'renderers' }
static get jsonSchema () {
@ -42,7 +42,7 @@ module.exports = class Renderer extends Model {
const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/rendering', dir, 'definition.yml'), 'utf8')
const defParsed = yaml.load(def)
defParsed.key = dir
defParsed.props = commonHelper.parseModuleProps(defParsed.props)
defParsed.props = parseModuleProps(defParsed.props)
WIKI.data.renderers.push(defParsed)
WIKI.logger.debug(`Loaded renderers module definition ${dir}: [ OK ]`)
}
@ -65,20 +65,20 @@ module.exports = class Renderer extends Model {
const newRenderers = []
let updatedRenderers = 0
for (const renderer of WIKI.data.renderers) {
if (!_.some(dbRenderers, ['module', renderer.key])) {
if (!some(dbRenderers, ['module', renderer.key])) {
newRenderers.push({
module: renderer.key,
isEnabled: renderer.enabledDefault ?? true,
config: _.transform(renderer.props, (result, value, key) => {
config: transform(renderer.props, (result, value, key) => {
result[key] = value.default
return result
}, {})
})
} else {
const rendererConfig = _.get(_.find(dbRenderers, ['module', renderer.key]), 'config', {})
const rendererConfig = get(find(dbRenderers, ['module', renderer.key]), 'config', {})
await WIKI.db.renderers.query().patch({
config: _.transform(renderer.props, (result, value, key) => {
if (!_.has(result, key)) {
config: transform(renderer.props, (result, value, key) => {
if (!has(result, key)) {
result[key] = value.default
}
return result
@ -98,7 +98,7 @@ module.exports = class Renderer extends Model {
// -> Delete removed Renderers
for (const renderer of dbRenderers) {
if (!_.some(WIKI.data.renderers, ['key', renderer.module])) {
if (!some(WIKI.data.renderers, ['key', renderer.module])) {
await WIKI.db.renderers.query().where('module', renderer.key).del()
WIKI.logger.info(`Removed renderer ${renderer.key} because it is no longer present in the modules folder: [ OK ]`)
}
@ -113,7 +113,7 @@ module.exports = class Renderer extends Model {
const renderersDb = await WIKI.db.renderers.query().where('isEnabled', true)
if (renderersDb && renderersDb.length > 0) {
const renderers = renderersDb.map(rdr => {
const renderer = _.find(WIKI.data.renderers, ['key', rdr.module])
const renderer = find(WIKI.data.renderers, ['key', rdr.module])
return {
...renderer,
config: rdr.config
@ -121,8 +121,8 @@ module.exports = class Renderer extends Model {
})
// Build tree
const rawCores = _.filter(renderers, renderer => !_.has(renderer, 'dependsOn')).map(core => {
core.children = _.filter(renderers, ['dependsOn', core.key])
const rawCores = filter(renderers, renderer => !has(renderer, 'dependsOn')).map(core => {
core.children = filter(renderers, ['dependsOn', core.key])
return core
})
@ -140,11 +140,11 @@ module.exports = class Renderer extends Model {
})
// Filter unused cores
let activeCoreKeys = _.filter(rawCores, ['input', contentType]).map(core => core.key)
_.clone(activeCoreKeys).map(coreKey => {
activeCoreKeys = _.union(activeCoreKeys, graph.dependenciesOf(coreKey))
let activeCoreKeys = filter(rawCores, ['input', contentType]).map(core => core.key)
clone(activeCoreKeys).map(coreKey => {
activeCoreKeys = union(activeCoreKeys, graph.dependenciesOf(coreKey))
})
const activeCores = _.filter(rawCores, core => _.includes(activeCoreKeys, core.key))
const activeCores = filter(rawCores, core => activeCoreKeys.includes(core.key))
// Rebuild dependency graph with active cores
const graphActive = new DepGraph({ circular: true })
@ -161,8 +161,8 @@ module.exports = class Renderer extends Model {
// Reorder cores in reverse dependency order
let orderedCores = []
_.reverse(graphActive.overallOrder()).map(coreKey => {
orderedCores.push(_.find(rawCores, ['key', coreKey]))
reverse(graphActive.overallOrder()).map(coreKey => {
orderedCores.push(find(rawCores, ['key', coreKey]))
})
return orderedCores

@ -1,10 +1,10 @@
const Model = require('objection').Model
const _ = require('lodash')
import { Model } from 'objection'
import { has, reduce, set } from 'lodash-es'
/**
* Settings model
*/
module.exports = class Setting extends Model {
export class Setting extends Model {
static get tableName() { return 'settings' }
static get idColumn() { return 'key' }
@ -26,8 +26,8 @@ module.exports = class Setting extends Model {
static async getConfig() {
const settings = await WIKI.db.settings.query()
if (settings.length > 0) {
return _.reduce(settings, (res, val, key) => {
_.set(res, val.key, (_.has(val.value, 'v')) ? val.value.v : val.value)
return reduce(settings, (res, val, key) => {
set(res, val.key, (has(val.value, 'v')) ? val.value.v : val.value)
return res
}, {})
} else {

@ -1,12 +1,10 @@
const Model = require('objection').Model
const crypto = require('crypto')
const pem2jwk = require('pem-jwk').pem2jwk
const _ = require('lodash')
import { Model } from 'objection'
import { defaultsDeep, keyBy } from 'lodash-es'
/**
* Site model
*/
module.exports = class Site extends Model {
export class Site extends Model {
static get tableName () { return 'sites' }
static get jsonSchema () {
@ -40,7 +38,7 @@ module.exports = class Site extends Model {
static async reloadCache () {
WIKI.logger.info('Reloading site configurations...')
const sites = await WIKI.db.sites.query().orderBy('id')
WIKI.sites = _.keyBy(sites, 'id')
WIKI.sites = keyBy(sites, 'id')
WIKI.sitesMappings = {}
for (const site of sites) {
WIKI.sitesMappings[site.hostname] = site.id
@ -52,7 +50,7 @@ module.exports = class Site extends Model {
const newSite = await WIKI.db.sites.query().insertAndFetch({
hostname,
isEnabled: true,
config: _.defaultsDeep(config, {
config: defaultsDeep(config, {
title: 'My Wiki Site',
description: '',
company: '',

@ -1,14 +1,13 @@
const Model = require('objection').Model
const path = require('path')
const fs = require('fs-extra')
const _ = require('lodash')
const yaml = require('js-yaml')
const commonHelper = require('../helpers/common')
import { Model } from 'objection'
import path from 'node:path'
import fs from 'node:fs/promises'
import { capitalize, find, has, hasIn, uniq } from 'lodash-es'
import yaml from 'js-yaml'
/**
* Storage model
*/
module.exports = class Storage extends Model {
export class Storage extends Model {
static get tableName() { return 'storage' }
static get idColumn() { return 'id' }
@ -67,7 +66,7 @@ module.exports = class Storage extends Model {
* Ensure a storage module is loaded
*/
static async ensureModule (moduleName) {
if (!_.has(WIKI.storage.modules, moduleName)) {
if (!has(WIKI.storage.modules, moduleName)) {
try {
WIKI.storage.modules[moduleName] = require(`../modules/storage/${moduleName}/storage`)
WIKI.logger.debug(`Activated storage module ${moduleName}: [ OK ]`)
@ -87,7 +86,7 @@ module.exports = class Storage extends Model {
*/
static async initTargets () {
const dbTargets = await WIKI.db.storage.query().where('isEnabled', true)
const activeModules = _.uniq(dbTargets.map(t => t.module))
const activeModules = uniq(dbTargets.map(t => t.module))
try {
// -> Stop and delete existing jobs
// const prevjobs = _.remove(WIKI.scheduler.jobs, job => job.name === 'sync-storage')
@ -168,7 +167,7 @@ module.exports = class Storage extends Model {
static async assetEvent({ event, asset }) {
try {
for (let target of this.targets) {
await target.fn[`asset${_.capitalize(event)}`](asset)
await target.fn[`asset${capitalize(event)}`](asset)
}
} catch (err) {
WIKI.logger.warn(err)
@ -195,9 +194,9 @@ module.exports = class Storage extends Model {
static async executeAction(targetKey, handler) {
try {
const target = _.find(this.targets, ['key', targetKey])
const target = find(this.targets, ['key', targetKey])
if (target) {
if (_.hasIn(target.fn, handler)) {
if (hasIn(target.fn, handler)) {
await target.fn[handler]()
} else {
throw new Error('Invalid Handler for Storage Target')

@ -1,10 +1,12 @@
const Model = require('objection').Model
const _ = require('lodash')
import { Model } from 'objection'
import { concat, differenceBy, some, uniq } from 'lodash-es'
import { Page } from './pages.mjs'
/**
* Tags model
*/
module.exports = class Tag extends Model {
export class Tag extends Model {
static get tableName() { return 'tags' }
static get jsonSchema () {
@ -26,7 +28,7 @@ module.exports = class Tag extends Model {
return {
pages: {
relation: Model.ManyToManyRelation,
modelClass: require('./pages'),
modelClass: Page,
join: {
from: 'tags.id',
through: {
@ -52,15 +54,15 @@ module.exports = class Tag extends Model {
// Format tags
tags = _.uniq(tags.map(t => _.trim(t).toLowerCase()))
tags = uniq(tags.map(t => t.trim().toLowerCase()))
// Create missing tags
const newTags = _.filter(tags, t => !_.some(existingTags, ['tag', t])).map(t => ({ tag: t }))
const newTags = tags.filter(t => !some(existingTags, ['tag', t])).map(t => ({ tag: t }))
if (newTags.length > 0) {
if (WIKI.config.db.type === 'postgres') {
const createdTags = await WIKI.db.tags.query().insert(newTags)
existingTags = _.concat(existingTags, createdTags)
existingTags = concat(existingTags, createdTags)
} else {
for (const newTag of newTags) {
const createdTag = await WIKI.db.tags.query().insert(newTag)
@ -71,12 +73,12 @@ module.exports = class Tag extends Model {
// Fetch current page tags
const targetTags = _.filter(existingTags, t => _.includes(tags, t.tag))
const targetTags = existingTags.filter(t => _.includes(tags, t.tag))
const currentTags = await page.$relatedQuery('tags')
// Tags to relate
const tagsToRelate = _.differenceBy(targetTags, currentTags, 'id')
const tagsToRelate = differenceBy(targetTags, currentTags, 'id')
if (tagsToRelate.length > 0) {
if (WIKI.config.db.type === 'postgres') {
await page.$relatedQuery('tags').relate(tagsToRelate)
@ -89,9 +91,9 @@ module.exports = class Tag extends Model {
// Tags to unrelate
const tagsToUnrelate = _.differenceBy(currentTags, targetTags, 'id')
const tagsToUnrelate = differenceBy(currentTags, targetTags, 'id')
if (tagsToUnrelate.length > 0) {
await page.$relatedQuery('tags').unrelate().whereIn('tags.id', _.map(tagsToUnrelate, 'id'))
await page.$relatedQuery('tags').unrelate().whereIn('tags.id', tagsToUnrelate.map(t => t.id))
}
page.tags = targetTags

@ -1,7 +1,9 @@
const Model = require('objection').Model
const _ = require('lodash')
import { Model } from 'objection'
import { differenceWith, dropRight, last, nth } from 'lodash-es'
import { decodeTreePath, encodeTreePath, generateHash } from '../helpers/common.mjs'
const commonHelper = require('../helpers/common')
import { Locale } from './locales.mjs'
import { Site } from './sites.mjs'
const rePathName = /^[a-z0-9-]+$/
const reTitle = /^[^<>"]+$/
@ -9,7 +11,7 @@ const reTitle = /^[^<>"]+$/
/**
* Tree model
*/
module.exports = class Tree extends Model {
export class Tree extends Model {
static get tableName() { return 'tree' }
static get jsonSchema () {
@ -37,7 +39,7 @@ module.exports = class Tree extends Model {
return {
locale: {
relation: Model.BelongsToOneRelation,
modelClass: require('./locales'),
modelClass: Locale,
join: {
from: 'tree.localeCode',
to: 'locales.code'
@ -45,7 +47,7 @@ module.exports = class Tree extends Model {
},
site: {
relation: Model.BelongsToOneRelation,
modelClass: require('./sites'),
modelClass: Site,
join: {
from: 'tree.siteId',
to: 'sites.id'
@ -82,11 +84,11 @@ module.exports = class Tree extends Model {
return parent
} else {
// Get by path
const parentPath = commonHelper.encodeTreePath(path)
const parentPath = encodeTreePath(path)
const parentPathParts = parentPath.split('.')
const parentFilter = {
folderPath: _.dropRight(parentPathParts).join('.'),
fileName: _.last(parentPathParts)
folderPath: dropRight(parentPathParts).join('.'),
fileName: last(parentPathParts)
}
const parent = await WIKI.db.knex('tree').where({
...parentFilter,
@ -133,7 +135,7 @@ module.exports = class Tree extends Model {
folderPath: '',
fileName: ''
}
const folderPath = commonHelper.decodeTreePath(folder.folderPath ? `${folder.folderPath}.${folder.fileName}` : folder.fileName)
const folderPath = decodeTreePath(folder.folderPath ? `${folder.folderPath}.${folder.fileName}` : folder.fileName)
const fullPath = folderPath ? `${folderPath}/${fileName}` : fileName
WIKI.logger.debug(`Adding page ${fullPath} to tree...`)
@ -144,7 +146,7 @@ module.exports = class Tree extends Model {
fileName,
type: 'page',
title: title,
hash: commonHelper.generateHash(fullPath),
hash: generateHash(fullPath),
localeCode: locale,
siteId,
meta
@ -177,7 +179,7 @@ module.exports = class Tree extends Model {
fileName: ''
}
const folderPath = folder.folderPath ? `${folder.folderPath}.${folder.fileName}` : folder.fileName
const decodedFolderPath = commonHelper.decodeTreePath(folderPath)
const decodedFolderPath = decodeTreePath(folderPath)
const fullPath = decodedFolderPath ? `${decodedFolderPath}/${fileName}` : fileName
WIKI.logger.debug(`Adding asset ${fullPath} to tree...`)
@ -188,7 +190,7 @@ module.exports = class Tree extends Model {
fileName,
type: 'asset',
title: title,
hash: commonHelper.generateHash(fullPath),
hash: generateHash(fullPath),
localeCode: locale,
siteId,
meta
@ -219,12 +221,12 @@ module.exports = class Tree extends Model {
throw new Error('ERR_INVALID_TITLE')
}
parentPath = commonHelper.encodeTreePath(parentPath)
parentPath = encodeTreePath(parentPath)
WIKI.logger.debug(`Creating new folder ${pathName}...`)
const parentPathParts = parentPath.split('.')
const parentFilter = {
folderPath: _.dropRight(parentPathParts).join('.'),
fileName: _.last(parentPathParts)
folderPath: dropRight(parentPathParts).join('.'),
fileName: last(parentPathParts)
}
// Get parent path
@ -259,8 +261,8 @@ module.exports = class Tree extends Model {
const parentPathParts = parentPath.split('.')
for (let i = 1; i <= parentPathParts.length; i++) {
const ancestor = {
folderPath: _.dropRight(parentPathParts, i).join('.'),
fileName: _.nth(parentPathParts, i * -1)
folderPath: dropRight(parentPathParts, i).join('.'),
fileName: nth(parentPathParts, i * -1)
}
expectedAncestors.push(ancestor)
builder.orWhere({
@ -269,14 +271,14 @@ module.exports = class Tree extends Model {
})
}
})
for (const ancestor of _.differenceWith(expectedAncestors, existingAncestors, (expAnc, exsAnc) => expAnc.folderPath === exsAnc.folderPath && expAnc.fileName === exsAnc.fileName)) {
for (const ancestor of differenceWith(expectedAncestors, existingAncestors, (expAnc, exsAnc) => expAnc.folderPath === exsAnc.folderPath && expAnc.fileName === exsAnc.fileName)) {
WIKI.logger.debug(`Creating missing parent folder ${ancestor.fileName} at path /${ancestor.folderPath}...`)
const newAncestorFullPath = ancestor.folderPath ? `${commonHelper.decodeTreePath(ancestor.folderPath)}/${ancestor.fileName}` : ancestor.fileName
const newAncestorFullPath = ancestor.folderPath ? `${decodeTreePath(ancestor.folderPath)}/${ancestor.fileName}` : ancestor.fileName
const newAncestor = await WIKI.db.knex('tree').insert({
...ancestor,
type: 'folder',
title: ancestor.fileName,
hash: commonHelper.generateHash(newAncestorFullPath),
hash: generateHash(newAncestorFullPath),
localeCode: locale,
siteId: siteId,
meta: {
@ -292,13 +294,13 @@ module.exports = class Tree extends Model {
}
// Create folder
const fullPath = parentPath ? `${commonHelper.decodeTreePath(parentPath)}/${pathName}` : pathName
const fullPath = parentPath ? `${decodeTreePath(parentPath)}/${pathName}` : pathName
const folder = await WIKI.db.knex('tree').insert({
folderPath: parentPath,
fileName: pathName,
type: 'folder',
title: title,
hash: commonHelper.generateHash(fullPath),
hash: generateHash(fullPath),
localeCode: locale,
siteId: siteId,
meta: {
@ -375,11 +377,11 @@ module.exports = class Tree extends Model {
})
// Rename the folder itself
const fullPath = folder.folderPath ? `${commonHelper.decodeTreePath(folder.folderPath)}/${pathName}` : pathName
const fullPath = folder.folderPath ? `${decodeTreePath(folder.folderPath)}/${pathName}` : pathName
await WIKI.db.knex('tree').where('id', folder.id).update({
fileName: pathName,
title: title,
hash: commonHelper.generateHash(fullPath)
hash: generateHash(fullPath)
})
} else {
// Update the folder title only
@ -437,8 +439,8 @@ module.exports = class Tree extends Model {
if (folder.folderPath) {
const parentPathParts = folder.folderPath.split('.')
const parent = await WIKI.db.knex('tree').where({
folderPath: _.dropRight(parentPathParts).join('.'),
fileName: _.last(parentPathParts)
folderPath: dropRight(parentPathParts).join('.'),
fileName: last(parentPathParts)
}).first()
await WIKI.db.knex('tree').where('id', parent.id).update({
meta: {

@ -1,13 +1,15 @@
/* global WIKI */
const Model = require('objection').Model
const { DateTime } = require('luxon')
const { nanoid } = require('nanoid')
import { Model } from 'objection'
import { DateTime } from 'luxon'
import { nanoid } from 'nanoid'
import { User } from './users.mjs'
/**
* Users model
*/
module.exports = class UserKey extends Model {
export class UserKey extends Model {
static get tableName() { return 'userKeys' }
static get jsonSchema () {
@ -29,7 +31,7 @@ module.exports = class UserKey extends Model {
return {
user: {
relation: Model.BelongsToOneRelation,
modelClass: require('./users'),
modelClass: User,
join: {
from: 'userKeys.userId',
to: 'users.id'

@ -1,18 +1,21 @@
/* global WIKI */
const _ = require('lodash')
const tfa = require('node-2fa')
const jwt = require('jsonwebtoken')
const Model = require('objection').Model
const validate = require('validate.js')
const qr = require('qr-image')
import { difference, find, first, flatten, flattenDeep, get, has, isArray, isEmpty, isNil, last, set, toString, truncate, uniq } from 'lodash-es'
import tfa from 'node-2fa'
import jwt from 'jsonwebtoken'
import { Model } from 'objection'
import validate from 'validate.js'
import qr from 'qr-image'
import { Group } from './groups.mjs'
import { Locale } from './locales.mjs'
const bcryptRegexp = /^\$2[ayb]\$[0-9]{2}\$[A-Za-z0-9./]{53}$/
/**
* Users model
*/
module.exports = class User extends Model {
export class User extends Model {
static get tableName() { return 'users' }
static get jsonSchema () {
@ -42,7 +45,7 @@ module.exports = class User extends Model {
return {
groups: {
relation: Model.ManyToManyRelation,
modelClass: require('./groups'),
modelClass: Group,
join: {
from: 'users.id',
through: {
@ -54,7 +57,7 @@ module.exports = class User extends Model {
},
locale: {
relation: Model.BelongsToOneRelation,
modelClass: require('./locales'),
modelClass: Locale,
join: {
from: 'users.localeCode',
to: 'locales.code'
@ -113,15 +116,15 @@ module.exports = class User extends Model {
verifyTFA(code) {
let result = tfa.verifyToken(this.tfaSecret, code)
return (result && _.has(result, 'delta') && result.delta === 0)
return (result && has(result, 'delta') && result.delta === 0)
}
getPermissions () {
return _.uniq(_.flatten(_.map(this.groups, 'permissions')))
return uniq(flatten(this.groups.map(g => g.permissions)))
}
getGroups() {
return _.uniq(_.map(this.groups, 'id'))
return uniq(this.groups.map(g => g.id))
}
// ------------------------------------------------
@ -135,32 +138,32 @@ module.exports = class User extends Model {
}
static async processProfile({ profile, providerKey }) {
const provider = _.get(WIKI.auth.strategies, providerKey, {})
provider.info = _.find(WIKI.data.authentication, ['key', provider.stategyKey])
const provider = get(WIKI.auth.strategies, providerKey, {})
provider.info = find(WIKI.data.authentication, ['key', provider.stategyKey])
// Find existing user
let user = await WIKI.db.users.query().findOne({
providerId: _.toString(profile.id),
providerId: toString(profile.id),
providerKey
})
// Parse email
let primaryEmail = ''
if (_.isArray(profile.emails)) {
const e = _.find(profile.emails, ['primary', true])
primaryEmail = (e) ? e.value : _.first(profile.emails).value
} else if (_.isArray(profile.email)) {
primaryEmail = _.first(_.flattenDeep([profile.email]))
} else if (_.isString(profile.email) && profile.email.length > 5) {
if (isArray(profile.emails)) {
const e = find(profile.emails, ['primary', true])
primaryEmail = (e) ? e.value : first(profile.emails).value
} else if (isArray(profile.email)) {
primaryEmail = first(flattenDeep([profile.email]))
} else if (isString(profile.email) && profile.email.length > 5) {
primaryEmail = profile.email
} else if (_.isString(profile.mail) && profile.mail.length > 5) {
} else if (isString(profile.mail) && profile.mail.length > 5) {
primaryEmail = profile.mail
} else if (profile.user && profile.user.email && profile.user.email.length > 5) {
primaryEmail = profile.user.email
} else {
throw new Error('Missing or invalid email address from profile.')
}
primaryEmail = _.toLower(primaryEmail)
primaryEmail = primaryEmail.toLowerCase()
// Find pending social user
if (!user) {
@ -171,16 +174,16 @@ module.exports = class User extends Model {
})
if (user) {
user = await user.$query().patchAndFetch({
providerId: _.toString(profile.id)
providerId: toString(profile.id)
})
}
}
// Parse display name
let displayName = ''
if (_.isString(profile.displayName) && profile.displayName.length > 0) {
if (isString(profile.displayName) && profile.displayName.length > 0) {
displayName = profile.displayName
} else if (_.isString(profile.name) && profile.name.length > 0) {
} else if (isString(profile.name) && profile.name.length > 0) {
displayName = profile.name
} else {
displayName = primaryEmail.split('@')[0]
@ -191,7 +194,7 @@ module.exports = class User extends Model {
if (profile.picture && Buffer.isBuffer(profile.picture)) {
pictureUrl = 'internal'
} else {
pictureUrl = _.truncate(_.get(profile, 'picture', _.get(user, 'pictureUrl', null)), {
pictureUrl = truncate(get(profile, 'picture', get(user, 'pictureUrl', null)), {
length: 255,
omission: ''
})
@ -222,9 +225,9 @@ module.exports = class User extends Model {
// Self-registration
if (provider.selfRegistration) {
// Check if email domain is whitelisted
if (_.get(provider, 'domainWhitelist', []).length > 0) {
const emailDomain = _.last(primaryEmail.split('@'))
if (!_.includes(provider.domainWhitelist, emailDomain)) {
if (get(provider, 'domainWhitelist', []).length > 0) {
const emailDomain = last(primaryEmail.split('@'))
if (!provider.domainWhitelist.includes(emailDomain)) {
throw new WIKI.Error.AuthRegistrationDomainUnauthorized()
}
}
@ -232,7 +235,7 @@ module.exports = class User extends Model {
// Create account
user = await WIKI.db.users.query().insertAndFetch({
providerKey: providerKey,
providerId: _.toString(profile.id),
providerId: toString(profile.id),
email: primaryEmail,
name: displayName,
pictureUrl: pictureUrl,
@ -263,19 +266,19 @@ module.exports = class User extends Model {
* Login a user
*/
static async login (opts, context) {
if (_.has(WIKI.auth.strategies, opts.strategy)) {
const selStrategy = _.get(WIKI.auth.strategies, opts.strategy)
if (has(WIKI.auth.strategies, opts.strategy)) {
const selStrategy = get(WIKI.auth.strategies, opts.strategy)
if (!selStrategy.isEnabled) {
throw new WIKI.Error.AuthProviderInvalid()
}
const strInfo = _.find(WIKI.data.authentication, ['key', selStrategy.module])
const strInfo = find(WIKI.data.authentication, ['key', selStrategy.module])
// Inject form user/pass
if (strInfo.useForm) {
_.set(context.req, 'body.email', opts.username)
_.set(context.req, 'body.password', opts.password)
_.set(context.req.params, 'strategy', opts.strategy)
set(context.req, 'body.email', opts.username)
set(context.req, 'body.password', opts.password)
set(context.req.params, 'strategy', opts.strategy)
}
// Authenticate
@ -312,7 +315,7 @@ module.exports = class User extends Model {
let redirect = '/'
if (user.groups && user.groups.length > 0) {
for (const grp of user.groups) {
if (!_.isEmpty(grp.redirectOnLogin) && grp.redirectOnLogin !== '/') {
if (!isEmpty(grp.redirectOnLogin) && grp.redirectOnLogin !== '/') {
redirect = grp.redirectOnLogin
break
}
@ -391,7 +394,7 @@ module.exports = class User extends Model {
* Generate a new token for a user
*/
static async refreshToken(user, provider) {
if (_.isString(user)) {
if (isString(user)) {
user = await WIKI.db.users.query().findById(user).withGraphFetched('groups').modifyGraph('groups', builder => {
builder.select('groups.id', 'permissions')
})
@ -403,7 +406,7 @@ module.exports = class User extends Model {
WIKI.logger.warn(`Failed to refresh token for user ${user}: Inactive.`)
throw new WIKI.Error.AuthAccountBanned()
}
} else if (_.isNil(user.groups)) {
} else if (isNil(user.groups)) {
user.groups = await user.$relatedQuery('groups').select('groups.id', 'permissions')
}
@ -523,7 +526,7 @@ module.exports = class User extends Model {
*/
static async createNewUser ({ providerKey, email, passwordRaw, name, groups, mustChangePassword, sendWelcomeEmail }) {
// Input sanitization
email = _.toLower(email)
email = email.toLowerCase()
// Input validation
let validation = null
@ -643,7 +646,7 @@ module.exports = class User extends Model {
const usr = await WIKI.db.users.query().findById(id)
if (usr) {
let usrData = {}
if (!_.isEmpty(email) && email !== usr.email) {
if (!isEmpty(email) && email !== usr.email) {
const dupUsr = await WIKI.db.users.query().select('id').where({
email,
providerKey: usr.providerKey
@ -651,44 +654,44 @@ module.exports = class User extends Model {
if (dupUsr) {
throw new WIKI.Error.AuthAccountAlreadyExists()
}
usrData.email = _.toLower(email)
usrData.email = email.toLowerCase()
}
if (!_.isEmpty(name) && name !== usr.name) {
usrData.name = _.trim(name)
if (!isEmpty(name) && name !== usr.name) {
usrData.name = name.trim()
}
if (!_.isEmpty(newPassword)) {
if (!isEmpty(newPassword)) {
if (newPassword.length < 6) {
throw new WIKI.Error.InputInvalid('Password must be at least 6 characters!')
}
usrData.password = newPassword
}
if (_.isArray(groups)) {
if (isArray(groups)) {
const usrGroupsRaw = await usr.$relatedQuery('groups')
const usrGroups = _.map(usrGroupsRaw, 'id')
const usrGroups = usrGroupsRaw.map(g => g.id)
// Relate added groups
const addUsrGroups = _.difference(groups, usrGroups)
const addUsrGroups = difference(groups, usrGroups)
for (const grp of addUsrGroups) {
await usr.$relatedQuery('groups').relate(grp)
}
// Unrelate removed groups
const remUsrGroups = _.difference(usrGroups, groups)
const remUsrGroups = difference(usrGroups, groups)
for (const grp of remUsrGroups) {
await usr.$relatedQuery('groups').unrelate().where('groupId', grp)
}
}
if (!_.isEmpty(location) && location !== usr.location) {
usrData.location = _.trim(location)
if (!isEmpty(location) && location !== usr.location) {
usrData.location = location.trim()
}
if (!_.isEmpty(jobTitle) && jobTitle !== usr.jobTitle) {
usrData.jobTitle = _.trim(jobTitle)
if (!isEmpty(jobTitle) && jobTitle !== usr.jobTitle) {
usrData.jobTitle = jobTitle.trim()
}
if (!_.isEmpty(timezone) && timezone !== usr.timezone) {
if (!isEmpty(timezone) && timezone !== usr.timezone) {
usrData.timezone = timezone
}
if (!_.isNil(dateFormat) && dateFormat !== usr.dateFormat) {
if (!isNil(dateFormat) && dateFormat !== usr.dateFormat) {
usrData.dateFormat = dateFormat
}
if (!_.isNil(appearance) && appearance !== usr.appearance) {
if (!isNil(appearance) && appearance !== usr.appearance) {
usrData.appearance = appearance
}
await WIKI.db.users.query().patch(usrData).findById(id)
@ -729,7 +732,7 @@ module.exports = class User extends Model {
// Check if self-registration is enabled
if (localStrg.selfRegistration || bypassChecks) {
// Input sanitization
email = _.toLower(email)
email = email.toLowerCase()
// Input validation
const validation = validate({
@ -766,9 +769,9 @@ module.exports = class User extends Model {
}
// Check if email domain is whitelisted
if (_.get(localStrg, 'domainWhitelist.v', []).length > 0 && !bypassChecks) {
const emailDomain = _.last(email.split('@'))
if (!_.includes(localStrg.domainWhitelist.v, emailDomain)) {
if (get(localStrg, 'domainWhitelist.v', []).length > 0 && !bypassChecks) {
const emailDomain = last(email.split('@'))
if (!localStrg.domainWhitelist.v.includes(emailDomain)) {
throw new WIKI.Error.AuthRegistrationDomainUnauthorized()
}
}
@ -790,7 +793,7 @@ module.exports = class User extends Model {
})
// Assign to group(s)
if (_.get(localStrg, 'autoEnrollGroups.v', []).length > 0) {
if (get(localStrg, 'autoEnrollGroups.v', []).length > 0) {
await newUsr.$relatedQuery('groups').relate(localStrg.autoEnrollGroups.v)
}
@ -832,12 +835,12 @@ module.exports = class User extends Model {
if (!context.req.user || context.req.user.id === WIKI.config.auth.guestUserId) {
return '/'
}
if (context.req.user.strategyId && _.has(WIKI.auth.strategies, context.req.user.strategyId)) {
if (context.req.user.strategyId && has(WIKI.auth.strategies, context.req.user.strategyId)) {
const selStrategy = WIKI.auth.strategies[context.req.user.strategyId]
if (!selStrategy.isEnabled) {
throw new WIKI.Error.AuthProviderInvalid()
}
const provider = _.find(WIKI.data.authentication, ['key', selStrategy.module])
const provider = find(WIKI.data.authentication, ['key', selStrategy.module])
if (provider.logout) {
return provider.logout(provider.config)
}

File diff suppressed because it is too large Load Diff

@ -3,12 +3,13 @@
"version": "3.0.0",
"releaseDate": "2023-01-01T01:01:01.000Z",
"description": "The most powerful and extensible open source Wiki software",
"main": "index.js",
"main": "index.mjs",
"type": "module",
"private": true,
"dev": true,
"scripts": {
"start": "cd .. && node server",
"dev": "cd .. && nodemon server --watch server --ext js,json,graphql,gql"
"dev": "cd .. && nodemon server --watch server --ext mjs,js,json,graphql,gql"
},
"repository": {
"type": "git",
@ -33,94 +34,91 @@
"node": ">=18.0"
},
"dependencies": {
"@azure/storage-blob": "12.11.0",
"@azure/storage-blob": "12.13.0",
"@exlinc/keycloak-passport": "1.0.2",
"@graphql-tools/schema": "8.3.7",
"@graphql-tools/utils": "8.6.6",
"@joplin/turndown-plugin-gfm": "1.0.44",
"@graphql-tools/schema": "9.0.17",
"@graphql-tools/utils": "9.2.1",
"@joplin/turndown-plugin-gfm": "1.0.47",
"@root/csr": "0.8.1",
"@root/keypairs": "0.10.3",
"@root/pem": "1.0.4",
"acme": "3.0.3",
"akismet-api": "5.3.0",
"akismet-api": "6.0.0",
"apollo-fetch": "0.7.0",
"apollo-server": "3.6.7",
"apollo-server-express": "3.6.7",
"auto-load": "3.0.4",
"aws-sdk": "2.1208.0",
"aws-sdk": "2.1353.0",
"bcryptjs-then": "1.0.1",
"body-parser": "1.20.0",
"chalk": "4.1.2",
"body-parser": "1.20.2",
"chalk": "5.2.0",
"cheerio": "1.0.0-rc.12",
"chokidar": "3.5.3",
"chromium-pickle-js": "0.2.0",
"clean-css": "4.2.3",
"clean-css": "5.3.2",
"command-exists": "1.2.9",
"compression": "1.7.4",
"connect-session-knex": "3.0.0",
"connect-session-knex": "3.0.1",
"cookie-parser": "1.4.6",
"cors": "2.8.5",
"cron-parser": "4.6.0",
"cron-parser": "4.8.1",
"cuint": "0.2.2",
"custom-error-instance": "2.1.2",
"dependency-graph": "0.9.0",
"diff": "4.0.2",
"diff2html": "3.1.14",
"dompurify": "2.4.0",
"dependency-graph": "0.11.0",
"diff": "5.1.0",
"diff2html": "3.4.34",
"dompurify": "3.0.1",
"dotize": "0.3.0",
"emoji-regex": "10.1.0",
"eventemitter2": "6.4.7",
"express": "4.18.1",
"emoji-regex": "10.2.1",
"eventemitter2": "6.4.9",
"express": "4.18.2",
"express-brute": "1.0.1",
"express-session": "1.17.3",
"file-type": "15.0.1",
"filesize": "6.1.0",
"fs-extra": "9.0.1",
"file-type": "18.2.1",
"filesize": "10.0.7",
"fs-extra": "11.1.1",
"getos": "3.2.1",
"graphql": "16.3.0",
"graphql": "16.6.0",
"graphql-list-fields": "2.0.2",
"graphql-rate-limit-directive": "2.0.2",
"graphql-tools": "8.2.5",
"graphql-upload": "13.0.0",
"graphql-rate-limit-directive": "2.0.3",
"graphql-tools": "8.3.19",
"graphql-upload": "16.0.2",
"he": "1.2.0",
"highlight.js": "10.3.1",
"i18next": "19.8.3",
"highlight.js": "11.7.0",
"i18next": "22.4.14",
"i18next-node-fs-backend": "2.1.3",
"image-size": "0.9.2",
"js-base64": "3.7.2",
"image-size": "1.0.2",
"js-base64": "3.7.5",
"js-binary": "1.2.0",
"js-yaml": "4.1.0",
"jsdom": "16.4.0",
"jsonwebtoken": "8.5.1",
"katex": "0.12.0",
"klaw": "4.0.1",
"knex": "2.3.0",
"jsdom": "21.1.1",
"jsonwebtoken": "9.0.0",
"katex": "0.16.4",
"klaw": "4.1.0",
"knex": "2.4.2",
"lodash": "4.17.21",
"luxon": "2.3.1",
"markdown-it": "11.0.1",
"lodash-es": "4.17.21",
"luxon": "3.3.0",
"markdown-it": "13.0.1",
"markdown-it-abbr": "1.0.4",
"markdown-it-attrs": "3.0.3",
"markdown-it-emoji": "1.4.0",
"markdown-it-emoji": "2.0.2",
"markdown-it-expand-tabs": "1.0.13",
"markdown-it-external-links": "0.0.6",
"markdown-it-footnote": "3.0.3",
"markdown-it-imsize": "2.0.1",
"markdown-it-mark": "3.0.1",
"markdown-it-mathjax": "2.0.0",
"markdown-it-multimd-table": "4.0.3",
"markdown-it-sub": "1.0.0",
"markdown-it-sup": "1.0.0",
"markdown-it-task-lists": "2.1.1",
"mathjax": "3.1.2",
"mathjax": "3.2.2",
"mime-types": "2.1.35",
"moment": "2.29.2",
"moment-timezone": "0.5.31",
"ms": "2.1.3",
"multer": "1.4.4",
"nanoid": "3.3.2",
"node-2fa": "1.1.2",
"nanoid": "4.0.2",
"node-2fa": "2.0.3",
"node-cache": "5.1.2",
"nodemailer": "6.7.8",
"nodemailer": "6.9.1",
"objection": "3.0.1",
"passport": "0.6.0",
"passport-auth0": "1.4.3",
@ -132,60 +130,59 @@
"passport-github2": "0.1.12",
"passport-gitlab2": "5.0.0",
"passport-google-oauth20": "2.0.0",
"passport-jwt": "4.0.0",
"passport-jwt": "4.0.1",
"passport-ldapauth": "3.0.1",
"passport-local": "1.0.0",
"passport-microsoft": "1.0.0",
"passport-oauth2": "1.6.1",
"passport-oauth2": "1.7.0",
"passport-okta-oauth": "0.0.1",
"passport-openidconnect": "0.1.1",
"passport-saml": "3.2.1",
"passport-slack-oauth2": "1.1.1",
"passport-twitch-strategy": "2.2.0",
"pem-jwk": "2.0.0",
"pg": "8.8.0",
"pg": "8.10.0",
"pg-hstore": "2.3.4",
"pg-pubsub": "0.8.0",
"pg-query-stream": "4.2.4",
"pg-tsquery": "8.4.0",
"poolifier": "2.2.0",
"pg-pubsub": "0.8.1",
"pg-query-stream": "4.4.0",
"pg-tsquery": "8.4.1",
"poolifier": "2.4.4",
"pug": "3.0.2",
"punycode": "2.1.1",
"puppeteer-core": "17.1.3",
"punycode": "2.3.0",
"puppeteer-core": "19.8.5",
"qr-image": "3.2.0",
"rate-limiter-flexible": "2.3.8",
"remove-markdown": "0.3.0",
"rate-limiter-flexible": "2.4.1",
"remove-markdown": "0.5.0",
"request": "2.88.2",
"request-promise": "4.2.6",
"safe-regex": "2.1.1",
"sanitize-filename": "1.6.3",
"scim-query-filter-parser": "2.0.4",
"semver": "7.3.7",
"semver": "7.3.8",
"serve-favicon": "2.5.0",
"sharp": "0.31.0",
"simple-git": "2.21.0",
"socket.io": "4.5.2",
"ssh2": "1.9.0",
"ssh2-promise": "1.0.2",
"sharp": "0.32.0",
"simple-git": "3.17.0",
"socket.io": "4.6.1",
"ssh2": "1.11.0",
"ssh2-promise": "1.0.3",
"striptags": "3.2.0",
"tar-fs": "2.1.1",
"turndown": "7.1.1",
"twemoji": "13.1.0",
"turndown": "7.1.2",
"uslug": "1.0.4",
"uuid": "8.3.2",
"uuid": "9.0.0",
"validate.js": "0.13.1",
"xss": "1.0.14",
"yargs": "16.1.0"
"yargs": "17.7.1"
},
"devDependencies": {
"eslint": "7.12.0",
"eslint": "8.38.0",
"eslint-config-requarks": "1.0.7",
"eslint-config-standard": "15.0.0",
"eslint-plugin-import": "2.22.1",
"eslint-config-standard": "17.0.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "4.2.1",
"eslint-plugin-standard": "4.0.2",
"nodemon": "2.0.15"
"eslint-plugin-promise": "6.1.1",
"eslint-plugin-standard": "4.1.0",
"nodemon": "2.0.22"
},
"collective": {
"type": "opencollective",

@ -1,4 +1,4 @@
module.exports = async (payload) => {
export async function task (payload) {
WIKI.logger.info('Checking for latest version...')
try {

@ -1,6 +1,6 @@
const { DateTime } = require('luxon')
import { DateTime } from 'luxon'
module.exports = async (payload) => {
export async function task (payload) {
WIKI.logger.info('Cleaning scheduler job history...')
try {

@ -1,4 +1,4 @@
module.exports = async (payload) => {
export async function task (payload) {
WIKI.logger.info('Fetching latest localization data...')
try {

@ -1,20 +1,20 @@
const path = require('node:path')
const fs = require('fs-extra')
const { DateTime } = require('luxon')
import path from 'node:path'
import fse from 'fs-extra'
import { DateTime } from 'luxon'
module.exports = async ({ payload }) => {
export async function task ({ payload }) {
WIKI.logger.info('Purging orphaned upload files...')
try {
const uplTempPath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'uploads')
await fs.ensureDir(uplTempPath)
const ls = await fs.readdir(uplTempPath)
await fse.ensureDir(uplTempPath)
const ls = await fse.readdir(uplTempPath)
const fifteenAgo = DateTime.now().minus({ minutes: 15 })
for (const f of ls) {
const stat = fs.stat(path.join(uplTempPath, f))
const stat = fse.stat(path.join(uplTempPath, f))
if ((await stat).isFile && stat.ctime < fifteenAgo) {
await fs.unlink(path.join(uplTempPath, f))
await fse.unlink(path.join(uplTempPath, f))
}
}

@ -1,7 +1,7 @@
const _ = require('lodash')
const cheerio = require('cheerio')
import { get, has, isEmpty, reduce, times, toSafeInteger } from 'lodash-es'
import cheerio from 'cheerio'
module.exports = async ({ payload }) => {
export async function task ({ payload }) {
WIKI.logger.info(`Rendering page ${payload.id}...`)
try {
@ -20,7 +20,7 @@ module.exports = async ({ payload }) => {
let output = page.render
if (_.isEmpty(page.content)) {
if (isEmpty(page.content)) {
WIKI.logger.warn(`Failed to render page ID ${payload.id} because content was empty: [ FAILED ]`)
}
@ -41,11 +41,11 @@ module.exports = async ({ payload }) => {
let toc = { root: [] }
$('h1,h2,h3,h4,h5,h6').each((idx, el) => {
const depth = _.toSafeInteger(el.name.substring(1)) - (isStrict ? 1 : 2)
const depth = toSafeInteger(el.name.substring(1)) - (isStrict ? 1 : 2)
let leafPathError = false
const leafPath = _.reduce(_.times(depth), (curPath, curIdx) => {
if (_.has(toc, curPath)) {
const leafPath = reduce(times(depth), (curPath, curIdx) => {
if (has(toc, curPath)) {
const lastLeafIdx = _.get(toc, curPath).length - 1
if (lastLeafIdx >= 0) {
curPath = `${curPath}[${lastLeafIdx}].children`
@ -61,8 +61,8 @@ module.exports = async ({ payload }) => {
const leafSlug = $('.toc-anchor', el).first().attr('href')
$('.toc-anchor', el).remove()
_.get(toc, leafPath).push({
label: _.trim($(el).text()),
get(toc, leafPath).push({
label: $(el).text().trim(),
key: leafSlug.substring(1),
children: []
})

@ -1,30 +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 session = require('express-session')
const KnexSessionStore = require('connect-session-knex')(session)
const favicon = require('serve-favicon')
const path = require('path')
const _ = require('lodash')
module.exports = async () => {
import bodyParser from 'body-parser'
import compression from 'compression'
import cookieParser from 'cookie-parser'
import cors from 'cors'
import express from 'express'
import session from 'express-session'
import KnexSessionStore from 'connect-session-knex'
import favicon from 'serve-favicon'
import path from 'node:path'
import { set } from 'lodash-es'
import auth from './core/auth.mjs'
import mail from './core/mail.mjs'
import system from './core/system.mjs'
import ctrlAuth from './controllers/auth.mjs'
import ctrlCommon from './controllers/common.mjs'
import ctrlSsl from './controllers/ssl.mjs'
import ctrlWs from './controllers/ws.mjs'
export async function init () {
// ----------------------------------------
// Load core modules
// ----------------------------------------
WIKI.auth = require('./core/auth').init()
WIKI.mail = require('./core/mail').init()
WIKI.system = require('./core/system').init()
// ----------------------------------------
// Load middlewares
// ----------------------------------------
const mw = autoload(path.join(WIKI.SERVERPATH, '/middlewares'))
const ctrl = autoload(path.join(WIKI.SERVERPATH, '/controllers'))
WIKI.auth = auth.init()
WIKI.mail = mail.init()
WIKI.system = system.init()
// ----------------------------------------
// Define Express App
@ -41,24 +42,57 @@ module.exports = async () => {
const useHTTPS = WIKI.config.ssl.enabled === true || WIKI.config.ssl.enabled === 'true' || WIKI.config.ssl.enabled === 1 || WIKI.config.ssl.enabled === '1'
await WIKI.servers.initHTTP()
if (useHTTPS) {
await WIKI.servers.initHTTPS()
}
await WIKI.servers.initWebSocket()
// ----------------------------------------
// Attach WebSocket Server
// ----------------------------------------
ctrl.ws()
ctrlWs()
// ----------------------------------------
// Security
// ----------------------------------------
app.use(mw.security)
app.use((req, res, next) => {
// -> Disable X-Powered-By
req.app.disable('x-powered-by')
// -> Disable Frame Embedding
if (WIKI.config.security.securityIframe) {
res.set('X-Frame-Options', 'deny')
}
// -> Re-enable XSS Fitler if disabled
res.set('X-XSS-Protection', '1; mode=block')
// -> Disable MIME-sniffing
res.set('X-Content-Type-Options', 'nosniff')
// -> Disable IE Compatibility Mode
res.set('X-UA-Compatible', 'IE=edge')
// -> Disables referrer header when navigating to a different origin
if (WIKI.config.security.securityReferrerPolicy) {
res.set('Referrer-Policy', 'same-origin')
}
// -> Enforce HSTS
if (WIKI.config.security.securityHSTS) {
res.set('Strict-Transport-Security', `max-age=${WIKI.config.security.securityHSTSDuration}; includeSubDomains`)
}
// -> Prevent Open Redirect from user provided URL
if (WIKI.config.security.securityOpenRedirect) {
// Strips out all repeating / character in the provided URL
req.url = req.url.replace(/(\/)(?=\/*\1)/g, '')
}
next()
})
app.use(cors({ origin: false }))
app.options('*', cors({ origin: false }))
if (WIKI.config.security.securityTrustProxy) {
@ -86,7 +120,7 @@ module.exports = async () => {
// SSL Handlers
// ----------------------------------------
app.use('/', ctrl.ssl)
app.use('/', ctrlSsl())
// ----------------------------------------
// Passport Authentication
@ -97,7 +131,7 @@ module.exports = async () => {
secret: WIKI.config.auth.secret,
resave: false,
saveUninitialized: false,
store: new KnexSessionStore({
store: new KnexSessionStore(session)({
knex: WIKI.db.knex
})
}))
@ -115,7 +149,15 @@ module.exports = async () => {
// SEO
// ----------------------------------------
app.use(mw.seo)
app.use((req, res, next) => {
if (req.path.length > 1 && req.path.endsWith('/')) {
let query = req.url.slice(req.path.length) || ''
res.redirect(301, req.path.slice(0, -1) + query)
} else {
set(res.locals, 'pageMeta.url', `${WIKI.config.host}${req.path}`)
next()
}
})
// ----------------------------------------
// View Engine Setup
@ -177,9 +219,8 @@ module.exports = async () => {
next()
})
app.use('/', ctrl.auth)
app.use('/', ctrl.upload)
app.use('/', ctrl.common)
app.use('/', ctrlAuth())
app.use('/', ctrlCommon())
// ----------------------------------------
// Error handling
@ -202,7 +243,7 @@ module.exports = async () => {
})
} else {
res.status(err.status || 500)
_.set(res.locals, 'pageMeta.title', 'Error')
set(res.locals, 'pageMeta.title', 'Error')
res.render('error', {
message: err.message,
error: WIKI.IS_DEBUG ? err : {}

@ -1,12 +1,12 @@
const { ThreadWorker } = require('poolifier')
const { kebabCase } = require('lodash')
const path = require('node:path')
import { ThreadWorker } from 'poolifier'
import { kebabCase } from 'lodash-es'
import path from 'node:path'
// ----------------------------------------
// Init Minimal Core
// ----------------------------------------
let WIKI = {
const WIKI = {
IS_DEBUG: process.env.NODE_ENV === 'development',
ROOTPATH: process.cwd(),
INSTANCE_ID: 'worker',
@ -39,9 +39,9 @@ WIKI.logger = require('./core/logger').init()
// Execute Task
// ----------------------------------------
module.exports = new ThreadWorker(async (job) => {
export default new ThreadWorker(async (job) => {
WIKI.INSTANCE_ID = job.INSTANCE_ID
const task = require(`./tasks/workers/${kebabCase(job.task)}.js`)
const task = (await import(`./tasks/workers/${kebabCase(job.task)}.mjs`)).task
await task(job)
return true
}, { async: true })

350
ux/package-lock.json generated

@ -11,32 +11,32 @@
"@apollo/client": "3.7.11",
"@lezer/common": "1.0.2",
"@mdi/font": "7.2.96",
"@quasar/extras": "1.16.1",
"@tiptap/core": "2.0.1",
"@tiptap/extension-code-block": "2.0.1",
"@tiptap/extension-code-block-lowlight": "2.0.1",
"@tiptap/extension-color": "2.0.1",
"@tiptap/extension-dropcursor": "2.0.1",
"@tiptap/extension-font-family": "2.0.1",
"@tiptap/extension-gapcursor": "2.0.1",
"@tiptap/extension-hard-break": "2.0.1",
"@tiptap/extension-highlight": "2.0.1",
"@tiptap/extension-history": "2.0.1",
"@tiptap/extension-image": "2.0.1",
"@tiptap/extension-mention": "2.0.1",
"@tiptap/extension-placeholder": "2.0.1",
"@tiptap/extension-table": "2.0.1",
"@tiptap/extension-table-cell": "2.0.1",
"@tiptap/extension-table-header": "2.0.1",
"@tiptap/extension-table-row": "2.0.1",
"@tiptap/extension-task-item": "2.0.1",
"@tiptap/extension-task-list": "2.0.1",
"@tiptap/extension-text-align": "2.0.1",
"@tiptap/extension-text-style": "2.0.1",
"@tiptap/extension-typography": "2.0.1",
"@tiptap/pm": "2.0.1",
"@tiptap/starter-kit": "2.0.1",
"@tiptap/vue-3": "2.0.1",
"@quasar/extras": "1.16.2",
"@tiptap/core": "2.0.2",
"@tiptap/extension-code-block": "2.0.2",
"@tiptap/extension-code-block-lowlight": "2.0.2",
"@tiptap/extension-color": "2.0.2",
"@tiptap/extension-dropcursor": "2.0.2",
"@tiptap/extension-font-family": "2.0.2",
"@tiptap/extension-gapcursor": "2.0.2",
"@tiptap/extension-hard-break": "2.0.2",
"@tiptap/extension-highlight": "2.0.2",
"@tiptap/extension-history": "2.0.2",
"@tiptap/extension-image": "2.0.2",
"@tiptap/extension-mention": "2.0.2",
"@tiptap/extension-placeholder": "2.0.2",
"@tiptap/extension-table": "2.0.2",
"@tiptap/extension-table-cell": "2.0.2",
"@tiptap/extension-table-header": "2.0.2",
"@tiptap/extension-table-row": "2.0.2",
"@tiptap/extension-task-item": "2.0.2",
"@tiptap/extension-task-list": "2.0.2",
"@tiptap/extension-text-align": "2.0.2",
"@tiptap/extension-text-style": "2.0.2",
"@tiptap/extension-typography": "2.0.2",
"@tiptap/pm": "2.0.2",
"@tiptap/starter-kit": "2.0.2",
"@tiptap/vue-3": "2.0.2",
"apollo-upload-client": "17.0.0",
"browser-fs-access": "0.33.0",
"clipboard": "2.0.11",
@ -78,7 +78,7 @@
"prosemirror-transform": "1.7.1",
"prosemirror-view": "1.30.2",
"pug": "3.0.2",
"quasar": "2.11.9",
"quasar": "2.11.10",
"slugify": "1.6.6",
"socket.io-client": "4.6.1",
"tabulator-tables": "5.4.4",
@ -652,9 +652,9 @@
}
},
"node_modules/@quasar/extras": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-1.16.1.tgz",
"integrity": "sha512-bRnWSC469Qogw0ceDVd0yTQVBQjyhV6L10EMixXK1dpOs9tWGKRVgyyGZ5DEBkDgyn4BeRuV5s5e9VrQdQPsOg==",
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-1.16.2.tgz",
"integrity": "sha512-spDc1DrwxGts0MjmOAJ11xpxJANhCI1vEadxaw89wRQJ/QfKd0HZrwN7uN1U15cRozGRkJpdbsnP4cVXpkPysA==",
"funding": {
"type": "github",
"url": "https://donate.quasar.dev"
@ -762,9 +762,9 @@
"license": "MIT"
},
"node_modules/@tiptap/core": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.0.1.tgz",
"integrity": "sha512-IY5K17e1YdlJIykCt3NuOyqK/SHyGwk2X7eyLYfZJGJwqzuPuf3y8X1zZQQN53t5UVfsgqsF9RweVEee40o1Aw==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.0.2.tgz",
"integrity": "sha512-DBry6tpX7mYaTJkEDjVA4WmF8Kgthr275L0uIIOVdwW5nG5PAnOvREKyVOoMQnN3vR7CjtaCK+c3y+MCQhMA/g==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -774,9 +774,9 @@
}
},
"node_modules/@tiptap/extension-blockquote": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.0.1.tgz",
"integrity": "sha512-j4n5iTeBc/YIoW83nZPoc1/fKeoA32tPaOH/quQdtkBgXEM9r4PiK7NfsJxbFq7UGk0oa3ibRSiBxzODgZIVlw==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.0.2.tgz",
"integrity": "sha512-KY4PZtQRf06sC2B1nKkm1hI2y7XFWqqA2lAmWRu12m7Zofc9aabLipEY8yijY7se0QMc4kDTVWp8d2uvbhyDFA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -786,9 +786,9 @@
}
},
"node_modules/@tiptap/extension-bold": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.0.1.tgz",
"integrity": "sha512-SYzs0e3FcAmJbYQkQ+LLRnoBNrFvG0i2zc+lFbrSYVCiEpqb5QPz5mgmQvnrH/Sl89WewnoeMu7pLqVEpfAWuw==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.0.2.tgz",
"integrity": "sha512-WRqc8WeKx3pmi0u0Svre5rhMeTT1c/Vch48BWTkUsmn2PAufg/mrmmR1fJ8Bp5soazEKknOT5LVe69OYKLfHIQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -798,9 +798,9 @@
}
},
"node_modules/@tiptap/extension-bubble-menu": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.1.tgz",
"integrity": "sha512-ZA8T7+yjlHzEjBeOlWAqz/9XvBb/rJ7/PiYjM57UXuT/ZgPMPL7KXI/KtS7vaRCnmKaGL6EJ1tBcZhjU24vNzA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.2.tgz",
"integrity": "sha512-cZDAMnf1+E711zY9RApWFajNp+ScRdN3L9+k6XEUnmTHlfVIeE1jsPmpH3PIZpMUpAO4TQ76DvLfEb4hKSu3eQ==",
"dependencies": {
"tippy.js": "^6.3.7"
},
@ -814,9 +814,9 @@
}
},
"node_modules/@tiptap/extension-bullet-list": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.1.tgz",
"integrity": "sha512-IniXv9VgHkyWF2wJKxoILhNnJPwWNlIRW2LNSlXzkl70k0BsPGcAfiEIJtPqpVwh03QPc5v1y5UeuOOO6VQbqA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.2.tgz",
"integrity": "sha512-n6P4N+3dZqBnyxpc6pz4qYHLOYr2oy6+K662GKzNQqe1TFSVr9+Vc/JmXiVPgwERs6RW6/Kdo7/9s454eDTIzg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -826,9 +826,9 @@
}
},
"node_modules/@tiptap/extension-code": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.0.1.tgz",
"integrity": "sha512-9Ygk2Ijfu89JFRwtId1x/u3yJUMfVSo6bkiUUmIFf04evgIgzBYMNl/KQerapmkcbkXWGCZ7/gcu+WUmU4c7lQ==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.0.2.tgz",
"integrity": "sha512-YG8BlqK50ZyYpBZ3KtO9/Ao+hQr3Z9wuW0fi7VIlwWferB20Px9WlBolAipRxfCh+oLmiacipHBIdGZGD+rC6A==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -838,9 +838,9 @@
}
},
"node_modules/@tiptap/extension-code-block": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.0.1.tgz",
"integrity": "sha512-dPGKAlg0P2Qpikp8IF3r3kAD9II/Mc9cB8Wa3czwPr8r9Oobyy3UL7R7WqMy33abLgHmS/RuerOsTXz6sHBMww==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.0.2.tgz",
"integrity": "sha512-GL8ogok1tl1FkXwk0P0ZWYh6oAmSA+R3oubtDZJG1fLlezKLcLYCN/Q2jgYDHDwEOnxMc4JIiT7EYwJ0pqmNaQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -851,9 +851,9 @@
}
},
"node_modules/@tiptap/extension-code-block-lowlight": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.0.1.tgz",
"integrity": "sha512-AUEeOfHSYh9a7+Lv57LSnYOJkC1lUhQC3AtjJK/1LG8NzDuL1XY+wpP0WKLKsH2qr95bJNi26amIcdVGtIVupg==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.0.2.tgz",
"integrity": "sha512-7BbRCKJE2oxsZ5n7HIjS0r/y1S/bSxEJgAFF1Tj3KN2IG3x48w+sqYxRMYmCZdoTexmmBpNF64uYXngKXB9/Ig==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -865,9 +865,9 @@
}
},
"node_modules/@tiptap/extension-color": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-color/-/extension-color-2.0.1.tgz",
"integrity": "sha512-NCfrwXvGjXbfHbm4DBjvJz1rXlWEXUtsXIXCVhRj/bUJHx/g1gV9Q5wcOLVWpd2h3y18H2YbO0LS1OypW8huYQ==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-color/-/extension-color-2.0.2.tgz",
"integrity": "sha512-2TwXnGpMgbc1VYgUSWEEReR4Yy4K0rbdifO7RKy9xOuccT8+F5EccoWzb8R7ZMB4QVJ1cE9b3Pc0miPL43Tt3w==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -878,9 +878,9 @@
}
},
"node_modules/@tiptap/extension-document": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.0.1.tgz",
"integrity": "sha512-9T14qBa6uKieJ+FCGm0nL9o8LTjwqlfc2pYQkkjXXWFivMXoVeL9rVIlarANgbJZKYsZ4L5tkyXcKQ32nZmMjg==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.0.2.tgz",
"integrity": "sha512-rY87m1sezlD37v5hGndiA/B/3upR3hQurSEsWhWyQE/11lOshPQKCCHfDV6KLwKdjd8lfwfbXueH/SBFHtrYAQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -890,9 +890,9 @@
}
},
"node_modules/@tiptap/extension-dropcursor": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.1.tgz",
"integrity": "sha512-OuqMhj13hPSdxc6G3xDmGvwe3AfczMQjAoSPakNW9pSSzrjJJa3Tr08kI+TyZDUm3vKM4af+lL/oCeXSupGAbQ==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.2.tgz",
"integrity": "sha512-jHBq5fAlgUeZBHPtdAeS40Xmx+2sUvzTnHonU6cDKOpD3+dzwNHEqVSk/9vcjEIcqeM5w3eOIS/AGvoFq7RCjA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -903,9 +903,9 @@
}
},
"node_modules/@tiptap/extension-floating-menu": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.1.tgz",
"integrity": "sha512-gOf7FyromNyQpdaU/MWV99KMu6vypN+IlK8ViCBPvcM7w+e5eTh39wY/u5OquM7LsXd8KXLC2neq1XzQsAVmzw==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.2.tgz",
"integrity": "sha512-sYBBmMyjygHimtekZMzOAU1yvHjn36O1tZ+lXR/K2F97WWd5Y9WJo+iUrnbzBDMTXGvPi5ZTByXg36YKFRTmPg==",
"dependencies": {
"tippy.js": "^6.3.7"
},
@ -919,9 +919,9 @@
}
},
"node_modules/@tiptap/extension-font-family": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-font-family/-/extension-font-family-2.0.1.tgz",
"integrity": "sha512-uUJippccCmy6qKCvzLJSphS79Cu1FghHLFQwc2NETmXAVNmyRcb8/61tF4hqDt8eEokvEdi8YNWD3JDm/wOBuw==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-font-family/-/extension-font-family-2.0.2.tgz",
"integrity": "sha512-uJ52qvSml2r8vtaZg4A2Cz7jx4+V5lT9cEKXsctH0uJJ4r5kMWOp0u7YB3AUGasOIpi/Mg++IK9dqt8zMadAlg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -932,9 +932,9 @@
}
},
"node_modules/@tiptap/extension-gapcursor": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.1.tgz",
"integrity": "sha512-NZbT4BIpXlf3gxqQMsZ173bJreGfr43eBwR/tJ8nBLcgBmFKiDfRTW3Whj7+EGD6Ek6rqUX0EekKG93Wes+I4w==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.2.tgz",
"integrity": "sha512-NimvDbM8Cc8+l/ZWJW8aqZRH6hzz1iJLOAMyj23UjHQWvKO0yqE1KBLGZI2GU+vizEK3LkZZXhXnh76rnTwgSQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -945,9 +945,9 @@
}
},
"node_modules/@tiptap/extension-hard-break": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.0.1.tgz",
"integrity": "sha512-H8NdmOuYehuMMRd0iWOeyN8ukAebqu6BWbDiZUKm+yx+1b7woadVofojLnGlreQT2II3cC4g8D9J7JfrcQtbyg==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.0.2.tgz",
"integrity": "sha512-X/HG9Mzb4Qx/x67tkLK0JdG/fNL7IMP5gTpqOdgRT4m4vQA4ItuZHi4GlvWXfvtP6J0qfKKNimJvBQ5TZCZV1A==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -957,9 +957,9 @@
}
},
"node_modules/@tiptap/extension-heading": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.0.1.tgz",
"integrity": "sha512-JkwlePSyDraVrWisr5lE/yqK4FXm5YSiiQiIo/1W9vFLdMPsAbRCSZrBO4fV7gyppbFgQQikEWDgxYHshq/TOg==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.0.2.tgz",
"integrity": "sha512-99PL9Rx8mioo13SNuIkhZm1VW6UuUKKG0b7fAimsunaEbbLNtToXIasS68pX8BkgWnsBfDhR0HyhnpIBRg7W0w==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -969,9 +969,9 @@
}
},
"node_modules/@tiptap/extension-highlight": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-2.0.1.tgz",
"integrity": "sha512-k9rJnAGzjXWQSN3RqSVPQfsbtFAgZu2AnwSFH2VQ2Ea7oiAUOaD7Qcqrs0PLo/CxLEuWPAbULNngXUXZVseMBA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-2.0.2.tgz",
"integrity": "sha512-FLW7VvHr6UjiHCxzGMrnTBCslDfUqpLJjg0j6P4+0ksKKEcjfgRNkI8ln5NB1IyphXYUvWczeD83rMTtX8+feA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -981,9 +981,9 @@
}
},
"node_modules/@tiptap/extension-history": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.0.1.tgz",
"integrity": "sha512-nT4XbZUEyi+xSnwIeG+JqfHmWhF+amCwYsr7oYM/oa/BFDGaRHaNIrS3D+DpzjRrKguuLDxyhZkJEu1K2Rjsqw==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.0.2.tgz",
"integrity": "sha512-K7u8HCEh9g35DSB4+c7z1wj9ais2cF3STDhyCRjBuLcCTgWVAim5cto4MkSdrXnLuWGgNK0jloWxVlMk3E1HPg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -994,9 +994,9 @@
}
},
"node_modules/@tiptap/extension-horizontal-rule": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.1.tgz",
"integrity": "sha512-JNgu0ioOBhn3Djn8vd41y3DrqeEfWMLIWNRVO81OqQRvWZhpRwIUuDoRrzR45wxjymq9flc+iz7zuFW1kszpQA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.2.tgz",
"integrity": "sha512-QUqNnHqRXMZRE6uFRX0UDFn07JDiiNukPRCIL4vyV/YJ6HmQiBjbsS/1hLjAwBuqaN8dtlHLdR4PjMuA6qrUYw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -1007,9 +1007,9 @@
}
},
"node_modules/@tiptap/extension-image": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.0.1.tgz",
"integrity": "sha512-FMXY0Y3nsDtAb1gNGidNhXnyJTZw6bOwYXg6jEV/K05HIz/svMQVkXLR04vSKczr7DWnlOmaJiqzge9Hc9WJ9Q==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.0.2.tgz",
"integrity": "sha512-Jyfzp78Mn20R1Bcpz4Cm47EOU5xrOcDkhSisvNcFqcaxSV/CHyNVPDRpRMussK3k7qDcDuf2flDjrTRXPWZqIg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -1019,9 +1019,9 @@
}
},
"node_modules/@tiptap/extension-italic": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.0.1.tgz",
"integrity": "sha512-lVIyKa3qjqD4rUKSzVghkS5xonJ8k+msvy5Sr5xd6duwEBOk7e0oKtXmdn7B1VBH64i/U3Cx0QRgDlEyukvV2Q==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.0.2.tgz",
"integrity": "sha512-AK8m4JKvjv2LgTho2GLPW4UI9Z8Oc1tErG9TAeJzud8NFFZ2iOOueAwyGjPLaEQxWaE2p5xlKjbBUWDV5bd6AQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -1031,9 +1031,9 @@
}
},
"node_modules/@tiptap/extension-list-item": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.0.1.tgz",
"integrity": "sha512-WxLRcwhMGp12+hV1nLTRVxPJ3ZsjLzA3tyaYySBf6IQpmoikH8DbzvprwV30lHfYbZvcQWQJx3ECIcQdGbtPPg==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.0.2.tgz",
"integrity": "sha512-q8nz79iB6EvkBucm0OwUg7SX3vDGCRttmf0OrEBsoUCoFt/NhrejUCMDSbysHtYp+EtZcKu4VsPuNHU3C7F1vQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -1043,9 +1043,9 @@
}
},
"node_modules/@tiptap/extension-mention": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-mention/-/extension-mention-2.0.1.tgz",
"integrity": "sha512-Wb7uX+MgjxMvhaaO6oTRcMBkhmsA7Rd5cERe4Yc2jkqIZKoyoL91AL5qqFJxVEhn18bXsqxbaCBpcjLxjYNCIQ==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-mention/-/extension-mention-2.0.2.tgz",
"integrity": "sha512-5nUSlqYi5x+I5Q84+l8OL+4R/NRAN0wBrEN0Cps4GJB3dqINMsTbjSbIwRk8bw79XBHNJI1PndBjVktBgwYxNA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -1057,9 +1057,9 @@
}
},
"node_modules/@tiptap/extension-ordered-list": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.1.tgz",
"integrity": "sha512-7Q3hqAy2TYZ1p3J0GtEDcUJrbC/NxzWNF1H4zKW3VQGQhffUDNgHBk4uCLLVyf1A3vm50JmEAwQFrrwoozFmqQ==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.2.tgz",
"integrity": "sha512-fgWIQNDlC2pYNnS9Iqw2UZaNIInfks3mZm0Aj9DQ78LbTYDAcGvHcWaulLkd0CcfD6smTYwi9oBzP3dpPbcPTw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -1069,9 +1069,9 @@
}
},
"node_modules/@tiptap/extension-paragraph": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.0.1.tgz",
"integrity": "sha512-fVr5BZ7glyf/80vIO+GSQdeIcTvuXKGW9QvU0Mw8y1ek9Edd3MLWMxKdDurTswApnxIluvJFjmzp4XEogHHlhg==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.0.2.tgz",
"integrity": "sha512-BuiUV8Wh8DjRrNmmk5sIlVlk0V8P4BT2fCB4H7Ar+QDUPKY1g0djB/5g3F70iZh1Z7CiY1ctoL8VQznvtb0KsQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -1081,9 +1081,9 @@
}
},
"node_modules/@tiptap/extension-placeholder": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.0.1.tgz",
"integrity": "sha512-Jc0SrZw6HQ6Ddxr58SAAw5QPYh2pgjc1OSfrKn7+zBzcZDMFLWWgx9lTRwlR5L5VqTEuWqatJCfDrgjbnE4hLw==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.0.2.tgz",
"integrity": "sha512-5D46ONEN4Hcgn9xwWgY0mSUp9DAM/z74P9vZcdChUXxj96L2ngM/nU92qEby0kGCSGoO3ucxJGf/aL8KhGqIxQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -1094,9 +1094,9 @@
}
},
"node_modules/@tiptap/extension-strike": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.0.1.tgz",
"integrity": "sha512-gHNO47T5tbeFzhjJUn2Ob5RrqkG4joVyvUv3fFkSqpNngTIcqT5hJ7A3tDj2CKQGmUpU5SxM68sHMoIGpXdWIQ==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.0.2.tgz",
"integrity": "sha512-v5goRcfORMkHaA4/mQH80mO0IVFJup1sSrSd4lNUwWN68Qy2ckMVRLRgvbk0JjG2kbamFgJ4LmN/3ceZvUj27A==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -1106,9 +1106,9 @@
}
},
"node_modules/@tiptap/extension-table": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-2.0.1.tgz",
"integrity": "sha512-HpFuDrBolIC9E2KGZ4PAEncgX+Kk3kcCccG/Cuiigb3M/VnJm6l5m5UEOa9egSVW2wmdj4xsM9adfHNcsWi6Mw==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-2.0.2.tgz",
"integrity": "sha512-0HUpCQ+roTZqgMDtKQfgq8Yjo5SSxUJc+lMVHqcdeW2UwPkwXfAPCZ0ojle8SyQgQjv6GTcVTssvzq7+HGpd0Q==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -1119,9 +1119,9 @@
}
},
"node_modules/@tiptap/extension-table-cell": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-2.0.1.tgz",
"integrity": "sha512-ymxDC5leH/e7/V1JAup478nt3ellxMxJ9F+q/y3O/mN0wkisZXGWue2GYPMx+ymrKyF1K0pS2I0AYqxQ6HzMPw==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-2.0.2.tgz",
"integrity": "sha512-tz4H+GPhV/Ed7DG4GXChlq1+sbWwBNb1v9Lp0D1JiU3VKzO9ZbOiPg0YDn+m3cE2rhpVXNKvZzlAG0IALl0VEQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -1131,9 +1131,9 @@
}
},
"node_modules/@tiptap/extension-table-header": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-2.0.1.tgz",
"integrity": "sha512-QBpoIZQ7skaqQZE62xjwxtaVuJb42g7w4sqLYLwH2ChYBurpsbBp8fb2V/8GtuTsJNdXNmsYmZAkJw0KkHk4jA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-2.0.2.tgz",
"integrity": "sha512-v0diLuGQ200uZ1cl+xkFpRozLtuMpAFd/G2JFsi2o2bRNh2U7y5F48LOCIoeOOr5vqDy4qD04feUVWv/qdYMjg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -1143,9 +1143,9 @@
}
},
"node_modules/@tiptap/extension-table-row": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-2.0.1.tgz",
"integrity": "sha512-Asa7f6no2z+yGZ7BVmvBLEMFCs5oW2c4a6aMuoG/qHQzuIRfI7y433Pe+SUJRhWXNLNLJLhM42g+fBgzFABo0g==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-2.0.2.tgz",
"integrity": "sha512-R5mI/S9iMNVqvdUVHEOC96C55vmIkvYEO7UQUijXgsjPHufoKQr+i2EH85BbmzHL+JdF9S5MMShczQJzJuAAkA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -1155,9 +1155,9 @@
}
},
"node_modules/@tiptap/extension-task-item": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-task-item/-/extension-task-item-2.0.1.tgz",
"integrity": "sha512-JKiXxvBJqn4d6vdJ2ejDFXExxGPwlAeaj8uQNzUMI93v5pkqyeU22lYMoMBK0EPeBy7EhMiIEcxY8tonNFT73g==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-task-item/-/extension-task-item-2.0.2.tgz",
"integrity": "sha512-YGzQuLXC4HQOHcvGw2MArUVDxEW2LX7IV3qeS89+H2HWZQDoWOoGRMHBZoRRAgzppG36Y5rQVWae/avDc5RuOQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -1168,9 +1168,9 @@
}
},
"node_modules/@tiptap/extension-task-list": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-task-list/-/extension-task-list-2.0.1.tgz",
"integrity": "sha512-jGP94bR2FyvZwOE2f6SmW7H6RBfhMkwc+HaFwYHadECbDbmXgIzITI85T4qWm481PhXKOEK0i2OTcJiEjU5P5Q==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-task-list/-/extension-task-list-2.0.2.tgz",
"integrity": "sha512-lxrRYNKwX1P/Q8HmbtL+IqoMwr4A4MSWUKdXFKde/bbXfFczeOopfW9ioaIvvMiXYIoDz8x3/QpHRxt8t11pCA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -1180,9 +1180,9 @@
}
},
"node_modules/@tiptap/extension-text": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.0.1.tgz",
"integrity": "sha512-jwfpf71JZn04T4xcOMGJXCHTZoyvVtBusn6ZyRSQT4cVnDUgWcIbmIW5xccvGy8klBepBDS2ff+Nce3pxQnqug==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.0.2.tgz",
"integrity": "sha512-kAO+WurWOyHIV/x8qHMF3bSlWrdlPtjEYmf+w8wHKy3FzE55eF6SsGt4FymClNkJmyXdgflXBB3Wv/Z53myy8g==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -1192,9 +1192,9 @@
}
},
"node_modules/@tiptap/extension-text-align": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-2.0.1.tgz",
"integrity": "sha512-XhMcx7qaReJlB5BeaXFQ+8QZED45/BRwCFqE+WkfwvWH1M/ySiiazOJyootDTudx0lijbf2rUCUKYc4oexoMew==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-2.0.2.tgz",
"integrity": "sha512-cNYOQPpqAOquTjSm2lSzP9J1d5T9FueWArq2A3FsYzOSZfVAJmqp9gn/bfCKveKgE5m7nLN3g3o+RNryIWTr2w==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -1204,9 +1204,9 @@
}
},
"node_modules/@tiptap/extension-text-style": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.0.1.tgz",
"integrity": "sha512-fAS3zGsSW2xOuozmpKiRzOkC031GLK+V3yXWOOMWPOKwx1YoA7Q3RFELXgZGIO1lHJnwcrKZBCcu+4VAxmaNug==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.0.2.tgz",
"integrity": "sha512-0XJJA4MKBi/jOfN60LC8beSzjx0BMWFhZqtFczfOq73YTbQriDOkKFLn3AiEc8oRlYNdRnIg4chVPCheBkoKwg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -1216,9 +1216,9 @@
}
},
"node_modules/@tiptap/extension-typography": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/extension-typography/-/extension-typography-2.0.1.tgz",
"integrity": "sha512-0yyM9YjNx71zmb8EYIVIxU0C2zi5IDe3FzCDEdqMzi6bwZZzrtqYeX/fG1IW974RVWKGu+ScrNDd5Y5lcUriOw==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/extension-typography/-/extension-typography-2.0.2.tgz",
"integrity": "sha512-EopmjSP9ZKM98vUa+W29L5SxfQgBOgJTZJ6oR3jvQ15Ctu0pssOAI+7u7U5OnzUaaiR3w4M6a9iDAwCjSmwNiA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@ -1228,9 +1228,9 @@
}
},
"node_modules/@tiptap/pm": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.0.1.tgz",
"integrity": "sha512-Vu6PAVNbIArhNCA3TXC9bX4Qj4duI+3jZhGptTDU+4wLHSjp6p1zM+RKRHFdIIBZG+v9uATWyitrvNd7qHlJdQ==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.0.2.tgz",
"integrity": "sha512-vXlI82bZ4XrmVD6m/pO27gqlm+tU57mpjy9WjkJpEUOifQZK8LihR3l5k55Z0RqalV4/E79iU1cp8mw0v13nhA==",
"dependencies": {
"prosemirror-changeset": "^2.2.0",
"prosemirror-collab": "^1.3.0",
@ -1260,29 +1260,29 @@
}
},
"node_modules/@tiptap/starter-kit": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.0.1.tgz",
"integrity": "sha512-LxSAwVpaLsbUodDQRvfAiWc6QYG2Fq1m5ZDzoPBAFJq/aCcI0Gt5Gf3AQrOWpuj9FV8kelbr9gUkPqJR60K+Pg==",
"dependencies": {
"@tiptap/core": "^2.0.1",
"@tiptap/extension-blockquote": "^2.0.1",
"@tiptap/extension-bold": "^2.0.1",
"@tiptap/extension-bullet-list": "^2.0.1",
"@tiptap/extension-code": "^2.0.1",
"@tiptap/extension-code-block": "^2.0.1",
"@tiptap/extension-document": "^2.0.1",
"@tiptap/extension-dropcursor": "^2.0.1",
"@tiptap/extension-gapcursor": "^2.0.1",
"@tiptap/extension-hard-break": "^2.0.1",
"@tiptap/extension-heading": "^2.0.1",
"@tiptap/extension-history": "^2.0.1",
"@tiptap/extension-horizontal-rule": "^2.0.1",
"@tiptap/extension-italic": "^2.0.1",
"@tiptap/extension-list-item": "^2.0.1",
"@tiptap/extension-ordered-list": "^2.0.1",
"@tiptap/extension-paragraph": "^2.0.1",
"@tiptap/extension-strike": "^2.0.1",
"@tiptap/extension-text": "^2.0.1"
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.0.2.tgz",
"integrity": "sha512-s0yE6nEVYnxnHHdLxZ6McMhOAMbZ1czAj9qvmc1Mra0U/p08fg3VTn1gIgy8FA3t0TWUNpuwsO4FcgKlWaQ+eg==",
"dependencies": {
"@tiptap/core": "^2.0.2",
"@tiptap/extension-blockquote": "^2.0.2",
"@tiptap/extension-bold": "^2.0.2",
"@tiptap/extension-bullet-list": "^2.0.2",
"@tiptap/extension-code": "^2.0.2",
"@tiptap/extension-code-block": "^2.0.2",
"@tiptap/extension-document": "^2.0.2",
"@tiptap/extension-dropcursor": "^2.0.2",
"@tiptap/extension-gapcursor": "^2.0.2",
"@tiptap/extension-hard-break": "^2.0.2",
"@tiptap/extension-heading": "^2.0.2",
"@tiptap/extension-history": "^2.0.2",
"@tiptap/extension-horizontal-rule": "^2.0.2",
"@tiptap/extension-italic": "^2.0.2",
"@tiptap/extension-list-item": "^2.0.2",
"@tiptap/extension-ordered-list": "^2.0.2",
"@tiptap/extension-paragraph": "^2.0.2",
"@tiptap/extension-strike": "^2.0.2",
"@tiptap/extension-text": "^2.0.2"
},
"funding": {
"type": "github",
@ -1304,12 +1304,12 @@
}
},
"node_modules/@tiptap/vue-3": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tiptap/vue-3/-/vue-3-2.0.1.tgz",
"integrity": "sha512-4szBIsZZvnnRv6G7MoQ4nFIYwAvcOSbj+FsR/H666VK+sU0zwaD3opqN/5c2kYMeeCmmU/UuMbnQ9slQEnETjA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@tiptap/vue-3/-/vue-3-2.0.2.tgz",
"integrity": "sha512-Z0Znj1a+4AW58749PvEPi/YjSY3U4Pu28g7jjD6rGcae0DCa0g8TQuPsPqgxBsZkFJ0JJisasEAKJRX96zlUbg==",
"dependencies": {
"@tiptap/extension-bubble-menu": "^2.0.1",
"@tiptap/extension-floating-menu": "^2.0.1"
"@tiptap/extension-bubble-menu": "^2.0.2",
"@tiptap/extension-floating-menu": "^2.0.2"
},
"funding": {
"type": "github",
@ -6433,9 +6433,9 @@
}
},
"node_modules/quasar": {
"version": "2.11.9",
"resolved": "https://registry.npmjs.org/quasar/-/quasar-2.11.9.tgz",
"integrity": "sha512-ZHSZRQJk/zN6whhvihh0EyRpAs3IYBZZ+1O5/RSe7nXyOXOVi6RwPBzleab5HoTxtHL5Yuk3/bKgb3CywAyBQQ==",
"version": "2.11.10",
"resolved": "https://registry.npmjs.org/quasar/-/quasar-2.11.10.tgz",
"integrity": "sha512-pV7bMdY/FUmOvNhZ2XjKSXJH92fsDu0cU/z7a9roPKV54cW41N1en3sLATrirjPComyZnk4uXrMdGtXp8+IpCg==",
"engines": {
"node": ">= 10.18.1",
"npm": ">= 6.13.4",

@ -8,38 +8,40 @@
"scripts": {
"dev": "quasar dev",
"build": "quasar build",
"lint": "eslint --ext .js,.vue ./"
"lint": "eslint --ext .js,.vue ./",
"ncu": "ncu -x codemirror,codemirror-asciidoc",
"ncu-u": "ncu -u -x codemirror,codemirror-asciidoc"
},
"dependencies": {
"@apollo/client": "3.7.11",
"@lezer/common": "1.0.2",
"@mdi/font": "7.2.96",
"@quasar/extras": "1.16.1",
"@tiptap/core": "2.0.1",
"@tiptap/extension-code-block": "2.0.1",
"@tiptap/extension-code-block-lowlight": "2.0.1",
"@tiptap/extension-color": "2.0.1",
"@tiptap/extension-dropcursor": "2.0.1",
"@tiptap/extension-font-family": "2.0.1",
"@tiptap/extension-gapcursor": "2.0.1",
"@tiptap/extension-hard-break": "2.0.1",
"@tiptap/extension-highlight": "2.0.1",
"@tiptap/extension-history": "2.0.1",
"@tiptap/extension-image": "2.0.1",
"@tiptap/extension-mention": "2.0.1",
"@tiptap/extension-placeholder": "2.0.1",
"@tiptap/extension-table": "2.0.1",
"@tiptap/extension-table-cell": "2.0.1",
"@tiptap/extension-table-header": "2.0.1",
"@tiptap/extension-table-row": "2.0.1",
"@tiptap/extension-task-item": "2.0.1",
"@tiptap/extension-task-list": "2.0.1",
"@tiptap/extension-text-align": "2.0.1",
"@tiptap/extension-text-style": "2.0.1",
"@tiptap/extension-typography": "2.0.1",
"@tiptap/pm": "2.0.1",
"@tiptap/starter-kit": "2.0.1",
"@tiptap/vue-3": "2.0.1",
"@quasar/extras": "1.16.2",
"@tiptap/core": "2.0.2",
"@tiptap/extension-code-block": "2.0.2",
"@tiptap/extension-code-block-lowlight": "2.0.2",
"@tiptap/extension-color": "2.0.2",
"@tiptap/extension-dropcursor": "2.0.2",
"@tiptap/extension-font-family": "2.0.2",
"@tiptap/extension-gapcursor": "2.0.2",
"@tiptap/extension-hard-break": "2.0.2",
"@tiptap/extension-highlight": "2.0.2",
"@tiptap/extension-history": "2.0.2",
"@tiptap/extension-image": "2.0.2",
"@tiptap/extension-mention": "2.0.2",
"@tiptap/extension-placeholder": "2.0.2",
"@tiptap/extension-table": "2.0.2",
"@tiptap/extension-table-cell": "2.0.2",
"@tiptap/extension-table-header": "2.0.2",
"@tiptap/extension-table-row": "2.0.2",
"@tiptap/extension-task-item": "2.0.2",
"@tiptap/extension-task-list": "2.0.2",
"@tiptap/extension-text-align": "2.0.2",
"@tiptap/extension-text-style": "2.0.2",
"@tiptap/extension-typography": "2.0.2",
"@tiptap/pm": "2.0.2",
"@tiptap/starter-kit": "2.0.2",
"@tiptap/vue-3": "2.0.2",
"apollo-upload-client": "17.0.0",
"browser-fs-access": "0.33.0",
"clipboard": "2.0.11",
@ -81,7 +83,7 @@
"prosemirror-transform": "1.7.1",
"prosemirror-view": "1.30.2",
"pug": "3.0.2",
"quasar": "2.11.9",
"quasar": "2.11.10",
"slugify": "1.6.6",
"socket.io-client": "4.6.1",
"tabulator-tables": "5.4.4",

Loading…
Cancel
Save