tree.js 6.0 KB

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