You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
wiki/server/graph/resolvers/tree.mjs

224 lines
6.3 KiB

import _ from 'lodash-es'
import {
decodeFolderPath,
encodeFolderPath,
decodeTreePath,
encodeTreePath
} from '../../helpers/common.mjs'
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
const typeResolvers = {
folder: 'TreeItemFolder',
page: 'TreeItemPage',
asset: 'TreeItemAsset'
}
const rePathName = /^[a-z0-9-]+$/
const reTitle = /^[^<>"]+$/
export default {
Query: {
/**
* FETCH TREE
*/
async tree (obj, args, context, info) {
// Offset
const offset = args.offset || 0
if (offset < 0) {
throw new Error('Invalid Offset')
}
// Limit
const limit = args.limit || 1000
if (limit < 1 || limit > 1000) {
throw new Error('Invalid Limit')
}
// Order By
const orderByDirection = args.orderByDirection || 'asc'
const orderBy = args.orderBy || 'title'
// Parse depth
const depth = args.depth || 0
if (depth < 0 || depth > 10) {
throw new Error('Invalid Depth')
}
const depthCondition = depth > 0 ? `*{,${depth}}` : '*{0}'
// Get parent path
let parentPath = ''
if (args.parentId) {
const parent = await WIKI.db.knex('tree').where('id', args.parentId).first()
if (parent) {
parentPath = (parent.folderPath ? `${decodeFolderPath(parent.folderPath)}.${parent.fileName}` : parent.fileName)
}
} else if (args.parentPath) {
parentPath = encodeTreePath(args.parentPath)
}
const folderPathCondition = parentPath ? `${encodeFolderPath(parentPath)}.${depthCondition}` : depthCondition
// Fetch Items
const items = await WIKI.db.knex('tree')
.select(WIKI.db.knex.raw('tree.*, nlevel(tree."folderPath") AS depth'))
.where(builder => {
builder.where('folderPath', '~', folderPathCondition)
// -> Include ancestors
if (args.includeAncestors) {
const parentPathParts = parentPath.split('.')
for (let i = 0; i <= parentPathParts.length; i++) {
builder.orWhere({
folderPath: encodeFolderPath(_.dropRight(parentPathParts, i).join('.')),
fileName: _.nth(parentPathParts, i * -1),
type: 'folder'
})
}
}
// -> Include root items
if (args.includeRootFolders) {
builder.orWhere({
folderPath: '',
type: 'folder'
})
}
// -> Filter by tags
if (args.tags && args.tags.length > 0) {
builder.where('tags', '@>', args.tags)
}
})
.andWhere(builder => {
// -> Limit to specific types
if (args.types && args.types.length > 0) {
builder.whereIn('type', args.types)
}
})
.limit(limit)
.offset(offset)
.orderBy([
{ column: 'depth' },
{ column: orderBy, order: orderByDirection }
])
return items.map(item => ({
id: item.id,
depth: item.depth,
type: item.type,
folderPath: decodeTreePath(decodeFolderPath(item.folderPath)),
fileName: item.fileName,
title: item.title,
tags: item.tags ?? [],
createdAt: item.createdAt,
updatedAt: item.updatedAt,
...(item.type === 'folder') && {
childrenCount: item.meta?.children || 0,
isAncestor: item.folderPath.length < parentPath.length
},
...(item.type === 'asset') && {
fileSize: item.meta?.fileSize || 0,
fileExt: item.meta?.fileExt || '',
mimeType: item.meta?.mimeType || ''
},
...(item.type === 'page') && {
description: item.meta?.description || ''
}
}))
},
/**
* FETCH SINGLE FOLDER BY ID
*/
async folderById (obj, args, context) {
const folder = await WIKI.db.knex('tree')
.select(WIKI.db.knex.raw('tree.*, nlevel(tree."folderPath") AS depth'))
.where('id', args.id)
.first()
if (!folder) {
throw new Error('ERR_INVALID_FOLDER')
}
return {
...folder,
folderPath: folder.folderPath.replaceAll('.', '/').replaceAll('_', '-'),
childrenCount: folder.meta?.children || 0
}
},
/**
* FETCH SINGLE FOLDER BY PATH
*/
async folderByPath (obj, args, context) {
const parentPathParts = args.path.replaceAll('/', '.').replaceAll('-', '_').split('.')
const folder = await WIKI.db.knex('tree')
.select(WIKI.db.knex.raw('tree.*, nlevel(tree."folderPath") AS depth'))
.where({
siteId: args.siteId,
locale: args.locale,
folderPath: _.dropRight(parentPathParts).join('.'),
fileName: _.last(parentPathParts)
})
.first()
if (!folder) {
throw new Error('ERR_INVALID_FOLDER')
}
return {
...folder,
folderPath: folder.folderPath.replaceAll('.', '/').replaceAll('_', '-'),
childrenCount: folder.meta?.children || 0
}
}
},
Mutation: {
/**
* CREATE FOLDER
*/
async createFolder (obj, args, context) {
try {
await WIKI.db.tree.createFolder(args)
return {
operation: generateSuccess('Folder created successfully')
}
} catch (err) {
WIKI.logger.debug(`Failed to create folder: ${err.message}`)
return generateError(err)
}
},
/**
* RENAME FOLDER
*/
async renameFolder (obj, args, context) {
try {
await WIKI.db.tree.renameFolder(args)
return {
operation: generateSuccess('Folder renamed successfully')
}
} catch (err) {
WIKI.logger.debug(`Failed to rename folder ${args.folderId}: ${err.message}`)
return generateError(err)
}
},
/**
* DELETE FOLDER
*/
async deleteFolder (obj, args, context) {
try {
await WIKI.db.tree.deleteFolder(args.folderId)
return {
operation: generateSuccess('Folder deleted successfully')
}
} catch (err) {
WIKI.logger.debug(`Failed to delete folder ${args.folderId}: ${err.message}`)
return generateError(err)
}
}
},
TreeItem: {
__resolveType (obj, context, info) {
return typeResolvers[obj.type] ?? null
}
}
}