123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- import { generateError, generateSuccess } from '../../helpers/graph.mjs'
- import _ from 'lodash-es'
- import CleanCSS from 'clean-css'
- import path from 'node:path'
- import fs from 'fs-extra'
- import { v4 as uuid } from 'uuid'
- export default {
- Query: {
- async sites () {
- const sites = await WIKI.db.sites.query().orderBy('hostname')
- return sites.map(s => ({
- ...s.config,
- id: s.id,
- hostname: s.hostname,
- isEnabled: s.isEnabled,
- pageExtensions: s.config.pageExtensions.join(', ')
- }))
- },
- async siteById (obj, args) {
- const site = await WIKI.db.sites.query().findById(args.id)
- return site ? {
- ...site.config,
- id: site.id,
- hostname: site.hostname,
- isEnabled: site.isEnabled,
- pageExtensions: site.config.pageExtensions.join(', ')
- } : null
- },
- async siteByHostname (obj, args) {
- let site = await WIKI.db.sites.query().where({
- hostname: args.hostname
- }).first()
- if (!site && !args.exact) {
- site = await WIKI.db.sites.query().where({
- hostname: '*'
- }).first()
- }
- return site ? {
- ...site.config,
- id: site.id,
- hostname: site.hostname,
- isEnabled: site.isEnabled,
- pageExtensions: site.config.pageExtensions.join(', ')
- } : null
- }
- },
- Mutation: {
- /**
- * CREATE SITE
- */
- async createSite (obj, args, context) {
- try {
- if (!WIKI.auth.checkAccess(context.req.user, ['write:sites', 'manage:sites'])) {
- throw new Error('ERR_FORBIDDEN')
- }
- // -> Validate inputs
- if (!args.hostname || args.hostname.length < 1 || !/^(\\*)|([a-z0-9\-.:]+)$/.test(args.hostname)) {
- throw new WIKI.Error.Custom('SiteCreateInvalidHostname', 'Invalid Site Hostname')
- }
- if (!args.title || args.title.length < 1 || !/^[^<>"]+$/.test(args.title)) {
- throw new WIKI.Error.Custom('SiteCreateInvalidTitle', 'Invalid Site Title')
- }
- // -> Check for duplicate catch-all
- if (args.hostname === '*') {
- const site = await WIKI.db.sites.query().where({
- hostname: args.hostname
- }).first()
- if (site) {
- throw new WIKI.Error.Custom('SiteCreateDuplicateCatchAll', 'A site with a catch-all hostname already exists! Cannot have 2 catch-all hostnames.')
- }
- }
- // -> Create site
- const newSite = await WIKI.db.sites.createSite(args.hostname, {
- title: args.title
- })
- return {
- operation: generateSuccess('Site created successfully'),
- site: newSite
- }
- } catch (err) {
- WIKI.logger.warn(err)
- return generateError(err)
- }
- },
- /**
- * UPDATE SITE
- */
- async updateSite (obj, args, context) {
- try {
- if (!WIKI.auth.checkAccess(context.req.user, ['manage:sites'])) {
- throw new Error('ERR_FORBIDDEN')
- }
- // -> Load site
- const site = await WIKI.db.sites.query().findById(args.id)
- if (!site) {
- throw new WIKI.Error.Custom('SiteInvalidId', 'Invalid Site ID')
- }
- // -> Check for bad input
- if (_.has(args.patch, 'hostname') && _.trim(args.patch.hostname).length < 1) {
- throw new WIKI.Error.Custom('SiteInvalidHostname', 'Hostname is invalid.')
- }
- // -> Check for duplicate catch-all
- if (args.patch.hostname === '*' && site.hostname !== '*') {
- const dupSite = await WIKI.db.sites.query().where({ hostname: '*' }).first()
- if (dupSite) {
- throw new WIKI.Error.Custom('SiteUpdateDuplicateCatchAll', `Site ${dupSite.config.title} with a catch-all hostname already exists! Cannot have 2 catch-all hostnames.`)
- }
- }
- // -> Format Code
- if (args.patch?.theme?.injectCSS) {
- args.patch.theme.injectCSS = new CleanCSS({ inline: false }).minify(args.patch.theme.injectCSS).styles
- }
- // -> Format Page Extensions
- if (args.patch?.pageExtensions) {
- args.patch.pageExtensions = args.patch.pageExtensions.split(',').map(ext => ext.trim().toLowerCase()).filter(ext => ext.length > 0)
- }
- // -> Update site
- await WIKI.db.sites.updateSite(args.id, {
- hostname: args.patch.hostname ?? site.hostname,
- isEnabled: args.patch.isEnabled ?? site.isEnabled,
- config: _.defaultsDeep(_.omit(args.patch, ['hostname', 'isEnabled']), site.config)
- })
- return {
- operation: generateSuccess('Site updated successfully')
- }
- } catch (err) {
- WIKI.logger.warn(err)
- return generateError(err)
- }
- },
- /**
- * DELETE SITE
- */
- async deleteSite (obj, args, context) {
- try {
- if (!WIKI.auth.checkAccess(context.req.user, ['manage:sites'])) {
- throw new Error('ERR_FORBIDDEN')
- }
- // -> Ensure site isn't last one
- const sitesCount = await WIKI.db.sites.query().count('id').first()
- if (sitesCount?.count && _.toNumber(sitesCount?.count) <= 1) {
- throw new WIKI.Error.Custom('SiteDeleteLastSite', 'Cannot delete the last site. At least 1 site must exists at all times.')
- }
- // -> Delete site
- await WIKI.db.sites.deleteSite(args.id)
- return {
- operation: generateSuccess('Site deleted successfully')
- }
- } catch (err) {
- WIKI.logger.warn(err)
- return generateError(err)
- }
- },
- /**
- * UPLOAD LOGO
- */
- async uploadSiteLogo (obj, args, context) {
- try {
- if (!WIKI.auth.checkAccess(context.req.user, ['manage:sites'])) {
- throw new Error('ERR_FORBIDDEN')
- }
- const { filename, mimetype, createReadStream } = await args.image
- WIKI.logger.info(`Processing site logo ${filename} of type ${mimetype}...`)
- if (!WIKI.extensions.ext.sharp.isInstalled) {
- throw new Error('This feature requires the Sharp extension but it is not installed.')
- }
- if (!['.svg', '.png', '.jpg', 'webp', '.gif'].some(s => filename.endsWith(s))) {
- throw new Error('Invalid File Extension. Must be svg, png, jpg, webp or gif.')
- }
- const destFormat = mimetype.startsWith('image/svg') ? 'svg' : 'png'
- const destFolder = path.resolve(
- process.cwd(),
- WIKI.config.dataPath,
- `assets`
- )
- const destPath = path.join(destFolder, `logo-${args.id}.${destFormat}`)
- await fs.ensureDir(destFolder)
- // -> Resize
- await WIKI.extensions.ext.sharp.resize({
- format: destFormat,
- inputStream: createReadStream(),
- outputPath: destPath,
- height: 72
- })
- // -> Save logo meta to DB
- const site = await WIKI.db.sites.query().findById(args.id)
- if (!site.config.assets.logo) {
- site.config.assets.logo = uuid()
- }
- site.config.assets.logoExt = destFormat
- await WIKI.db.sites.query().findById(args.id).patch({ config: site.config })
- await WIKI.db.sites.reloadCache()
- // -> Save image data to DB
- const imgBuffer = await fs.readFile(destPath)
- await WIKI.db.knex('assets').insert({
- id: site.config.assets.logo,
- fileName: `_logo.${destFormat}`,
- fileExt: `.${destFormat}`,
- isSystem: true,
- kind: 'image',
- mimeType: (destFormat === 'svg') ? 'image/svg' : 'image/png',
- fileSize: Math.ceil(imgBuffer.byteLength / 1024),
- data: imgBuffer,
- authorId: context.req.user.id,
- siteId: site.id
- }).onConflict('id').merge()
- WIKI.logger.info('New site logo processed successfully.')
- return {
- operation: generateSuccess('Site logo uploaded successfully')
- }
- } catch (err) {
- WIKI.logger.warn(err)
- return generateError(err)
- }
- },
- /**
- * UPLOAD FAVICON
- */
- async uploadSiteFavicon (obj, args, context) {
- try {
- if (!WIKI.auth.checkAccess(context.req.user, ['manage:sites'])) {
- throw new Error('ERR_FORBIDDEN')
- }
- const { filename, mimetype, createReadStream } = await args.image
- WIKI.logger.info(`Processing site favicon ${filename} of type ${mimetype}...`)
- if (!WIKI.extensions.ext.sharp.isInstalled) {
- throw new Error('This feature requires the Sharp extension but it is not installed.')
- }
- if (!['.svg', '.png', '.jpg', '.webp', '.gif'].some(s => filename.endsWith(s))) {
- throw new Error('Invalid File Extension. Must be svg, png, jpg, webp or gif.')
- }
- const destFormat = mimetype.startsWith('image/svg') ? 'svg' : 'png'
- const destFolder = path.resolve(
- process.cwd(),
- WIKI.config.dataPath,
- `assets`
- )
- const destPath = path.join(destFolder, `favicon-${args.id}.${destFormat}`)
- await fs.ensureDir(destFolder)
- // -> Resize
- await WIKI.extensions.ext.sharp.resize({
- format: destFormat,
- inputStream: createReadStream(),
- outputPath: destPath,
- width: 64,
- height: 64
- })
- // -> Save favicon meta to DB
- const site = await WIKI.db.sites.query().findById(args.id)
- if (!site.config.assets.favicon) {
- site.config.assets.favicon = uuid()
- }
- site.config.assets.faviconExt = destFormat
- await WIKI.db.sites.query().findById(args.id).patch({ config: site.config })
- await WIKI.db.sites.reloadCache()
- // -> Save image data to DB
- const imgBuffer = await fs.readFile(destPath)
- await WIKI.db.knex('assets').insert({
- id: site.config.assets.favicon,
- fileName: `_favicon.${destFormat}`,
- fileExt: `.${destFormat}`,
- isSystem: true,
- kind: 'image',
- mimeType: (destFormat === 'svg') ? 'image/svg' : 'image/png',
- fileSize: Math.ceil(imgBuffer.byteLength / 1024),
- data: imgBuffer,
- authorId: context.req.user.id,
- siteId: site.id
- }).onConflict('id').merge()
- WIKI.logger.info('New site favicon processed successfully.')
- return {
- operation: generateSuccess('Site favicon uploaded successfully')
- }
- } catch (err) {
- WIKI.logger.warn(err)
- return generateError(err)
- }
- },
- /**
- * UPLOAD LOGIN BG
- */
- async uploadSiteLoginBg (obj, args, context) {
- try {
- if (!WIKI.auth.checkAccess(context.req.user, ['manage:sites'])) {
- throw new Error('ERR_FORBIDDEN')
- }
- const { filename, mimetype, createReadStream } = await args.image
- WIKI.logger.info(`Processing site login bg ${filename} of type ${mimetype}...`)
- if (!WIKI.extensions.ext.sharp.isInstalled) {
- throw new Error('This feature requires the Sharp extension but it is not installed.')
- }
- if (!['.png', '.jpg', '.webp'].some(s => filename.endsWith(s))) {
- throw new Error('Invalid File Extension. Must be png, jpg or webp.')
- }
- const destFolder = path.resolve(
- process.cwd(),
- WIKI.config.dataPath,
- `assets`
- )
- const destPath = path.join(destFolder, `loginbg-${args.id}.jpg`)
- await fs.ensureDir(destFolder)
- // -> Resize
- await WIKI.extensions.ext.sharp.resize({
- format: 'jpg',
- inputStream: createReadStream(),
- outputPath: destPath,
- width: 1920
- })
- // -> Save login bg meta to DB
- const site = await WIKI.db.sites.query().findById(args.id)
- if (!site.config.assets.loginBg) {
- site.config.assets.loginBg = uuid()
- await WIKI.db.sites.query().findById(args.id).patch({ config: site.config })
- await WIKI.db.sites.reloadCache()
- }
- // -> Save image data to DB
- const imgBuffer = await fs.readFile(destPath)
- await WIKI.db.knex('assets').insert({
- id: site.config.assets.loginBg,
- filename: '_loginbg.jpg',
- hash: '_loginbg',
- ext: '.jpg',
- isSystem: true,
- kind: 'image',
- mime: 'image/jpg',
- fileSize: Math.ceil(imgBuffer.byteLength / 1024),
- data: imgBuffer,
- authorId: context.req.user.id,
- siteId: site.id
- }).onConflict('id').merge()
- WIKI.logger.info('New site login bg processed successfully.')
- return {
- operation: generateSuccess('Site login bg uploaded successfully')
- }
- } catch (err) {
- WIKI.logger.warn(err)
- return generateError(err)
- }
- }
- },
- SiteLocales: {
- async active (obj, args, context) {
- return obj.active.map(l => WIKI.cache.get(`locale:${l}`))
- }
- }
- }
|