tree.mjs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import _ from 'lodash-es'
  2. import {
  3. decodeFolderPath,
  4. encodeFolderPath,
  5. decodeTreePath,
  6. encodeTreePath
  7. } from '../../helpers/common.mjs'
  8. import { generateError, generateSuccess } from '../../helpers/graph.mjs'
  9. const typeResolvers = {
  10. folder: 'TreeItemFolder',
  11. page: 'TreeItemPage',
  12. asset: 'TreeItemAsset'
  13. }
  14. const rePathName = /^[a-z0-9-]+$/
  15. const reTitle = /^[^<>"]+$/
  16. export default {
  17. Query: {
  18. /**
  19. * FETCH TREE
  20. */
  21. async tree (obj, args, context, info) {
  22. // Offset
  23. const offset = args.offset || 0
  24. if (offset < 0) {
  25. throw new Error('Invalid Offset')
  26. }
  27. // Limit
  28. const limit = args.limit || 1000
  29. if (limit < 1 || limit > 1000) {
  30. throw new Error('Invalid Limit')
  31. }
  32. // Order By
  33. const orderByDirection = args.orderByDirection || 'asc'
  34. const orderBy = args.orderBy || 'title'
  35. // Parse depth
  36. const depth = args.depth || 0
  37. if (depth < 0 || depth > 10) {
  38. throw new Error('Invalid Depth')
  39. }
  40. const depthCondition = depth > 0 ? `*{,${depth}}` : '*{0}'
  41. // Get parent path
  42. let parentPath = ''
  43. if (args.parentId) {
  44. const parent = await WIKI.db.knex('tree').where('id', args.parentId).first()
  45. if (parent) {
  46. parentPath = (parent.folderPath ? `${decodeFolderPath(parent.folderPath)}.${parent.fileName}` : parent.fileName)
  47. }
  48. } else if (args.parentPath) {
  49. parentPath = encodeTreePath(args.parentPath)
  50. }
  51. const folderPathCondition = parentPath ? `${encodeFolderPath(parentPath)}.${depthCondition}` : depthCondition
  52. // Fetch Items
  53. const items = await WIKI.db.knex('tree')
  54. .select(WIKI.db.knex.raw('tree.*, nlevel(tree."folderPath") AS depth'))
  55. .where(builder => {
  56. builder.where('folderPath', '~', folderPathCondition)
  57. // -> Include ancestors
  58. if (args.includeAncestors) {
  59. const parentPathParts = parentPath.split('.')
  60. for (let i = 0; i <= parentPathParts.length; i++) {
  61. builder.orWhere({
  62. folderPath: encodeFolderPath(_.dropRight(parentPathParts, i).join('.')),
  63. fileName: _.nth(parentPathParts, i * -1),
  64. type: 'folder'
  65. })
  66. }
  67. }
  68. // -> Include root items
  69. if (args.includeRootFolders) {
  70. builder.orWhere({
  71. folderPath: '',
  72. type: 'folder'
  73. })
  74. }
  75. })
  76. .andWhere(builder => {
  77. // -> Limit to specific types
  78. if (args.types && args.types.length > 0) {
  79. builder.whereIn('type', args.types)
  80. }
  81. })
  82. .limit(limit)
  83. .offset(offset)
  84. .orderBy([
  85. { column: 'depth' },
  86. { column: orderBy, order: orderByDirection }
  87. ])
  88. return items.map(item => ({
  89. id: item.id,
  90. depth: item.depth,
  91. type: item.type,
  92. folderPath: decodeTreePath(decodeFolderPath(item.folderPath)),
  93. fileName: item.fileName,
  94. title: item.title,
  95. createdAt: item.createdAt,
  96. updatedAt: item.updatedAt,
  97. ...(item.type === 'folder') && {
  98. childrenCount: item.meta?.children || 0,
  99. isAncestor: item.folderPath.length < parentPath.length
  100. },
  101. ...(item.type === 'asset') && {
  102. fileSize: item.meta?.fileSize || 0,
  103. fileExt: item.meta?.fileExt || '',
  104. mimeType: item.meta?.mimeType || ''
  105. }
  106. }))
  107. },
  108. /**
  109. * FETCH SINGLE FOLDER BY ID
  110. */
  111. async folderById (obj, args, context) {
  112. const folder = await WIKI.db.knex('tree')
  113. .select(WIKI.db.knex.raw('tree.*, nlevel(tree."folderPath") AS depth'))
  114. .where('id', args.id)
  115. .first()
  116. if (!folder) {
  117. throw new Error('ERR_FOLDER_NOT_EXIST')
  118. }
  119. return {
  120. ...folder,
  121. folderPath: folder.folderPath.replaceAll('.', '/').replaceAll('_', '-'),
  122. childrenCount: folder.meta?.children || 0
  123. }
  124. },
  125. /**
  126. * FETCH SINGLE FOLDER BY PATH
  127. */
  128. async folderByPath (obj, args, context) {
  129. const parentPathParts = args.path.replaceAll('/', '.').replaceAll('-', '_').split('.')
  130. const folder = await WIKI.db.knex('tree')
  131. .select(WIKI.db.knex.raw('tree.*, nlevel(tree."folderPath") AS depth'))
  132. .where({
  133. siteId: args.siteId,
  134. locale: args.locale,
  135. folderPath: _.dropRight(parentPathParts).join('.'),
  136. fileName: _.last(parentPathParts)
  137. })
  138. .first()
  139. if (!folder) {
  140. throw new Error('ERR_FOLDER_NOT_EXIST')
  141. }
  142. return {
  143. ...folder,
  144. folderPath: folder.folderPath.replaceAll('.', '/').replaceAll('_', '-'),
  145. childrenCount: folder.meta?.children || 0
  146. }
  147. }
  148. },
  149. Mutation: {
  150. /**
  151. * CREATE FOLDER
  152. */
  153. async createFolder (obj, args, context) {
  154. try {
  155. await WIKI.db.tree.createFolder(args)
  156. return {
  157. operation: generateSuccess('Folder created successfully')
  158. }
  159. } catch (err) {
  160. WIKI.logger.debug(`Failed to create folder: ${err.message}`)
  161. return generateError(err)
  162. }
  163. },
  164. /**
  165. * RENAME FOLDER
  166. */
  167. async renameFolder (obj, args, context) {
  168. try {
  169. await WIKI.db.tree.renameFolder(args)
  170. return {
  171. operation: generateSuccess('Folder renamed successfully')
  172. }
  173. } catch (err) {
  174. WIKI.logger.debug(`Failed to rename folder ${args.folderId}: ${err.message}`)
  175. return generateError(err)
  176. }
  177. },
  178. /**
  179. * DELETE FOLDER
  180. */
  181. async deleteFolder (obj, args, context) {
  182. try {
  183. await WIKI.db.tree.deleteFolder(args.folderId)
  184. return {
  185. operation: generateSuccess('Folder deleted successfully')
  186. }
  187. } catch (err) {
  188. WIKI.logger.debug(`Failed to delete folder ${args.folderId}: ${err.message}`)
  189. return generateError(err)
  190. }
  191. }
  192. },
  193. TreeItem: {
  194. __resolveType (obj, context, info) {
  195. return typeResolvers[obj.type] ?? null
  196. }
  197. }
  198. }