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 } } }