ProjectEntityHandler.mjs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import path from 'node:path'
  2. import DocstoreManager from '../Docstore/DocstoreManager.mjs'
  3. import Errors from '../Errors/Errors.js'
  4. import ProjectGetter from './ProjectGetter.mjs'
  5. import { callbackifyAll } from '@overleaf/promise-utils'
  6. import OError from '@overleaf/o-error'
  7. import { iterablePaths } from './IterablePath.mjs'
  8. async function getAllDocs(projectId) {
  9. // We get the path and name info from the project, and the lines and
  10. // version info from the doc store.
  11. const docContentsArray = await DocstoreManager.promises.getAllDocs(projectId)
  12. // Turn array from docstore into a dictionary based on doc id
  13. const docContents = {}
  14. for (const docContent of docContentsArray) {
  15. docContents[docContent._id] = docContent
  16. }
  17. const folders = await _getAllFolders(projectId)
  18. const docs = {}
  19. for (const { path: folderPath, folder } of folders) {
  20. for (const doc of iterablePaths(folder, 'docs')) {
  21. const content = docContents[doc._id.toString()]
  22. if (content != null) {
  23. docs[path.join(folderPath, doc.name)] = {
  24. _id: doc._id,
  25. name: doc.name,
  26. lines: content.lines,
  27. rev: content.rev,
  28. folder,
  29. }
  30. }
  31. }
  32. }
  33. return docs
  34. }
  35. async function getAllFiles(projectId) {
  36. const folders = await _getAllFolders(projectId)
  37. const files = {}
  38. for (const { path: folderPath, folder } of folders) {
  39. for (const file of iterablePaths(folder, 'fileRefs')) {
  40. if (file != null) {
  41. files[path.join(folderPath, file.name)] = { ...file, folder }
  42. }
  43. }
  44. }
  45. return files
  46. }
  47. async function getAllEntities(projectId) {
  48. const project = await ProjectGetter.promises.getProject(projectId)
  49. if (project == null) {
  50. throw new Errors.NotFoundError('project not found')
  51. }
  52. const entities = getAllEntitiesFromProject(project)
  53. return entities
  54. }
  55. function getAllEntitiesFromProject(project) {
  56. const folders = _getAllFoldersFromProject(project)
  57. const docs = []
  58. const files = []
  59. for (const { path: folderPath, folder } of folders) {
  60. for (const doc of iterablePaths(folder, 'docs')) {
  61. if (doc != null) {
  62. docs.push({ path: path.join(folderPath, doc.name), doc })
  63. }
  64. }
  65. for (const file of iterablePaths(folder, 'fileRefs')) {
  66. if (file != null) {
  67. files.push({ path: path.join(folderPath, file.name), file })
  68. }
  69. }
  70. }
  71. return { docs, files, folders }
  72. }
  73. async function getAllDocPathsFromProjectById(projectId) {
  74. const project =
  75. await ProjectGetter.promises.getProjectWithoutDocLines(projectId)
  76. if (project == null) {
  77. throw new Errors.NotFoundError('no project')
  78. }
  79. const docPaths = getAllDocPathsFromProject(project)
  80. return docPaths
  81. }
  82. function getAllDocPathsFromProject(project) {
  83. const folders = _getAllFoldersFromProject(project)
  84. const docPath = {}
  85. for (const { path: folderPath, folder } of folders) {
  86. for (const doc of iterablePaths(folder, 'docs')) {
  87. docPath[doc._id] = path.join(folderPath, doc.name)
  88. }
  89. }
  90. return docPath
  91. }
  92. /**
  93. *
  94. * @param {string} projectId
  95. * @param {string} docId
  96. * @param {{peek?: boolean, include_deleted?: boolean}} options
  97. * @return {Promise<{lines: *, rev: *, version: *, ranges: *}>}
  98. */
  99. async function getDoc(projectId, docId, options = {}) {
  100. const { lines, rev, version, ranges } = await DocstoreManager.promises.getDoc(
  101. projectId,
  102. docId,
  103. options
  104. )
  105. return { lines, rev, version, ranges }
  106. }
  107. /**
  108. * @param {ObjectId | string} projectId
  109. * @param {ObjectId | string} docId
  110. */
  111. async function getDocPathByProjectIdAndDocId(projectId, docId) {
  112. const project =
  113. await ProjectGetter.promises.getProjectWithoutDocLines(projectId)
  114. if (project == null) {
  115. throw new Errors.NotFoundError('no project')
  116. }
  117. const docPath = await getDocPathFromProjectByDocId(project, docId)
  118. if (docPath == null) {
  119. throw new Errors.NotFoundError('no doc')
  120. }
  121. return docPath
  122. }
  123. function _recursivelyFindDocInFolder(basePath, docId, folder) {
  124. const docInCurrentFolder = (folder.docs || []).find(
  125. currentDoc => currentDoc._id.toString() === docId.toString()
  126. )
  127. if (docInCurrentFolder != null) {
  128. return path.join(basePath, docInCurrentFolder.name)
  129. } else {
  130. let docPath, childFolder
  131. for (childFolder of iterablePaths(folder, 'folders')) {
  132. docPath = _recursivelyFindDocInFolder(
  133. path.join(basePath, childFolder.name),
  134. docId,
  135. childFolder
  136. )
  137. if (docPath != null) {
  138. return docPath
  139. }
  140. }
  141. return null
  142. }
  143. }
  144. /**
  145. * @param {Project} project
  146. * @param {ObjectId | string} docId
  147. * @param {Function} callback
  148. */
  149. async function getDocPathFromProjectByDocId(project, docId) {
  150. const docPath = _recursivelyFindDocInFolder('/', docId, project.rootFolder[0])
  151. return docPath
  152. }
  153. async function _getAllFolders(projectId) {
  154. const project =
  155. await ProjectGetter.promises.getProjectWithoutDocLines(projectId)
  156. if (project == null) {
  157. throw new Errors.NotFoundError('no project')
  158. }
  159. const folders = _getAllFoldersFromProject(project)
  160. return folders
  161. }
  162. function _getAllFoldersFromProject(project) {
  163. const folders = []
  164. try {
  165. const processFolder = (basePath, folder) => {
  166. folders.push({ path: basePath, folder })
  167. if (folder.folders) {
  168. for (const childFolder of iterablePaths(folder, 'folders')) {
  169. if (childFolder.name != null) {
  170. const childPath = path.join(basePath, childFolder.name)
  171. processFolder(childPath, childFolder)
  172. }
  173. }
  174. }
  175. }
  176. processFolder('/', project.rootFolder[0])
  177. return folders
  178. } catch (err) {
  179. throw OError.tag(err, 'Error getting folders', { projectId: project._id })
  180. }
  181. }
  182. const ProjectEntityHandler = {
  183. getAllDocs,
  184. getAllFiles,
  185. getAllEntities,
  186. getAllDocPathsFromProjectById,
  187. getDoc,
  188. getDocPathByProjectIdAndDocId,
  189. getDocPathFromProjectByDocId,
  190. _getAllFolders,
  191. }
  192. export default {
  193. ...callbackifyAll(ProjectEntityHandler, {
  194. multiResult: {
  195. getDoc: ['lines', 'rev', 'version', 'ranges'],
  196. },
  197. }),
  198. promises: ProjectEntityHandler,
  199. getAllEntitiesFromProject,
  200. getAllDocPathsFromProject,
  201. _getAllFoldersFromProject,
  202. }