tree.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  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.includeRootItems) {
  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. }
  94. }))
  95. },
  96. /**
  97. * FETCH SINGLE FOLDER BY ID
  98. */
  99. async folderById (obj, args, context) {
  100. const folder = await WIKI.db.knex('tree')
  101. .select(WIKI.db.knex.raw('tree.*, nlevel(tree."folderPath") AS depth'))
  102. .where('id', args.id)
  103. .first()
  104. if (!folder) {
  105. throw new Error('ERR_FOLDER_NOT_EXIST')
  106. }
  107. return {
  108. ...folder,
  109. folderPath: folder.folderPath.replaceAll('.', '/').replaceAll('_', '-'),
  110. childrenCount: folder.meta?.children || 0
  111. }
  112. },
  113. /**
  114. * FETCH SINGLE FOLDER BY PATH
  115. */
  116. async folderByPath (obj, args, context) {
  117. const parentPathParts = args.path.replaceAll('/', '.').replaceAll('-', '_').split('.')
  118. const folder = await WIKI.db.knex('tree')
  119. .select(WIKI.db.knex.raw('tree.*, nlevel(tree."folderPath") AS depth'))
  120. .where({
  121. siteId: args.siteId,
  122. localeCode: args.locale,
  123. folderPath: _.dropRight(parentPathParts).join('.'),
  124. fileName: _.last(parentPathParts)
  125. })
  126. .first()
  127. if (!folder) {
  128. throw new Error('ERR_FOLDER_NOT_EXIST')
  129. }
  130. return {
  131. ...folder,
  132. folderPath: folder.folderPath.replaceAll('.', '/').replaceAll('_', '-'),
  133. childrenCount: folder.meta?.children || 0
  134. }
  135. }
  136. },
  137. Mutation: {
  138. /**
  139. * CREATE FOLDER
  140. */
  141. async createFolder (obj, args, context) {
  142. try {
  143. await WIKI.db.tree.createFolder(args)
  144. return {
  145. operation: graphHelper.generateSuccess('Folder created successfully')
  146. }
  147. } catch (err) {
  148. WIKI.logger.debug(`Failed to create folder: ${err.message}`)
  149. return graphHelper.generateError(err)
  150. }
  151. },
  152. /**
  153. * RENAME FOLDER
  154. */
  155. async renameFolder (obj, args, context) {
  156. try {
  157. await WIKI.db.tree.renameFolder(args)
  158. return {
  159. operation: graphHelper.generateSuccess('Folder renamed successfully')
  160. }
  161. } catch (err) {
  162. WIKI.logger.debug(`Failed to rename folder ${args.folderId}: ${err.message}`)
  163. return graphHelper.generateError(err)
  164. }
  165. },
  166. /**
  167. * DELETE FOLDER
  168. */
  169. async deleteFolder (obj, args, context) {
  170. try {
  171. await WIKI.db.tree.deleteFolder(args.folderId)
  172. return {
  173. operation: graphHelper.generateSuccess('Folder deleted successfully')
  174. }
  175. } catch (err) {
  176. WIKI.logger.debug(`Failed to delete folder ${args.folderId}: ${err.message}`)
  177. return graphHelper.generateError(err)
  178. }
  179. }
  180. },
  181. TreeItem: {
  182. __resolveType (obj, context, info) {
  183. return typeResolvers[obj.type] ?? null
  184. }
  185. }
  186. }