123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709 |
- import _ from 'lodash-es'
- import { generateError, generateSuccess } from '../../helpers/graph.mjs'
- import { parsePath } from '../../helpers/page.mjs'
- import tsquery from 'pg-tsquery'
- const tsq = tsquery()
- const tagsInQueryRgx = /#[a-z0-9-\u3400-\u4DBF\u4E00-\u9FFF]+(?=(?:[^"]*(?:")[^"]*(?:"))*[^"]*$)/g
- export default {
- Query: {
- /**
- * PAGE HISTORY
- */
- async pageHistoryById (obj, args, context, info) {
- const page = await WIKI.db.pages.query().select('path', 'locale').findById(args.id)
- if (WIKI.auth.checkAccess(context.req.user, ['read:history'], {
- path: page.path,
- locale: page.locale
- })) {
- return WIKI.db.pageHistory.getHistory({
- pageId: args.id,
- offsetPage: args.offsetPage || 0,
- offsetSize: args.offsetSize || 100
- })
- } else {
- throw new WIKI.Error.PageHistoryForbidden()
- }
- },
- /**
- * PAGE VERSION
- */
- async pageVersionById (obj, args, context, info) {
- const page = await WIKI.db.pages.query().select('path', 'locale').findById(args.pageId)
- if (WIKI.auth.checkAccess(context.req.user, ['read:history'], {
- path: page.path,
- locale: page.locale
- })) {
- return WIKI.db.pageHistory.getVersion({
- pageId: args.pageId,
- versionId: args.versionId
- })
- } else {
- throw new WIKI.Error.PageHistoryForbidden()
- }
- },
- /**
- * SEARCH PAGES
- */
- async searchPages (obj, args, context) {
- const q = args.query.trim()
- const hasQuery = q.length > 0
- // -> Validate parameters
- if (!args.siteId) {
- throw new Error('Missing Site ID')
- }
- if (args.offset && args.offset < 0) {
- throw new Error('Invalid offset value.')
- }
- if (args.limit && (args.limit < 1 || args.limit > 100)) {
- throw new Error('Limit must be between 1 and 100.')
- }
- try {
- const dictName = 'english' // TODO: Use provided locale or fallback on site locale
- // -> Select Columns
- const searchCols = [
- 'id',
- 'path',
- 'locale',
- 'title',
- 'description',
- 'icon',
- 'tags',
- 'updatedAt',
- WIKI.db.knex.raw('count(*) OVER() AS total')
- ]
- // -> Set relevancy
- if (hasQuery) {
- searchCols.push(WIKI.db.knex.raw('ts_rank_cd(ts, query) AS relevancy'))
- } else {
- args.orderBy = args.orderBy === 'relevancy' ? 'title' : args.orderBy
- }
- // -> Add Highlighting if enabled
- if (WIKI.config.search.termHighlighting && hasQuery) {
- searchCols.push(WIKI.db.knex.raw(`ts_headline(?, "searchContent", query, 'MaxWords=5, MinWords=3, MaxFragments=5') AS highlight`, [dictName]))
- }
- const results = await WIKI.db.knex
- .select(searchCols)
- .fromRaw(hasQuery ? 'pages, to_tsquery(?, ?) query' : 'pages', hasQuery ? [dictName, tsq(q)] : [])
- .where('siteId', args.siteId)
- .where('isSearchableComputed', true)
- .where(builder => {
- if (args.path) {
- builder.where('path', 'ILIKE', `${args.path}%`)
- }
- if (args.locale?.length > 0) {
- builder.whereIn('locale', args.locale)
- }
- if (args.editor) {
- builder.where('editor', args.editor)
- }
- if (args.publishState) {
- builder.where('publishState', args.publishState)
- }
- if (args.tags) {
- builder.where('tags', '@>', args.tags)
- }
- if (hasQuery) {
- builder.whereRaw('query @@ ts')
- }
- })
- .orderBy(args.orderBy || 'relevancy', args.orderByDirection || 'desc')
- .offset(args.offset || 0)
- .limit(args.limit || 25)
- // -> Remove highlights without matches
- if (WIKI.config.search.termHighlighting && hasQuery) {
- for (const r of results) {
- if (r.highlight?.indexOf('<b>') < 0) {
- r.highlight = null
- }
- }
- }
- return {
- results,
- totalHits: results?.length > 0 ? results[0].total : 0
- }
- } catch (err) {
- WIKI.logger.warn(`Search Query Error: ${err.message}`)
- throw err
- }
- },
- /**
- * LIST PAGES
- */
- async pages (obj, args, context, info) {
- let results = await WIKI.db.pages.query().column([
- 'pages.id',
- 'path',
- 'locale',
- 'title',
- 'description',
- 'isPublished',
- 'isPrivate',
- 'privateNS',
- 'contentType',
- 'createdAt',
- 'updatedAt'
- ])
- .withGraphJoined('tags')
- .modifyGraph('tags', builder => {
- builder.select('tag')
- })
- .modify(queryBuilder => {
- if (args.limit) {
- queryBuilder.limit(args.limit)
- }
- if (args.locale) {
- queryBuilder.where('locale', args.locale)
- }
- if (args.creatorId && args.authorId && args.creatorId > 0 && args.authorId > 0) {
- queryBuilder.where(function () {
- this.where('creatorId', args.creatorId).orWhere('authorId', args.authorId)
- })
- } else {
- if (args.creatorId && args.creatorId > 0) {
- queryBuilder.where('creatorId', args.creatorId)
- }
- if (args.authorId && args.authorId > 0) {
- queryBuilder.where('authorId', args.authorId)
- }
- }
- if (args.tags && args.tags.length > 0) {
- queryBuilder.whereIn('tags.tag', args.tags.map(t => _.trim(t).toLowerCase()))
- }
- const orderDir = args.orderByDirection === 'DESC' ? 'desc' : 'asc'
- switch (args.orderBy) {
- case 'CREATED':
- queryBuilder.orderBy('createdAt', orderDir)
- break
- case 'PATH':
- queryBuilder.orderBy('path', orderDir)
- break
- case 'TITLE':
- queryBuilder.orderBy('title', orderDir)
- break
- case 'UPDATED':
- queryBuilder.orderBy('updatedAt', orderDir)
- break
- default:
- queryBuilder.orderBy('pages.id', orderDir)
- break
- }
- })
- results = _.filter(results, r => {
- return WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
- path: r.path,
- locale: r.locale
- })
- }).map(r => ({
- ...r,
- tags: _.map(r.tags, 'tag')
- }))
- if (args.tags && args.tags.length > 0) {
- results = _.filter(results, r => _.every(args.tags, t => _.includes(r.tags, t)))
- }
- return results
- },
- /**
- * FETCH SINGLE PAGE BY ID
- */
- async pageById (obj, args, context, info) {
- const page = await WIKI.db.pages.getPageFromDb(args.id)
- if (page) {
- if (WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
- path: page.path,
- locale: page.locale
- })) {
- return {
- ...page,
- ...page.config,
- scriptCss: page.scripts?.css,
- scriptJsLoad: page.scripts?.jsLoad,
- scriptJsUnload: page.scripts?.jsUnload
- }
- } else {
- throw new Error('ERR_FORBIDDEN')
- }
- } else {
- throw new Error('ERR_PAGE_NOT_FOUND')
- }
- },
- /**
- * FETCH SINGLE PAGE BY PATH
- */
- async pageByPath (obj, args, context, info) {
- // console.info(info)
- const pageArgs = parsePath(args.path)
- const page = await WIKI.db.pages.getPageFromDb({
- ...pageArgs,
- siteId: args.siteId
- })
- if (page) {
- if (WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
- path: page.path,
- locale: page.locale
- })) {
- return {
- ...page,
- ...page.config,
- scriptCss: page.scripts?.css,
- scriptJsLoad: page.scripts?.jsLoad,
- scriptJsUnload: page.scripts?.jsUnload
- }
- } else {
- throw new Error('ERR_FORBIDDEN')
- }
- } else {
- throw new Error('ERR_PAGE_NOT_FOUND')
- }
- },
- /**
- * FETCH PATH FROM ALIAS
- */
- async pathFromAlias (obj, args, context, info) {
- const alias = args.alias?.trim()
- if (!alias) {
- throw new Error('ERR_PAGE_ALIAS_MISSING')
- }
- if (!WIKI.sites[args.siteId]) {
- throw new Error('ERR_INVALID_SITE')
- }
- const page = await WIKI.db.pages.query().findOne({
- alias: args.alias,
- siteId: args.siteId
- }).select('id', 'path', 'locale')
- if (!page) {
- throw new Error('ERR_PAGE_ALIAS_NOT_FOUND')
- }
- return {
- id: page.id,
- path: WIKI.sites[args.siteId].config.localeNamespacing ? `${page.locale}/${page.path}` : page.path
- }
- },
- /**
- * FETCH TAGS
- */
- async tags (obj, args, context, info) {
- if (!args.siteId) { throw new Error('Missing Site ID') }
- const tags = await WIKI.db.knex('tags').where('siteId', args.siteId).orderBy('tag')
- // TODO: check permissions
- return tags
- },
- /**
- * FETCH PAGE TREE
- */
- async pageTree (obj, args, context, info) {
- let curPage = null
- if (!args.locale) { args.locale = WIKI.config.lang.code }
- if (args.path && !args.parent) {
- curPage = await WIKI.db.knex('pageTree').first('parent', 'ancestors').where({
- path: args.path,
- locale: args.locale
- })
- if (curPage) {
- args.parent = curPage.parent || 0
- } else {
- return []
- }
- }
- const results = await WIKI.db.knex('pageTree').where(builder => {
- builder.where('locale', args.locale)
- switch (args.mode) {
- case 'FOLDERS':
- builder.andWhere('isFolder', true)
- break
- case 'PAGES':
- builder.andWhereNotNull('pageId')
- break
- }
- if (!args.parent || args.parent < 1) {
- builder.whereNull('parent')
- } else {
- builder.where('parent', args.parent)
- if (args.includeAncestors && curPage && curPage.ancestors.length > 0) {
- builder.orWhereIn('id', _.isString(curPage.ancestors) ? JSON.parse(curPage.ancestors) : curPage.ancestors)
- }
- }
- }).orderBy([{ column: 'isFolder', order: 'desc' }, 'title'])
- return results.filter(r => {
- return WIKI.auth.checkAccess(context.req.user, ['read:pages'], {
- path: r.path,
- locale: r.locale
- })
- }).map(r => ({
- ...r,
- parent: r.parent || 0,
- locale: r.locale
- }))
- },
- /**
- * FETCH PAGE LINKS
- */
- async pageLinks (obj, args, context, info) {
- let results
- if (WIKI.config.db.type === 'mysql' || WIKI.config.db.type === 'mariadb' || WIKI.config.db.type === 'sqlite') {
- results = await WIKI.db.knex('pages')
- .column({ id: 'pages.id' }, { path: 'pages.path' }, 'title', { link: 'pageLinks.path' }, { locale: 'pageLinks.locale' })
- .leftJoin('pageLinks', 'pages.id', 'pageLinks.pageId')
- .where({
- 'pages.locale': args.locale
- })
- .unionAll(
- WIKI.db.knex('pageLinks')
- .column({ id: 'pages.id' }, { path: 'pages.path' }, 'title', { link: 'pageLinks.path' }, { locale: 'pageLinks.locale' })
- .leftJoin('pages', 'pageLinks.pageId', 'pages.id')
- .where({
- 'pages.locale': args.locale
- })
- )
- } else {
- results = await WIKI.db.knex('pages')
- .column({ id: 'pages.id' }, { path: 'pages.path' }, 'title', { link: 'pageLinks.path' }, { locale: 'pageLinks.locale' })
- .fullOuterJoin('pageLinks', 'pages.id', 'pageLinks.pageId')
- .where({
- 'pages.locale': args.locale
- })
- }
- return _.reduce(results, (result, val) => {
- // -> Check if user has access to source and linked page
- if (
- !WIKI.auth.checkAccess(context.req.user, ['read:pages'], { path: val.path, locale: args.locale }) ||
- !WIKI.auth.checkAccess(context.req.user, ['read:pages'], { path: val.link, locale: val.locale })
- ) {
- return result
- }
- const existingEntry = _.findIndex(result, ['id', val.id])
- if (existingEntry >= 0) {
- if (val.link) {
- result[existingEntry].links.push(`${val.locale}/${val.link}`)
- }
- } else {
- result.push({
- id: val.id,
- title: val.title,
- path: `${args.locale}/${val.path}`,
- links: val.link ? [`${val.locale}/${val.link}`] : []
- })
- }
- return result
- }, [])
- },
- /**
- * CHECK FOR EDITING CONFLICT
- */
- async checkConflicts (obj, args, context, info) {
- let page = await WIKI.db.pages.query().select('path', 'locale', 'updatedAt').findById(args.id)
- if (page) {
- if (WIKI.auth.checkAccess(context.req.user, ['write:pages', 'manage:pages'], {
- path: page.path,
- locale: page.locale
- })) {
- return page.updatedAt > args.checkoutDate
- } else {
- throw new WIKI.Error.PageUpdateForbidden()
- }
- } else {
- throw new WIKI.Error.PageNotFound()
- }
- },
- /**
- * FETCH LATEST VERSION FOR CONFLICT COMPARISON
- */
- async checkConflictsLatest (obj, args, context, info) {
- let page = await WIKI.db.pages.getPageFromDb(args.id)
- if (page) {
- if (WIKI.auth.checkAccess(context.req.user, ['write:pages', 'manage:pages'], {
- path: page.path,
- locale: page.locale
- })) {
- return {
- ...page,
- tags: page.tags.map(t => t.tag),
- locale: page.locale
- }
- } else {
- throw new WIKI.Error.PageViewForbidden()
- }
- } else {
- throw new WIKI.Error.PageNotFound()
- }
- }
- },
- Mutation: {
- /**
- * CREATE PAGE
- */
- async createPage(obj, args, context) {
- try {
- const page = await WIKI.db.pages.createPage({
- ...args,
- user: context.req.user
- })
- return {
- operation: generateSuccess('Page created successfully.'),
- page
- }
- } catch (err) {
- return generateError(err)
- }
- },
- /**
- * UPDATE PAGE
- */
- async updatePage(obj, args, context) {
- try {
- const page = await WIKI.db.pages.updatePage({
- ...args,
- user: context.req.user
- })
- return {
- operation: generateSuccess('Page has been updated.'),
- page
- }
- } catch (err) {
- return generateError(err)
- }
- },
- /**
- * CONVERT PAGE
- */
- async convertPage(obj, args, context) {
- try {
- await WIKI.db.pages.convertPage({
- ...args,
- user: context.req.user
- })
- return {
- operation: generateSuccess('Page has been converted.')
- }
- } catch (err) {
- return generateError(err)
- }
- },
- /**
- * RENAME PAGE
- */
- async renamePage(obj, args, context) {
- try {
- await WIKI.db.pages.movePage({
- ...args,
- user: context.req.user
- })
- return {
- operation: generateSuccess('Page has been moved.')
- }
- } catch (err) {
- return generateError(err)
- }
- },
- /**
- * DELETE PAGE
- */
- async deletePage(obj, args, context) {
- try {
- await WIKI.db.pages.deletePage({
- ...args,
- user: context.req.user
- })
- return {
- operation: generateSuccess('Page has been deleted.')
- }
- } catch (err) {
- return generateError(err)
- }
- },
- /**
- * DELETE TAG
- */
- async deleteTag (obj, args, context) {
- try {
- const tagToDel = await WIKI.db.tags.query().findById(args.id)
- if (tagToDel) {
- await tagToDel.$relatedQuery('pages').unrelate()
- await WIKI.db.tags.query().deleteById(args.id)
- } else {
- throw new Error('This tag does not exist.')
- }
- return {
- operation: generateSuccess('Tag has been deleted.')
- }
- } catch (err) {
- return generateError(err)
- }
- },
- /**
- * UPDATE TAG
- */
- async updateTag (obj, args, context) {
- try {
- const affectedRows = await WIKI.db.tags.query()
- .findById(args.id)
- .patch({
- tag: _.trim(args.tag).toLowerCase(),
- title: _.trim(args.title)
- })
- if (affectedRows < 1) {
- throw new Error('This tag does not exist.')
- }
- return {
- operation: generateSuccess('Tag has been updated successfully.')
- }
- } catch (err) {
- return generateError(err)
- }
- },
- /**
- * FLUSH PAGE CACHE
- */
- async flushCache(obj, args, context) {
- try {
- await WIKI.db.pages.flushCache()
- WIKI.events.outbound.emit('flushCache')
- return {
- operation: generateSuccess('Pages Cache has been flushed successfully.')
- }
- } catch (err) {
- return generateError(err)
- }
- },
- /**
- * MIGRATE ALL PAGES FROM SOURCE LOCALE TO TARGET LOCALE
- */
- async migrateToLocale(obj, args, context) {
- try {
- const count = await WIKI.db.pages.migrateToLocale(args)
- return {
- operation: generateSuccess('Migrated content to target locale successfully.'),
- count
- }
- } catch (err) {
- return generateError(err)
- }
- },
- /**
- * REBUILD TREE
- */
- async rebuildPageTree(obj, args, context) {
- try {
- await WIKI.db.pages.rebuildTree()
- return {
- operation: generateSuccess('Page tree rebuilt successfully.')
- }
- } catch (err) {
- return generateError(err)
- }
- },
- /**
- * RERENDER PAGE
- */
- async rerenderPage (obj, args, context) {
- try {
- const page = await WIKI.db.pages.query().findById(args.id)
- if (!page) {
- throw new WIKI.Error.PageNotFound()
- }
- await WIKI.db.pages.renderPage(page)
- return {
- operation: generateSuccess('Page rerendered successfully.')
- }
- } catch (err) {
- return generateError(err)
- }
- },
- /**
- * RESTORE PAGE VERSION
- */
- async restorePage (obj, args, context) {
- try {
- const page = await WIKI.db.pages.query().select('path', 'locale').findById(args.pageId)
- if (!page) {
- throw new WIKI.Error.PageNotFound()
- }
- if (!WIKI.auth.checkAccess(context.req.user, ['write:pages'], {
- path: page.path,
- locale: page.locale
- })) {
- throw new WIKI.Error.PageRestoreForbidden()
- }
- const targetVersion = await WIKI.db.pageHistory.getVersion({ pageId: args.pageId, versionId: args.versionId })
- if (!targetVersion) {
- throw new WIKI.Error.PageNotFound()
- }
- await WIKI.db.pages.updatePage({
- ...targetVersion,
- id: targetVersion.pageId,
- user: context.req.user,
- action: 'restored'
- })
- return {
- operation: generateSuccess('Page version restored successfully.')
- }
- } catch (err) {
- return generateError(err)
- }
- },
- /**
- * Purge history
- */
- async purgePagesHistory (obj, args, context) {
- try {
- await WIKI.db.pageHistory.purge(args.olderThan)
- return {
- operation: generateSuccess('Page history purged successfully.')
- }
- } catch (err) {
- return generateError(err)
- }
- }
- },
- Page: {
- icon (page) {
- return page.icon || 'las la-file-alt'
- },
- password (page) {
- return page.password ? '********' : ''
- },
- content (page, args, context) {
- if (!WIKI.auth.checkAccess(context.req.user, ['read:source', 'write:pages', 'manage:pages'], {
- path: page.path,
- locale: page.locale
- })) {
- throw new Error('ERR_FORBIDDEN')
- }
- return page.content
- },
- // async tags (page) {
- // return WIKI.db.pages.relatedQuery('tags').for(page.id)
- // },
- tocDepth (page) {
- return {
- min: page.extra?.tocDepth?.min ?? 1,
- max: page.extra?.tocDepth?.max ?? 2
- }
- }
- // comments(pg) {
- // return pg.$relatedQuery('comments')
- // }
- }
- }
|