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' export default {
module.exports = {
arabic: '\u0600-\u06ff\u0750-\u077f\ufb50-\ufc3f\ufe70-\ufefc', arabic: '\u0600-\u06ff\u0750-\u077f\ufb50-\ufc3f\ufe70-\ufefc',
cjk: '\u4E00-\u9FBF\u3040-\u309F\u30A0-\u30FFㄱ-ㅎ가-힣ㅏ-ㅣ', cjk: '\u4E00-\u9FBF\u3040-\u309F\u30A0-\u30FFㄱ-ㅎ가-힣ㅏ-ㅣ',
youtube: /(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|&v(?:i)?=))([^#&?]*).*/, 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') import chalk from 'chalk'
const os = require('node:os') import os from 'node:os'
module.exports = () => { export default function () {
WIKI.servers.ws.on('connection', (socket) => { WIKI.servers.ws.on('connection', (socket) => {
// TODO: Validate token + permissions // TODO: Validate token + permissions
const token = socket.handshake.auth.token const token = socket.handshake.auth.token
console.info(token) // console.info(token)
const listeners = {} const listeners = {}
socket.on('server:logs', () => { 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) => { listeners.serverLogs = (msg) => {
socket.emit('server:log', msg) socket.emit('server:log', msg)
} }

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

@ -1,24 +1,25 @@
const passport = require('passport') import passport from 'passport'
const passportJWT = require('passport-jwt') import passportJWT from 'passport-jwt'
const _ = require('lodash') import _ from 'lodash'
const jwt = require('jsonwebtoken') import jwt from 'jsonwebtoken'
const ms = require('ms') import ms from 'ms'
const { DateTime } = require('luxon') import { DateTime } from 'luxon'
const util = require('node:util') import util from 'node:util'
const crypto = require('node:crypto') import crypto from 'node:crypto'
const randomBytes = util.promisify(crypto.randomBytes) import { pem2jwk } from 'pem-jwk'
const pem2jwk = require('pem-jwk').pem2jwk 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: {}, strategies: {},
guest: { guest: {
cacheExpiration: DateTime.utc().minus({ days: 1 }) cacheExpiration: DateTime.utc().minus({ days: 1 })
}, },
groups: {}, groups: {},
validApiKeys: [], validApiKeys: [],
revocationList: require('./cache').init(), revocationList: new NodeCache(),
/** /**
* Initialize the authentication module * Initialize the authentication module
@ -64,7 +65,7 @@ module.exports = {
// Load JWT // Load JWT
passport.use('jwt', new passportJWT.Strategy({ passport.use('jwt', new passportJWT.Strategy({
jwtFromRequest: securityHelper.extractJWT, jwtFromRequest: extractJWT,
secretOrKey: WIKI.config.auth.certs.public, secretOrKey: WIKI.config.auth.certs.public,
audience: WIKI.config.auth.audience, audience: WIKI.config.auth.audience,
issuer: 'urn:wiki.js', issuer: 'urn:wiki.js',
@ -141,7 +142,7 @@ module.exports = {
// Revalidate and renew token // Revalidate and renew token
if (mustRevalidate) { if (mustRevalidate) {
const jwtPayload = jwt.decode(securityHelper.extractJWT(req)) const jwtPayload = jwt.decode(extractJWT(req))
try { try {
const newToken = await WIKI.db.users.refreshToken(jwtPayload.id, jwtPayload.pvd) const newToken = await WIKI.db.users.refreshToken(jwtPayload.id, jwtPayload.pvd)
user = newToken.user 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') import { defaultsDeep, get, isPlainObject } from 'lodash-es'
const chalk = require('chalk') import chalk from 'chalk'
const cfgHelper = require('../helpers/config') import cfgHelper from '../helpers/config.mjs'
const fs = require('fs') import regexData from '../app/regex.mjs'
const path = require('path') import fs from 'node:fs/promises'
const yaml = require('js-yaml') import path from 'node:path'
import yaml from 'js-yaml'
module.exports = {
export default {
/** /**
* Load root config from disk * Load root config from disk
*/ */
init(silent = false) { async init(silent = false) {
let confPaths = { const confPaths = {
config: path.join(WIKI.ROOTPATH, 'config.yml'), config: path.join(WIKI.ROOTPATH, 'config.yml'),
data: path.join(WIKI.SERVERPATH, 'app/data.yml'), data: path.join(WIKI.SERVERPATH, 'app/data.yml')
dataRegex: path.join(WIKI.SERVERPATH, 'app/regex.js')
} }
if (process.env.dockerdev) { if (process.env.dockerdev) {
@ -34,11 +34,11 @@ module.exports = {
try { try {
appconfig = yaml.load( appconfig = yaml.load(
cfgHelper.parseConfigValue( cfgHelper.parseConfigValue(
fs.readFileSync(confPaths.config, 'utf8') await fs.readFile(confPaths.config, 'utf8')
) )
) )
appdata = yaml.load(fs.readFileSync(confPaths.data, 'utf8')) appdata = yaml.load(await fs.readFile(confPaths.data, 'utf8'))
appdata.regex = require(confPaths.dataRegex) appdata.regex = regexData
if (!silent) { if (!silent) {
console.info(chalk.green.bold(`OK`)) console.info(chalk.green.bold(`OK`))
} }
@ -52,7 +52,7 @@ module.exports = {
// Merge with defaults // Merge with defaults
appconfig = _.defaultsDeep(appconfig, appdata.defaults.config) appconfig = defaultsDeep(appconfig, appdata.defaults.config)
// Override port // Override port
@ -66,7 +66,7 @@ module.exports = {
// Load package info // 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 // Load DB Password from Docker Secret File
if (process.env.DB_PASS_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.`)) console.info(chalk.blue(`DB_PASS_FILE is defined. Will use secret from file.`))
} }
try { 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) { } catch (err) {
console.error(chalk.red.bold(`>>> Failed to read Docker Secret File using path defined in DB_PASS_FILE env variable!`)) console.error(chalk.red.bold(`>>> Failed to read Docker Secret File using path defined in DB_PASS_FILE env variable!`))
console.error(err.message) console.error(err.message)
@ -93,9 +93,9 @@ module.exports = {
* Load config from DB * Load config from DB
*/ */
async loadFromDb() { async loadFromDb() {
let conf = await WIKI.db.settings.getConfig() const conf = await WIKI.db.settings.getConfig()
if (conf) { if (conf) {
WIKI.config = _.defaultsDeep(conf, WIKI.config) WIKI.config = defaultsDeep(conf, WIKI.config)
} else { } else {
WIKI.logger.warn('Missing DB Configuration!') WIKI.logger.warn('Missing DB Configuration!')
process.exit(1) process.exit(1)
@ -110,8 +110,8 @@ module.exports = {
async saveToDb(keys, propagate = true) { async saveToDb(keys, propagate = true) {
try { try {
for (let key of keys) { for (let key of keys) {
let value = _.get(WIKI.config, key, null) let value = get(WIKI.config, key, null)
if (!_.isPlainObject(value)) { if (!isPlainObject(value)) {
value = { v: value } value = { v: value }
} }
let affectedRows = await WIKI.db.settings.query().patch({ value }).where('key', key) let affectedRows = await WIKI.db.settings.query().patch({ value }).where('key', key)

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

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

@ -1,16 +1,24 @@
const _ = require('lodash') import { padEnd } from 'lodash-es'
const EventEmitter = require('eventemitter2').EventEmitter2 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 let isShuttingDown = false
module.exports = { export default {
async init() { async init() {
WIKI.logger.info('=======================================') 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('=======================================')
WIKI.logger.info('Initializing...') WIKI.logger.info('Initializing...')
WIKI.logger.info(`Running node.js ${process.version}`)
WIKI.db = require('./db').init() WIKI.db = await db.init()
try { try {
await WIKI.db.onReady await WIKI.db.onReady
@ -31,15 +39,15 @@ module.exports = {
*/ */
async preBootWeb() { async preBootWeb() {
try { try {
WIKI.cache = require('./cache').init() WIKI.cache = new NodeCache()
WIKI.scheduler = await require('./scheduler').init() WIKI.scheduler = await scheduler.init()
WIKI.servers = require('./servers') WIKI.servers = servers
WIKI.events = { WIKI.events = {
inbound: new EventEmitter(), inbound: new eventemitter2.EventEmitter2(),
outbound: new EventEmitter() outbound: new eventemitter2.EventEmitter2()
} }
WIKI.extensions = require('./extensions') WIKI.extensions = extensions
WIKI.asar = require('./asar') WIKI.asar = asar
} catch (err) { } catch (err) {
WIKI.logger.error(err) WIKI.logger.error(err)
process.exit(1) process.exit(1)
@ -51,7 +59,7 @@ module.exports = {
async bootWeb() { async bootWeb() {
try { try {
await this.preBootWeb() await this.preBootWeb()
await require('../web')() await (await import('../web.mjs')).init()
this.postBootWeb() this.postBootWeb()
} catch (err) { } catch (err) {
WIKI.logger.error(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') import nodemailer from 'nodemailer'
const _ = require('lodash') import { get, has, kebabCase, set, template } from 'lodash-es'
const fs = require('fs-extra') import fs from 'node:fs/promises'
const path = require('path') import path from 'node:path'
module.exports = { export default {
transport: null, transport: null,
templates: {}, templates: {},
init() { init() {
if (_.get(WIKI.config, 'mail.host', '').length > 2) { if (get(WIKI.config, 'mail.host', '').length > 2) {
let conf = { let conf = {
host: WIKI.config.mail.host, host: WIKI.config.mail.host,
port: WIKI.config.mail.port, port: WIKI.config.mail.port,
@ -17,7 +17,7 @@ module.exports = {
rejectUnauthorized: !(WIKI.config.mail.verifySSL === false) rejectUnauthorized: !(WIKI.config.mail.verifySSL === false)
} }
} }
if (_.get(WIKI.config, 'mail.user', '').length > 1) { if (get(WIKI.config, 'mail.user', '').length > 1) {
conf = { conf = {
...conf, ...conf,
auth: { auth: {
@ -26,7 +26,7 @@ module.exports = {
} }
} }
} }
if (_.get(WIKI.config, 'mail.useDKIM', false)) { if (get(WIKI.config, 'mail.useDKIM', false)) {
conf = { conf = {
...conf, ...conf,
dkim: { dkim: {
@ -57,7 +57,7 @@ module.exports = {
to: opts.to, to: opts.to,
subject: `${opts.subject} - ${WIKI.config.title}`, subject: `${opts.subject} - ${WIKI.config.title}`,
text: opts.text, 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, logo: (WIKI.config.logoUrl.startsWith('http') ? '' : WIKI.config.host) + WIKI.config.logoUrl,
siteTitle: WIKI.config.title, siteTitle: WIKI.config.title,
copyright: WIKI.config.company.length > 0 ? WIKI.config.company : 'Powered by Wiki.js', copyright: WIKI.config.company.length > 0 ? WIKI.config.company : 'Powered by Wiki.js',
@ -66,11 +66,11 @@ module.exports = {
}) })
}, },
async loadTemplate(key) { async loadTemplate(key) {
if (_.has(this.templates, key)) { return } if (has(this.templates, key)) { return }
const keyKebab = _.kebabCase(key) const keyKebab = kebabCase(key)
try { try {
const rawTmpl = await fs.readFile(path.join(WIKI.SERVERPATH, `templates/${keyKebab}.html`), 'utf8') 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) { } catch (err) {
WIKI.logger.warn(err) WIKI.logger.warn(err)
throw new WIKI.Error.MailTemplateFailed() throw new WIKI.Error.MailTemplateFailed()

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

@ -1,13 +1,15 @@
const fs = require('fs-extra') import fs from 'node:fs/promises'
const http = require('http') import http from 'node:http'
const https = require('https') import https from 'node:https'
const { ApolloServer } = require('apollo-server-express') import { ApolloServer } from 'apollo-server-express'
const _ = require('lodash') import { isEmpty } from 'lodash-es'
const io = require('socket.io') import { Server as IoServer } from 'socket.io'
const { ApolloServerPluginLandingPageGraphQLPlayground, ApolloServerPluginLandingPageProductionDefault } = require('apollo-server-core') import { ApolloServerPluginLandingPageGraphQLPlayground, ApolloServerPluginLandingPageProductionDefault } from 'apollo-server-core'
const { graphqlUploadExpress } = require('graphql-upload') import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs'
module.exports = { import { initSchema } from '../graph/index.mjs'
export default {
graph: null, graph: null,
http: null, http: null,
https: null, https: null,
@ -69,15 +71,15 @@ module.exports = {
const tlsOpts = {} const tlsOpts = {}
try { try {
if (WIKI.config.ssl.format === 'pem') { if (WIKI.config.ssl.format === 'pem') {
tlsOpts.key = WIKI.config.ssl.inline ? WIKI.config.ssl.key : fs.readFileSync(WIKI.config.ssl.key) 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 : fs.readFileSync(WIKI.config.ssl.cert) tlsOpts.cert = WIKI.config.ssl.inline ? WIKI.config.ssl.cert : await fs.readFile(WIKI.config.ssl.cert, 'utf-8')
} else { } 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 tlsOpts.passphrase = WIKI.config.ssl.passphrase
} }
if (!_.isEmpty(WIKI.config.ssl.dhparam)) { if (!isEmpty(WIKI.config.ssl.dhparam)) {
tlsOpts.dhparam = WIKI.config.ssl.dhparam tlsOpts.dhparam = WIKI.config.ssl.dhparam
} }
} catch (err) { } catch (err) {
@ -127,7 +129,7 @@ module.exports = {
* Start GraphQL Server * Start GraphQL Server
*/ */
async startGraphQL () { async startGraphQL () {
const graphqlSchema = require('../graph') const graphqlSchema = await initSchema()
this.graph = new ApolloServer({ this.graph = new ApolloServer({
schema: graphqlSchema, schema: graphqlSchema,
csrfPrevention: true, csrfPrevention: true,
@ -155,13 +157,13 @@ module.exports = {
*/ */
async initWebSocket() { async initWebSocket() {
if (this.https) { if (this.https) {
this.ws = new io.Server(this.https, { this.ws = new IoServer(this.https, {
path: '/_ws/', path: '/_ws/',
serveClient: false serveClient: false
}) })
WIKI.logger.info(`WebSocket Server attached to HTTPS Server [ OK ]`) WIKI.logger.info(`WebSocket Server attached to HTTPS Server [ OK ]`)
} else { } else {
this.ws = new io.Server(this.http, { this.ws = new IoServer(this.http, {
path: '/_ws/', path: '/_ws/',
serveClient: false, serveClient: false,
cors: true // TODO: dev only, replace with app settings once stable 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') import { v4 as uuid } from 'uuid'
const bcrypt = require('bcryptjs-then') import bcrypt from 'bcryptjs-then'
const crypto = require('crypto') import crypto from 'node:crypto'
const { DateTime } = require('luxon') import { DateTime } from 'luxon'
const pem2jwk = require('pem-jwk').pem2jwk import { pem2jwk } from 'pem-jwk'
exports.up = async knex => { export async function up (knex) {
WIKI.logger.info('Running 3.0.0 database migration...') 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.') 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') import { find, get, reduce, set, sortBy, transform } from 'lodash-es'
const graphHelper = require('../../helpers/graph') import { generateError, generateSuccess } from '../../helpers/graph.mjs'
module.exports = { export default {
Query: { Query: {
async analyticsProviders(obj, args, context, info) { async analyticsProviders(obj, args, context, info) {
let providers = await WIKI.db.analytics.getProviders(args.isEnabled) let providers = await WIKI.db.analytics.getProviders(args.isEnabled)
providers = providers.map(stg => { providers = providers.map(stg => {
const providerInfo = _.find(WIKI.data.analytics, ['key', stg.key]) || {} const providerInfo = find(WIKI.data.analytics, ['key', stg.key]) || {}
return { return {
...providerInfo, ...providerInfo,
...stg, ...stg,
config: _.sortBy(_.transform(stg.config, (res, value, key) => { config: sortBy(transform(stg.config, (res, value, key) => {
const configData = _.get(providerInfo.props, key, {}) const configData = get(providerInfo.props, key, {})
res.push({ res.push({
key, key,
value: JSON.stringify({ value: JSON.stringify({
@ -31,18 +31,18 @@ module.exports = {
for (let str of args.providers) { for (let str of args.providers) {
await WIKI.db.analytics.query().patch({ await WIKI.db.analytics.query().patch({
isEnabled: str.isEnabled, isEnabled: str.isEnabled,
config: _.reduce(str.config, (result, value, key) => { config: reduce(str.config, (result, value, key) => {
_.set(result, `${value.key}`, _.get(JSON.parse(value.value), 'v', null)) set(result, `${value.key}`, get(JSON.parse(value.value), 'v', null))
return result return result
}, {}) }, {})
}).where('key', str.key) }).where('key', str.key)
await WIKI.cache.del('analytics') await WIKI.cache.del('analytics')
} }
return { return {
responseResult: graphHelper.generateSuccess('Providers updated successfully') responseResult: generateSuccess('Providers updated successfully')
} }
} catch (err) { } catch (err) {
return graphHelper.generateError(err) return generateError(err)
} }
} }
} }

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

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

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

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

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

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

@ -1,7 +1,7 @@
const _ = require('lodash') import _ from 'lodash-es'
const graphHelper = require('../../helpers/graph') import { generateError, generateSuccess } from '../../helpers/graph.mjs'
module.exports = { export default {
Query: { Query: {
async mailConfig(obj, args, context, info) { async mailConfig(obj, args, context, info) {
return { return {
@ -28,10 +28,10 @@ module.exports = {
}) })
return { return {
operation: graphHelper.generateSuccess('Test email sent successfully.') operation: generateSuccess('Test email sent successfully.')
} }
} catch (err) { } catch (err) {
return graphHelper.generateError(err) return generateError(err)
} }
}, },
async updateMailConfig(obj, args, context) { async updateMailConfig(obj, args, context) {
@ -56,10 +56,10 @@ module.exports = {
WIKI.mail.init() WIKI.mail.init()
return { return {
operation: graphHelper.generateSuccess('Mail configuration updated successfully.') operation: generateSuccess('Mail configuration updated successfully.')
} }
} catch (err) { } 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: { Query: {
async navigationTree (obj, args, context, info) { async navigationTree (obj, args, context, info) {
return WIKI.db.navigation.getTree({ cache: false, locale: 'all', bypassAuth: true }) return WIKI.db.navigation.getTree({ cache: false, locale: 'all', bypassAuth: true })
@ -20,10 +20,10 @@ module.exports = {
} }
return { return {
responseResult: graphHelper.generateSuccess('Navigation updated successfully') responseResult: generateSuccess('Navigation updated successfully')
} }
} catch (err) { } catch (err) {
return graphHelper.generateError(err) return generateError(err)
} }
}, },
async updateNavigationConfig (obj, args, context) { async updateNavigationConfig (obj, args, context) {
@ -34,10 +34,10 @@ module.exports = {
await WIKI.configSvc.saveToDb(['nav']) await WIKI.configSvc.saveToDb(['nav'])
return { return {
responseResult: graphHelper.generateSuccess('Navigation config updated successfully') responseResult: generateSuccess('Navigation config updated successfully')
} }
} catch (err) { } catch (err) {
return graphHelper.generateError(err) return generateError(err)
} }
} }
} }

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

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

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

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

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

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

@ -1,4 +1,4 @@
const { Kind, GraphQLScalarType } = require('graphql') import { Kind, GraphQLScalarType } from 'graphql'
function ensureObject (value) { function ensureObject (value) {
if (typeof value !== 'object' || value === null || Array.isArray(value)) { if (typeof value !== 'object' || value === null || Array.isArray(value)) {
@ -39,7 +39,7 @@ function parseObject (typeName, ast, variables) {
return value return value
} }
module.exports = new GraphQLScalarType({ export default new GraphQLScalarType({
name: 'JSON', name: 'JSON',
description: 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).', '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 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' const nilUUID = '00000000-0000-0000-0000-000000000000'
@ -7,7 +7,7 @@ function isUUID (value) {
return uuidRegex.test(value) || nilUUID === value return uuidRegex.test(value) || nilUUID === value
} }
module.exports = new GraphQLScalarType({ export default new GraphQLScalarType({
name: 'UUID', name: 'UUID',
description: 'The `UUID` scalar type represents UUID values as specified by [RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122).', description: 'The `UUID` scalar type represents UUID values as specified by [RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122).',
serialize: (value) => { 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) options = options || Object.create(null)
AbstractClientStore.apply(this, arguments) AbstractClientStore.apply(this, arguments)
@ -146,3 +146,5 @@ KnexStore.defaultsKnex = {
filename: './brute-knex.sqlite' 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' import { replace } from 'lodash-es'
const _ = require('lodash')
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)?)?$/ 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 * Parse configuration value for environment vars
* *
@ -16,7 +14,7 @@ module.exports = {
* @returns Parse configuration value * @returns Parse configuration value
*/ */
parseConfigValue (cfg) { parseConfigValue (cfg) {
return _.replace( return replace(
cfg, cfg,
/\$\(([A-Z0-9_]+)(?::(.+))?\)/g, /\$\(([A-Z0-9_]+)(?::(.+))?\)/g,
(fm, m, d) => { return process.env[m] || d } (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) { Custom (slug, message) {
return CustomError(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 // Licensed under AGPLv3
// =========================================== // ===========================================
const path = require('path') import path from 'node:path'
const { DateTime } = require('luxon') import { DateTime } from 'luxon'
const semver = require('semver') import semver from 'semver'
const nanoid = require('nanoid').customAlphabet('1234567890abcdef', 10) import { customAlphabet } from 'nanoid'
const fs = require('fs-extra') 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')) { if (!semver.satisfies(process.version, '>=18')) {
console.error('ERROR: Node.js 18.x or later required!') console.error('ERROR: Node.js 18.x or later required!')
process.exit(1) process.exit(1)
} }
if (fs.pathExistsSync('./package.json')) { if (fse.pathExistsSync('./package.json')) {
console.error('ERROR: Must run server from the parent directory!') console.error('ERROR: Must run server from the parent directory!')
process.exit(1) process.exit(1)
} }
let WIKI = { const WIKI = {
IS_DEBUG: process.env.NODE_ENV === 'development', IS_DEBUG: process.env.NODE_ENV === 'development',
ROOTPATH: process.cwd(), ROOTPATH: process.cwd(),
INSTANCE_ID: nanoid(10), INSTANCE_ID: nanoid(10),
SERVERPATH: path.join(process.cwd(), 'server'), SERVERPATH: path.join(process.cwd(), 'server'),
Error: require('./helpers/error'), configSvc,
configSvc: require('./core/config'), kernel,
kernel: require('./core/kernel'),
sites: {}, sites: {},
sitesMappings: {}, sitesMappings: {},
startedAt: DateTime.utc(), startedAt: DateTime.utc(),
@ -37,13 +41,13 @@ let WIKI = {
} }
global.WIKI = WIKI global.WIKI = WIKI
WIKI.configSvc.init() await WIKI.configSvc.init()
// ---------------------------------------- // ----------------------------------------
// Init Logger // Init Logger
// ---------------------------------------- // ----------------------------------------
WIKI.logger = require('./core/logger').init() WIKI.logger = logger.init()
// ---------------------------------------- // ----------------------------------------
// Start Kernel // 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 import { Model } from 'objection'
const fs = require('fs-extra') import fs from 'node:fs/promises'
const path = require('path') import path from 'node:path'
const _ = require('lodash') import { defaultTo, forOwn, isBoolean, replace, sortBy } from 'lodash-es'
const yaml = require('js-yaml') import yaml from 'js-yaml'
const commonHelper = require('../helpers/common') import { parseModuleProps } from '../helpers/common.mjs'
/** /**
* Analytics model * Analytics model
*/ */
module.exports = class Analytics extends Model { export class Analytics extends Model {
static get tableName() { return 'analytics' } static get tableName() { return 'analytics' }
static get jsonSchema () { static get jsonSchema () {
@ -29,8 +29,8 @@ module.exports = class Analytics extends Model {
} }
static async getProviders(isEnabled) { static async getProviders(isEnabled) {
const providers = await WIKI.db.analytics.query().where(_.isBoolean(isEnabled) ? { isEnabled } : {}) const providers = await WIKI.db.analytics.query().where(isBoolean(isEnabled) ? { isEnabled } : {})
return _.sortBy(providers, ['module']) return sortBy(providers, ['module'])
} }
static async refreshProvidersFromDisk() { 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 def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/analytics', dir, 'definition.yml'), 'utf8')
const defParsed = yaml.load(def) const defParsed = yaml.load(def)
defParsed.key = dir defParsed.key = dir
defParsed.props = commonHelper.parseModuleProps(defParsed.props) defParsed.props = parseModuleProps(defParsed.props)
WIKI.data.analytics.push(defParsed) WIKI.data.analytics.push(defParsed)
WIKI.logger.debug(`Loaded analytics module definition ${dir}: [ OK ]`) WIKI.logger.debug(`Loaded analytics module definition ${dir}: [ OK ]`)
} }
@ -72,14 +72,14 @@ module.exports = class Analytics extends Model {
for (let provider of providers) { for (let provider of providers) {
const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/analytics', provider.key, 'code.yml'), 'utf8') const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/analytics', provider.key, 'code.yml'), 'utf8')
let code = yaml.safeLoad(def) let code = yaml.safeLoad(def)
code.head = _.defaultTo(code.head, '') code.head = defaultTo(code.head, '')
code.bodyStart = _.defaultTo(code.bodyStart, '') code.bodyStart = defaultTo(code.bodyStart, '')
code.bodyEnd = _.defaultTo(code.bodyEnd, '') code.bodyEnd = defaultTo(code.bodyEnd, '')
_.forOwn(provider.config, (value, key) => { forOwn(provider.config, (value, key) => {
code.head = _.replace(code.head, new RegExp(`{{${key}}}`, 'g'), value) code.head = replace(code.head, new RegExp(`{{${key}}}`, 'g'), value)
code.bodyStart = _.replace(code.bodyStart, `{{${key}}}`, value) code.bodyStart = replace(code.bodyStart, `{{${key}}}`, value)
code.bodyEnd = _.replace(code.bodyEnd, `{{${key}}}`, value) code.bodyEnd = replace(code.bodyEnd, `{{${key}}}`, value)
}) })
analyticsCode.head += code.head analyticsCode.head += code.head

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

@ -1,14 +1,15 @@
const Model = require('objection').Model import { Model } from 'objection'
const moment = require('moment') import path from 'path'
const path = require('path') import fse from 'fs-extra'
const fs = require('fs-extra') import { startsWith } from 'lodash-es'
const _ = require('lodash') import { generateHash } from '../helpers/common.mjs'
const commonHelper = require('../helpers/common')
import { User } from './users.mjs'
/** /**
* Users model * Users model
*/ */
module.exports = class Asset extends Model { export class Asset extends Model {
static get tableName() { return 'assets' } static get tableName() { return 'assets' }
static get jsonSchema () { static get jsonSchema () {
@ -34,19 +35,11 @@ module.exports = class Asset extends Model {
return { return {
author: { author: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: require('./users'), modelClass: User,
join: { join: {
from: 'assets.authorId', from: 'assets.authorId',
to: 'users.id' 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() { 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) { static async upload(opts) {
@ -88,7 +81,7 @@ module.exports = class Asset extends Model {
let assetRow = { let assetRow = {
filename: opts.originalname, filename: opts.originalname,
ext: fileInfo.ext, ext: fileInfo.ext,
kind: _.startsWith(opts.mimetype, 'image/') ? 'image' : 'binary', kind: startsWith(opts.mimetype, 'image/') ? 'image' : 'binary',
mime: opts.mimetype, mime: opts.mimetype,
fileSize: opts.size, fileSize: opts.size,
folderId: opts.folderId folderId: opts.folderId
@ -112,7 +105,7 @@ module.exports = class Asset extends Model {
// Save asset data // Save asset data
try { try {
const fileBuffer = await fs.readFile(opts.path) const fileBuffer = await fse.readFile(opts.path)
if (asset) { if (asset) {
// Patch existing asset // Patch existing asset
@ -166,7 +159,7 @@ module.exports = class Asset extends Model {
.select('tree.*', 'assets.preview', 'assets.previewState') .select('tree.*', 'assets.preview', 'assets.previewState')
.innerJoin('assets', 'tree.id', 'assets.id') .innerJoin('assets', 'tree.id', 'assets.id')
.where(id ? { 'tree.id': id } : { .where(id ? { 'tree.id': id } : {
'tree.hash': commonHelper.generateHash(path), 'tree.hash': generateHash(path),
'tree.localeCode': locale, 'tree.localeCode': locale,
'tree.siteId': siteId 'tree.siteId': siteId
}) })
@ -202,7 +195,7 @@ module.exports = class Asset extends Model {
static async getAssetFromCache(assetPath, cachePath, res) { static async getAssetFromCache(assetPath, cachePath, res) {
try { try {
await fs.access(cachePath, fs.constants.R_OK) await fse.access(cachePath, fse.constants.R_OK)
} catch (err) { } catch (err) {
return false return false
} }
@ -217,7 +210,7 @@ module.exports = class Asset extends Model {
path: assetPath 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) const assetExists = await WIKI.db.assets.getAssetFromCache(assetPath, location.path, res)
if (assetExists) { if (assetExists) {
return true return true
@ -232,13 +225,13 @@ module.exports = class Asset extends Model {
const assetData = await WIKI.db.knex('assetData').where('id', asset.id).first() const assetData = await WIKI.db.knex('assetData').where('id', asset.id).first()
res.type(asset.ext) res.type(asset.ext)
res.send(assetData.data) res.send(assetData.data)
await fs.outputFile(cachePath, assetData.data) await fse.outputFile(cachePath, assetData.data)
} else { } else {
res.sendStatus(404) res.sendStatus(404)
} }
} }
static async flushTempUploads() { 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 import { Model } from 'objection'
const fs = require('fs-extra') import fs from 'node:fs/promises'
const path = require('path') import path from 'node:path'
const _ = require('lodash') import { get } from 'lodash-es'
const yaml = require('js-yaml') import yaml from 'js-yaml'
const commonHelper = require('../helpers/common') import { parseModuleProps } from '../helpers/common.mjs'
/** /**
* Authentication model * Authentication model
*/ */
module.exports = class Authentication extends Model { export class Authentication extends Model {
static get tableName() { return 'authentication' } static get tableName() { return 'authentication' }
static get jsonSchema () { static get jsonSchema () {
@ -37,8 +37,8 @@ module.exports = class Authentication extends Model {
const strategies = await WIKI.db.authentication.query().where(enabledOnly ? { isEnabled: true } : {}) const strategies = await WIKI.db.authentication.query().where(enabledOnly ? { isEnabled: true } : {})
return strategies.map(str => ({ return strategies.map(str => ({
...str, ...str,
domainWhitelist: _.get(str.domainWhitelist, 'v', []), domainWhitelist: get(str.domainWhitelist, 'v', []),
autoEnrollGroups: _.get(str.autoEnrollGroups, 'v', []) autoEnrollGroups: get(str.autoEnrollGroups, 'v', [])
})) }))
} }
@ -52,7 +52,7 @@ module.exports = class Authentication extends Model {
const defParsed = yaml.load(def) const defParsed = yaml.load(def)
if (!defParsed.isAvailable) { continue } if (!defParsed.isAvailable) { continue }
defParsed.key = dir defParsed.key = dir
defParsed.props = commonHelper.parseModuleProps(defParsed.props) defParsed.props = parseModuleProps(defParsed.props)
WIKI.data.authentication.push(defParsed) WIKI.data.authentication.push(defParsed)
WIKI.logger.debug(`Loaded authentication module definition ${dir}: [ OK ]`) WIKI.logger.debug(`Loaded authentication module definition ${dir}: [ OK ]`)
} }

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

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

@ -1,9 +1,9 @@
const Model = require('objection').Model import { Model } from 'objection'
/** /**
* Hook model * Hook model
*/ */
module.exports = class Hook extends Model { export class Hook extends Model {
static get tableName () { return 'hooks' } static get tableName () { return 'hooks' }
static get jsonAttributes () { 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 * Locales model
*/ */
module.exports = class Locale extends Model { export class Locale extends Model {
static get tableName() { return 'locales' } static get tableName() { return 'locales' }
static get idColumn() { return 'code' } static get idColumn() { return 'code' }

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

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

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

@ -1,17 +1,22 @@
const Model = require('objection').Model import { Model } from 'objection'
const _ = require('lodash') import { find, get, has, isEmpty, isString, pick } from 'lodash-es'
const JSBinType = require('js-binary').Type import { Type as JSBinType } from 'js-binary'
const pageHelper = require('../helpers/page') import { generateHash, getFileExtension, injectPageMetadata } from '../helpers/page.mjs'
const path = require('path') import path from 'node:path'
const fs = require('fs-extra') import fse from 'fs-extra'
const yaml = require('js-yaml') import yaml from 'js-yaml'
const striptags = require('striptags') import striptags from 'striptags'
const emojiRegex = require('emoji-regex') import emojiRegex from 'emoji-regex'
const he = require('he') import he from 'he'
const CleanCSS = require('clean-css') import CleanCSS from 'clean-css'
const TurndownService = require('turndown') import TurndownService from 'turndown'
const turndownPluginGfm = require('@joplin/turndown-plugin-gfm').gfm import { gfm as turndownPluginGfm } from '@joplin/turndown-plugin-gfm'
const cheerio = require('cheerio') 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-_/]*$/ const pageRegex = /^[a-zA0-90-9-_/]*$/
@ -27,7 +32,7 @@ const punctuationRegex = /[!,:;/\\_+\-=()&#@<>$~%^*[\]{}"'|]+|(\.\s)|(\s\.)/ig
/** /**
* Pages model * Pages model
*/ */
module.exports = class Page extends Model { export class Page extends Model {
static get tableName() { return 'pages' } static get tableName() { return 'pages' }
static get jsonSchema () { static get jsonSchema () {
@ -62,7 +67,7 @@ module.exports = class Page extends Model {
return { return {
tags: { tags: {
relation: Model.ManyToManyRelation, relation: Model.ManyToManyRelation,
modelClass: require('./tags'), modelClass: Tag,
join: { join: {
from: 'pages.id', from: 'pages.id',
through: { through: {
@ -74,7 +79,7 @@ module.exports = class Page extends Model {
}, },
links: { links: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: require('./pageLinks'), modelClass: PageLink,
join: { join: {
from: 'pages.id', from: 'pages.id',
to: 'pageLinks.pageId' to: 'pageLinks.pageId'
@ -82,7 +87,7 @@ module.exports = class Page extends Model {
}, },
author: { author: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: require('./users'), modelClass: User,
join: { join: {
from: 'pages.authorId', from: 'pages.authorId',
to: 'users.id' to: 'users.id'
@ -90,7 +95,7 @@ module.exports = class Page extends Model {
}, },
creator: { creator: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: require('./users'), modelClass: User,
join: { join: {
from: 'pages.creatorId', from: 'pages.creatorId',
to: 'users.id' to: 'users.id'
@ -98,7 +103,7 @@ module.exports = class Page extends Model {
}, },
locale: { locale: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: require('./locales'), modelClass: Locale,
join: { join: {
from: 'pages.localeCode', from: 'pages.localeCode',
to: 'locales.code' to: 'locales.code'
@ -162,7 +167,7 @@ module.exports = class Page extends Model {
* @returns {string} Page Contents with Injected Metadata * @returns {string} Page Contents with Injected Metadata
*/ */
injectMetadata () { injectMetadata () {
return pageHelper.injectPageMetadata(this) return injectPageMetadata(this)
} }
/** /**
@ -171,7 +176,7 @@ module.exports = class Page extends Model {
* @returns {string} File Extension * @returns {string} File Extension
*/ */
getFileExtension() { 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', contentType: WIKI.data.editors[opts.editor]?.contentType ?? 'text',
description: opts.description, description: opts.description,
editor: opts.editor, editor: opts.editor,
hash: pageHelper.generateHash({ path: opts.path, locale: opts.locale }), hash: generateHash({ path: opts.path, locale: opts.locale }),
icon: opts.icon, icon: opts.icon,
isBrowsable: opts.isBrowsable ?? true, isBrowsable: opts.isBrowsable ?? true,
localeCode: opts.locale, localeCode: opts.locale,
@ -561,7 +566,7 @@ module.exports = class Page extends Model {
})) { })) {
patch.scripts = { patch.scripts = {
...patch.scripts ?? ogPage.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') historyData.affectedFields.push('scripts.css')
} }
@ -698,7 +703,7 @@ module.exports = class Page extends Model {
// -> Check content type // -> Check content type
const sourceContentType = ogPage.contentType 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 const shouldConvert = sourceContentType !== targetContentType
let convertedContent = null let convertedContent = null
@ -846,7 +851,7 @@ module.exports = class Page extends Model {
*/ */
static async movePage(opts) { static async movePage(opts) {
let page let page
if (_.has(opts, 'id')) { if (has(opts, 'id')) {
page = await WIKI.db.pages.query().findById(opts.id) page = await WIKI.db.pages.query().findById(opts.id)
} else { } else {
page = await WIKI.db.pages.query().findOne({ page = await WIKI.db.pages.query().findOne({
@ -904,7 +909,7 @@ module.exports = class Page extends Model {
versionDate: page.updatedAt versionDate: page.updatedAt
}) })
const destinationHash = pageHelper.generateHash({ path: opts.destinationPath, locale: opts.destinationLocale }) const destinationHash = generateHash({ path: opts.destinationPath, locale: opts.destinationLocale })
// -> Move page // -> Move page
const destinationTitle = (page.title === page.path ? opts.destinationPath : page.title) 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 * @returns {Promise} Promise with no value
*/ */
static async deletePage(opts) { 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) { if (!page) {
throw new WIKI.Error.PageNotFound() throw new WIKI.Error.PageNotFound()
} }
@ -1209,7 +1214,7 @@ module.exports = class Page extends Model {
*/ */
static async savePageToCache(page) { static async savePageToCache(page) {
const cachePath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${page.hash}.bin`) 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, id: page.id,
authorId: page.authorId, authorId: page.authorId,
authorName: page.authorName, authorName: page.authorName,
@ -1219,17 +1224,17 @@ module.exports = class Page extends Model {
description: page.description, description: page.description,
editor: page.editor, editor: page.editor,
extra: { extra: {
css: _.get(page, 'extra.css', ''), css: get(page, 'extra.css', ''),
js: _.get(page, 'extra.js', '') js: get(page, 'extra.js', '')
}, },
publishState: page.publishState ?? '', publishState: page.publishState ?? '',
publishEndDate: page.publishEndDate ?? '', publishEndDate: page.publishEndDate ?? '',
publishStartDate: page.publishStartDate ?? '', publishStartDate: page.publishStartDate ?? '',
render: page.render, render: page.render,
siteId: page.siteId, siteId: page.siteId,
tags: page.tags.map(t => _.pick(t, ['tag'])), tags: page.tags.map(t => pick(t, ['tag'])),
title: page.title, 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() updatedAt: page.updatedAt.toISOString()
})) }))
} }
@ -1241,11 +1246,11 @@ module.exports = class Page extends Model {
* @returns {Promise} Promise of the Page Model Instance * @returns {Promise} Promise of the Page Model Instance
*/ */
static async getPageFromCache(opts) { 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`) const cachePath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${pageHash}.bin`)
try { try {
const pageBuffer = await fs.readFile(cachePath) const pageBuffer = await fse.readFile(cachePath)
let page = WIKI.db.pages.cacheSchema.decode(pageBuffer) let page = WIKI.db.pages.cacheSchema.decode(pageBuffer)
return { return {
...page, ...page,
@ -1268,14 +1273,14 @@ module.exports = class Page extends Model {
* @returns {Promise} Promise with no value * @returns {Promise} Promise with no value
*/ */
static async deletePageFromCache(hash) { 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 * Flush the contents of the Cache
*/ */
static async flushCache() { 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 import { Model } from 'objection'
const path = require('path') import path from 'node:path'
const fs = require('fs-extra') import fs from 'fs/promises'
const _ = require('lodash') import { clone, filter, find, get, has, reverse, some, transform, union } from 'lodash-es'
const yaml = require('js-yaml') import yaml from 'js-yaml'
const DepGraph = require('dependency-graph').DepGraph import { DepGraph } from 'dependency-graph'
const commonHelper = require('../helpers/common') import { parseModuleProps } from '../helpers/common.mjs'
/** /**
* Renderer model * Renderer model
*/ */
module.exports = class Renderer extends Model { export class Renderer extends Model {
static get tableName() { return 'renderers' } static get tableName() { return 'renderers' }
static get jsonSchema () { 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 def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/rendering', dir, 'definition.yml'), 'utf8')
const defParsed = yaml.load(def) const defParsed = yaml.load(def)
defParsed.key = dir defParsed.key = dir
defParsed.props = commonHelper.parseModuleProps(defParsed.props) defParsed.props = parseModuleProps(defParsed.props)
WIKI.data.renderers.push(defParsed) WIKI.data.renderers.push(defParsed)
WIKI.logger.debug(`Loaded renderers module definition ${dir}: [ OK ]`) WIKI.logger.debug(`Loaded renderers module definition ${dir}: [ OK ]`)
} }
@ -65,20 +65,20 @@ module.exports = class Renderer extends Model {
const newRenderers = [] const newRenderers = []
let updatedRenderers = 0 let updatedRenderers = 0
for (const renderer of WIKI.data.renderers) { for (const renderer of WIKI.data.renderers) {
if (!_.some(dbRenderers, ['module', renderer.key])) { if (!some(dbRenderers, ['module', renderer.key])) {
newRenderers.push({ newRenderers.push({
module: renderer.key, module: renderer.key,
isEnabled: renderer.enabledDefault ?? true, isEnabled: renderer.enabledDefault ?? true,
config: _.transform(renderer.props, (result, value, key) => { config: transform(renderer.props, (result, value, key) => {
result[key] = value.default result[key] = value.default
return result return result
}, {}) }, {})
}) })
} else { } 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({ await WIKI.db.renderers.query().patch({
config: _.transform(renderer.props, (result, value, key) => { config: transform(renderer.props, (result, value, key) => {
if (!_.has(result, key)) { if (!has(result, key)) {
result[key] = value.default result[key] = value.default
} }
return result return result
@ -98,7 +98,7 @@ module.exports = class Renderer extends Model {
// -> Delete removed Renderers // -> Delete removed Renderers
for (const renderer of dbRenderers) { 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() 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 ]`) 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) const renderersDb = await WIKI.db.renderers.query().where('isEnabled', true)
if (renderersDb && renderersDb.length > 0) { if (renderersDb && renderersDb.length > 0) {
const renderers = renderersDb.map(rdr => { const renderers = renderersDb.map(rdr => {
const renderer = _.find(WIKI.data.renderers, ['key', rdr.module]) const renderer = find(WIKI.data.renderers, ['key', rdr.module])
return { return {
...renderer, ...renderer,
config: rdr.config config: rdr.config
@ -121,8 +121,8 @@ module.exports = class Renderer extends Model {
}) })
// Build tree // Build tree
const rawCores = _.filter(renderers, renderer => !_.has(renderer, 'dependsOn')).map(core => { const rawCores = filter(renderers, renderer => !has(renderer, 'dependsOn')).map(core => {
core.children = _.filter(renderers, ['dependsOn', core.key]) core.children = filter(renderers, ['dependsOn', core.key])
return core return core
}) })
@ -140,11 +140,11 @@ module.exports = class Renderer extends Model {
}) })
// Filter unused cores // Filter unused cores
let activeCoreKeys = _.filter(rawCores, ['input', contentType]).map(core => core.key) let activeCoreKeys = filter(rawCores, ['input', contentType]).map(core => core.key)
_.clone(activeCoreKeys).map(coreKey => { clone(activeCoreKeys).map(coreKey => {
activeCoreKeys = _.union(activeCoreKeys, graph.dependenciesOf(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 // Rebuild dependency graph with active cores
const graphActive = new DepGraph({ circular: true }) const graphActive = new DepGraph({ circular: true })
@ -161,8 +161,8 @@ module.exports = class Renderer extends Model {
// Reorder cores in reverse dependency order // Reorder cores in reverse dependency order
let orderedCores = [] let orderedCores = []
_.reverse(graphActive.overallOrder()).map(coreKey => { reverse(graphActive.overallOrder()).map(coreKey => {
orderedCores.push(_.find(rawCores, ['key', coreKey])) orderedCores.push(find(rawCores, ['key', coreKey]))
}) })
return orderedCores return orderedCores

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

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

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

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

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

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

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

File diff suppressed because it is too large Load Diff

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

@ -1,4 +1,4 @@
module.exports = async (payload) => { export async function task (payload) {
WIKI.logger.info('Checking for latest version...') WIKI.logger.info('Checking for latest version...')
try { 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...') WIKI.logger.info('Cleaning scheduler job history...')
try { try {

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

@ -1,20 +1,20 @@
const path = require('node:path') import path from 'node:path'
const fs = require('fs-extra') import fse from 'fs-extra'
const { DateTime } = require('luxon') import { DateTime } from 'luxon'
module.exports = async ({ payload }) => { export async function task ({ payload }) {
WIKI.logger.info('Purging orphaned upload files...') WIKI.logger.info('Purging orphaned upload files...')
try { try {
const uplTempPath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'uploads') const uplTempPath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'uploads')
await fs.ensureDir(uplTempPath) await fse.ensureDir(uplTempPath)
const ls = await fs.readdir(uplTempPath) const ls = await fse.readdir(uplTempPath)
const fifteenAgo = DateTime.now().minus({ minutes: 15 }) const fifteenAgo = DateTime.now().minus({ minutes: 15 })
for (const f of ls) { 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) { 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') import { get, has, isEmpty, reduce, times, toSafeInteger } from 'lodash-es'
const cheerio = require('cheerio') import cheerio from 'cheerio'
module.exports = async ({ payload }) => { export async function task ({ payload }) {
WIKI.logger.info(`Rendering page ${payload.id}...`) WIKI.logger.info(`Rendering page ${payload.id}...`)
try { try {
@ -20,7 +20,7 @@ module.exports = async ({ payload }) => {
let output = page.render 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 ]`) 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: [] } let toc = { root: [] }
$('h1,h2,h3,h4,h5,h6').each((idx, el) => { $('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 let leafPathError = false
const leafPath = _.reduce(_.times(depth), (curPath, curIdx) => { const leafPath = reduce(times(depth), (curPath, curIdx) => {
if (_.has(toc, curPath)) { if (has(toc, curPath)) {
const lastLeafIdx = _.get(toc, curPath).length - 1 const lastLeafIdx = _.get(toc, curPath).length - 1
if (lastLeafIdx >= 0) { if (lastLeafIdx >= 0) {
curPath = `${curPath}[${lastLeafIdx}].children` curPath = `${curPath}[${lastLeafIdx}].children`
@ -61,8 +61,8 @@ module.exports = async ({ payload }) => {
const leafSlug = $('.toc-anchor', el).first().attr('href') const leafSlug = $('.toc-anchor', el).first().attr('href')
$('.toc-anchor', el).remove() $('.toc-anchor', el).remove()
_.get(toc, leafPath).push({ get(toc, leafPath).push({
label: _.trim($(el).text()), label: $(el).text().trim(),
key: leafSlug.substring(1), key: leafSlug.substring(1),
children: [] children: []
}) })

@ -1,30 +1,31 @@
const autoload = require('auto-load') import bodyParser from 'body-parser'
const bodyParser = require('body-parser') import compression from 'compression'
const compression = require('compression') import cookieParser from 'cookie-parser'
const cookieParser = require('cookie-parser') import cors from 'cors'
const cors = require('cors') import express from 'express'
const express = require('express') import session from 'express-session'
const session = require('express-session') import KnexSessionStore from 'connect-session-knex'
const KnexSessionStore = require('connect-session-knex')(session) import favicon from 'serve-favicon'
const favicon = require('serve-favicon') import path from 'node:path'
const path = require('path') import { set } from 'lodash-es'
const _ = require('lodash')
import auth from './core/auth.mjs'
module.exports = async () => { 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 // Load core modules
// ---------------------------------------- // ----------------------------------------
WIKI.auth = require('./core/auth').init() WIKI.auth = auth.init()
WIKI.mail = require('./core/mail').init() WIKI.mail = mail.init()
WIKI.system = require('./core/system').init() WIKI.system = system.init()
// ----------------------------------------
// Load middlewares
// ----------------------------------------
const mw = autoload(path.join(WIKI.SERVERPATH, '/middlewares'))
const ctrl = autoload(path.join(WIKI.SERVERPATH, '/controllers'))
// ---------------------------------------- // ----------------------------------------
// Define Express App // 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' 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() await WIKI.servers.initHTTP()
if (useHTTPS) { if (useHTTPS) {
await WIKI.servers.initHTTPS() await WIKI.servers.initHTTPS()
} }
await WIKI.servers.initWebSocket() await WIKI.servers.initWebSocket()
// ---------------------------------------- // ----------------------------------------
// Attach WebSocket Server // Attach WebSocket Server
// ---------------------------------------- // ----------------------------------------
ctrl.ws() ctrlWs()
// ---------------------------------------- // ----------------------------------------
// Security // 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.use(cors({ origin: false }))
app.options('*', cors({ origin: false })) app.options('*', cors({ origin: false }))
if (WIKI.config.security.securityTrustProxy) { if (WIKI.config.security.securityTrustProxy) {
@ -86,7 +120,7 @@ module.exports = async () => {
// SSL Handlers // SSL Handlers
// ---------------------------------------- // ----------------------------------------
app.use('/', ctrl.ssl) app.use('/', ctrlSsl())
// ---------------------------------------- // ----------------------------------------
// Passport Authentication // Passport Authentication
@ -97,7 +131,7 @@ module.exports = async () => {
secret: WIKI.config.auth.secret, secret: WIKI.config.auth.secret,
resave: false, resave: false,
saveUninitialized: false, saveUninitialized: false,
store: new KnexSessionStore({ store: new KnexSessionStore(session)({
knex: WIKI.db.knex knex: WIKI.db.knex
}) })
})) }))
@ -115,7 +149,15 @@ module.exports = async () => {
// SEO // 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 // View Engine Setup
@ -177,9 +219,8 @@ module.exports = async () => {
next() next()
}) })
app.use('/', ctrl.auth) app.use('/', ctrlAuth())
app.use('/', ctrl.upload) app.use('/', ctrlCommon())
app.use('/', ctrl.common)
// ---------------------------------------- // ----------------------------------------
// Error handling // Error handling
@ -202,7 +243,7 @@ module.exports = async () => {
}) })
} else { } else {
res.status(err.status || 500) res.status(err.status || 500)
_.set(res.locals, 'pageMeta.title', 'Error') set(res.locals, 'pageMeta.title', 'Error')
res.render('error', { res.render('error', {
message: err.message, message: err.message,
error: WIKI.IS_DEBUG ? err : {} error: WIKI.IS_DEBUG ? err : {}

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

350
ux/package-lock.json generated

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

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

Loading…
Cancel
Save