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