tree.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  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. async tree (obj, args, context, info) {
  13. // Offset
  14. const offset = args.offset || 0
  15. if (offset < 0) {
  16. throw new Error('Invalid Offset')
  17. }
  18. // Limit
  19. const limit = args.limit || 100
  20. if (limit < 1 || limit > 100) {
  21. throw new Error('Invalid Limit')
  22. }
  23. // Order By
  24. const orderByDirection = args.orderByDirection || 'asc'
  25. const orderBy = args.orderBy || 'title'
  26. // Parse depth
  27. const depth = args.depth || 0
  28. if (depth < 0 || depth > 10) {
  29. throw new Error('Invalid Depth')
  30. }
  31. const depthCondition = depth > 0 ? `*{,${depth}}` : '*{0}'
  32. // Get parent path
  33. let parentPath = ''
  34. if (args.parentId) {
  35. const parent = await WIKI.db.knex('tree').where('id', args.parentId).first()
  36. if (parent) {
  37. parentPath = (parent.folderPath ? `${parent.folderPath}.${parent.fileName}` : parent.fileName).replaceAll('-', '_')
  38. }
  39. } else if (args.parentPath) {
  40. parentPath = args.parentPath.replaceAll('/', '.').replaceAll('-', '_').toLowerCase()
  41. }
  42. const folderPathCondition = parentPath ? `${parentPath}.${depthCondition}` : depthCondition
  43. // Fetch Items
  44. const items = await WIKI.db.knex('tree')
  45. .select(WIKI.db.knex.raw('tree.*, nlevel(tree."folderPath") AS depth'))
  46. .where(builder => {
  47. builder.where('folderPath', '~', folderPathCondition)
  48. if (args.includeAncestors) {
  49. const parentPathParts = parentPath.split('.')
  50. for (let i = 1; i <= parentPathParts.length; i++) {
  51. builder.orWhere({
  52. folderPath: _.dropRight(parentPathParts, i).join('.'),
  53. fileName: _.nth(parentPathParts, i * -1)
  54. })
  55. }
  56. }
  57. })
  58. .andWhere(builder => {
  59. if (args.types && args.types.length > 0) {
  60. builder.whereIn('type', args.types)
  61. }
  62. })
  63. .limit(limit)
  64. .offset(offset)
  65. .orderBy([
  66. { column: 'depth' },
  67. { column: orderBy, order: orderByDirection }
  68. ])
  69. return items.map(item => ({
  70. id: item.id,
  71. depth: item.depth,
  72. type: item.type,
  73. folderPath: item.folderPath.replaceAll('.', '/').replaceAll('_', '-'),
  74. fileName: item.fileName,
  75. title: item.title,
  76. createdAt: item.createdAt,
  77. updatedAt: item.updatedAt,
  78. ...(item.type === 'folder') && {
  79. childrenCount: 0
  80. }
  81. }))
  82. }
  83. },
  84. Mutation: {
  85. /**
  86. * CREATE FOLDER
  87. */
  88. async createFolder (obj, args, context) {
  89. try {
  90. // Get parent path
  91. let parentPath = ''
  92. if (args.parentId) {
  93. const parent = await WIKI.db.knex('tree').where('id', args.parentId).first()
  94. parentPath = parent ? `${parent.folderPath}.${parent.fileName}` : ''
  95. if (parent) {
  96. parentPath = parent.folderPath ? `${parent.folderPath}.${parent.fileName}` : parent.fileName
  97. }
  98. parentPath = parentPath.replaceAll('-', '_')
  99. }
  100. // Validate path name
  101. if (!rePathName.test(args.pathName)) {
  102. throw new Error('ERR_INVALID_PATH_NAME')
  103. }
  104. // Validate title
  105. if (!reTitle.test(args.title)) {
  106. throw new Error('ERR_INVALID_TITLE')
  107. }
  108. // Check for collision
  109. const existingFolder = await WIKI.db.knex('tree').where({
  110. siteId: args.siteId,
  111. folderPath: parentPath,
  112. fileName: args.pathName
  113. }).first()
  114. if (existingFolder) {
  115. throw new Error('ERR_FOLDER_ALREADY_EXISTS')
  116. }
  117. // Create folder
  118. await WIKI.db.knex('tree').insert({
  119. folderPath: parentPath,
  120. fileName: args.pathName,
  121. type: 'folder',
  122. title: args.title,
  123. siteId: args.siteId
  124. })
  125. return {
  126. operation: graphHelper.generateSuccess('Folder created successfully')
  127. }
  128. } catch (err) {
  129. return graphHelper.generateError(err)
  130. }
  131. },
  132. /**
  133. * DELETE FOLDER
  134. */
  135. async deleteFolder (obj, args, context) {
  136. try {
  137. // Get folder
  138. const folder = await WIKI.db.knex('tree').where('id', args.folderId).first()
  139. const folderPath = folder.folderPath ? `${folder.folderPath}.${folder.fileName}` : folder.fileName
  140. WIKI.logger.debug(`Deleting folder ${folder.id} at path ${folderPath}...`)
  141. // Delete all children
  142. const deletedNodes = await WIKI.db.knex('tree').where('folderPath', '~', `${folderPath}.*`).del().returning(['id', 'type'])
  143. // Delete folders
  144. const deletedFolders = deletedNodes.filter(n => n.type === 'folder').map(n => n.id)
  145. if (deletedFolders.length > 0) {
  146. WIKI.logger.debug(`Deleted ${deletedFolders.length} children folders.`)
  147. }
  148. // Delete pages
  149. const deletedPages = deletedNodes.filter(n => n.type === 'page').map(n => n.id)
  150. if (deletedPages.length > 0) {
  151. WIKI.logger.debug(`Deleting ${deletedPages.length} children pages...`)
  152. // TODO: Delete page
  153. }
  154. // Delete assets
  155. const deletedAssets = deletedNodes.filter(n => n.type === 'asset').map(n => n.id)
  156. if (deletedAssets.length > 0) {
  157. WIKI.logger.debug(`Deleting ${deletedPages.length} children assets...`)
  158. // TODO: Delete asset
  159. }
  160. // Delete the folder itself
  161. await WIKI.db.knex('tree').where('id', folder.id).del()
  162. WIKI.logger.debug(`Deleting folder ${folder.id} successfully.`)
  163. return {
  164. operation: graphHelper.generateSuccess('Folder deleted successfully')
  165. }
  166. } catch (err) {
  167. return graphHelper.generateError(err)
  168. }
  169. }
  170. },
  171. TreeItem: {
  172. __resolveType (obj, context, info) {
  173. return typeResolvers[obj.type] ?? null
  174. }
  175. }
  176. }