TpdsUpdateHandler.mjs 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import { callbackify } from 'node:util'
  2. import UpdateMerger from './UpdateMerger.mjs'
  3. import logger from '@overleaf/logger'
  4. import NotificationsBuilder from '../Notifications/NotificationsBuilder.mjs'
  5. import ProjectCreationHandler from '../Project/ProjectCreationHandler.mjs'
  6. import ProjectDeleter from '../Project/ProjectDeleter.mjs'
  7. import ProjectGetter from '../Project/ProjectGetter.mjs'
  8. import ProjectHelper from '../Project/ProjectHelper.mjs'
  9. import ProjectRootDocManager from '../Project/ProjectRootDocManager.mjs'
  10. import FileTypeManager from '../Uploads/FileTypeManager.mjs'
  11. import CooldownManager from '../Cooldown/CooldownManager.js'
  12. import Errors from '../Errors/Errors.js'
  13. import Modules from '../../infrastructure/Modules.js'
  14. async function newUpdate(
  15. userId,
  16. projectId,
  17. projectName,
  18. path,
  19. updateRequest,
  20. source
  21. ) {
  22. const project = await getOrCreateProject(userId, projectId, projectName)
  23. if (project == null) {
  24. return null
  25. }
  26. const projectIsOnCooldown =
  27. await CooldownManager.promises.isProjectOnCooldown(project._id)
  28. if (projectIsOnCooldown) {
  29. throw new Errors.TooManyRequestsError('project on cooldown')
  30. }
  31. const shouldIgnore = FileTypeManager.shouldIgnore(path)
  32. if (shouldIgnore) {
  33. return null
  34. }
  35. const metadata = await UpdateMerger.promises.mergeUpdate(
  36. userId,
  37. project._id,
  38. path,
  39. updateRequest,
  40. source
  41. )
  42. return metadata
  43. }
  44. async function deleteUpdate(userId, projectId, projectName, path, source) {
  45. logger.debug({ userId, filePath: path }, 'handling delete update from tpds')
  46. let projects = []
  47. if (projectId) {
  48. const project = await findProjectByIdWithRWAccess(userId, projectId)
  49. if (project) {
  50. projects = [project]
  51. }
  52. } else {
  53. projects = await ProjectGetter.promises.findUsersProjectsByName(
  54. userId,
  55. projectName
  56. )
  57. }
  58. const activeProjects = projects.filter(
  59. project => !ProjectHelper.isArchivedOrTrashed(project, userId)
  60. )
  61. if (activeProjects.length === 0) {
  62. logger.debug(
  63. { userId, filePath: path, projectName },
  64. 'project not found from tpds update, ignoring folder or project'
  65. )
  66. return
  67. }
  68. if (projects.length > 1) {
  69. // There is more than one project with that name, and one of them is
  70. // active (previous condition)
  71. await handleDuplicateProjects(userId, projectName)
  72. return
  73. }
  74. const project = activeProjects[0]
  75. if (path === '/') {
  76. logger.debug(
  77. { userId, filePath: path, projectName, projectId: project._id },
  78. 'project found for delete update, path is root so marking project as deleted'
  79. )
  80. await ProjectDeleter.promises.markAsDeletedByExternalSource(project._id)
  81. } else {
  82. await UpdateMerger.promises.deleteUpdate(userId, project._id, path, source)
  83. }
  84. }
  85. async function getOrCreateProject(userId, projectId, projectName) {
  86. if (projectId) {
  87. return findProjectByIdWithRWAccess(userId, projectId)
  88. } else {
  89. return getOrCreateProjectByName(userId, projectName)
  90. }
  91. }
  92. async function findProjectByIdWithRWAccess(userId, projectId) {
  93. const allProjects = await ProjectGetter.promises.findAllUsersProjects(
  94. userId,
  95. 'name archived trashed'
  96. )
  97. for (const projects of [allProjects.owned, allProjects.readAndWrite]) {
  98. for (const project of projects) {
  99. if (project._id.toString() === projectId) {
  100. return project
  101. }
  102. }
  103. }
  104. }
  105. async function getOrCreateProjectByName(userId, projectName) {
  106. const projects = await ProjectGetter.promises.findUsersProjectsByName(
  107. userId,
  108. projectName
  109. )
  110. if (projects.length === 0) {
  111. // No project with that name -- active, archived or trashed -- has been
  112. // found. Create one.
  113. const project = await ProjectCreationHandler.promises.createBlankProject(
  114. userId,
  115. projectName
  116. )
  117. // have a crack at setting the root doc after a while, on creation
  118. // we won't have it yet, but should have been sent it it within 30
  119. // seconds
  120. ProjectRootDocManager.setRootDocAutomaticallyInBackground(project._id)
  121. return project
  122. }
  123. const activeProjects = projects.filter(
  124. project => !ProjectHelper.isArchivedOrTrashed(project, userId)
  125. )
  126. if (activeProjects.length === 0) {
  127. // All projects with that name are archived or trashed. Ignore.
  128. return null
  129. }
  130. if (projects.length > 1) {
  131. // There is more than one project with that name, and one of them is
  132. // active (previous condition)
  133. await handleDuplicateProjects(userId, projectName)
  134. return null
  135. }
  136. return activeProjects[0]
  137. }
  138. async function handleDuplicateProjects(userId, projectName) {
  139. await Modules.promises.hooks.fire(
  140. 'removeDropbox',
  141. userId,
  142. 'duplicate-projects'
  143. )
  144. await NotificationsBuilder.promises
  145. .dropboxDuplicateProjectNames(userId)
  146. .create(projectName)
  147. }
  148. async function createFolder(userId, projectId, projectName, path) {
  149. const project = await getOrCreateProject(userId, projectId, projectName)
  150. if (project == null) {
  151. return null
  152. }
  153. const projectIsOnCooldown =
  154. await CooldownManager.promises.isProjectOnCooldown(project._id)
  155. if (projectIsOnCooldown) {
  156. throw new Errors.TooManyRequestsError('project on cooldown')
  157. }
  158. const shouldIgnore = FileTypeManager.shouldIgnore(path)
  159. if (shouldIgnore) {
  160. return null
  161. }
  162. const folder = await UpdateMerger.promises.createFolder(
  163. project._id,
  164. path,
  165. userId
  166. )
  167. return {
  168. folderId: folder._id,
  169. parentFolderId: folder.parentFolder_id,
  170. projectId: project._id,
  171. path,
  172. }
  173. }
  174. export default {
  175. newUpdate: callbackify(newUpdate),
  176. deleteUpdate: callbackify(deleteUpdate),
  177. createFolder: callbackify(createFolder),
  178. promises: {
  179. newUpdate,
  180. deleteUpdate,
  181. createFolder,
  182. },
  183. }