TpdsUpdateHandler.mjs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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.mjs'
  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. if (ProjectHelper.isArchivedOrTrashed(project, userId)) {
  101. return null
  102. } else {
  103. return project
  104. }
  105. }
  106. }
  107. }
  108. }
  109. async function getOrCreateProjectByName(userId, projectName) {
  110. const projects = await ProjectGetter.promises.findUsersProjectsByName(
  111. userId,
  112. projectName
  113. )
  114. if (projects.length === 0) {
  115. // No project with that name -- active, archived or trashed -- has been
  116. // found. Create one.
  117. const project = await ProjectCreationHandler.promises.createBlankProject(
  118. userId,
  119. projectName
  120. )
  121. // have a crack at setting the root doc after a while, on creation
  122. // we won't have it yet, but should have been sent it it within 30
  123. // seconds
  124. ProjectRootDocManager.setRootDocAutomaticallyInBackground(project._id)
  125. return project
  126. }
  127. const activeProjects = projects.filter(
  128. project => !ProjectHelper.isArchivedOrTrashed(project, userId)
  129. )
  130. if (activeProjects.length === 0) {
  131. // All projects with that name are archived or trashed. Ignore.
  132. return null
  133. }
  134. if (projects.length > 1) {
  135. // There is more than one project with that name, and one of them is
  136. // active (previous condition)
  137. await handleDuplicateProjects(userId, projectName)
  138. return null
  139. }
  140. return activeProjects[0]
  141. }
  142. async function handleDuplicateProjects(userId, projectName) {
  143. await Modules.promises.hooks.fire(
  144. 'removeDropbox',
  145. userId,
  146. 'duplicate-projects'
  147. )
  148. await NotificationsBuilder.promises
  149. .dropboxDuplicateProjectNames(userId)
  150. .create(projectName)
  151. }
  152. async function createFolder(userId, projectId, projectName, path) {
  153. const project = await getOrCreateProject(userId, projectId, projectName)
  154. if (project == null) {
  155. return null
  156. }
  157. const projectIsOnCooldown =
  158. await CooldownManager.promises.isProjectOnCooldown(project._id)
  159. if (projectIsOnCooldown) {
  160. throw new Errors.TooManyRequestsError('project on cooldown')
  161. }
  162. const shouldIgnore = FileTypeManager.shouldIgnore(path)
  163. if (shouldIgnore) {
  164. return null
  165. }
  166. const folder = await UpdateMerger.promises.createFolder(
  167. project._id,
  168. path,
  169. userId
  170. )
  171. return {
  172. folderId: folder._id,
  173. parentFolderId: folder.parentFolder_id,
  174. projectId: project._id,
  175. path,
  176. }
  177. }
  178. export default {
  179. newUpdate: callbackify(newUpdate),
  180. deleteUpdate: callbackify(deleteUpdate),
  181. createFolder: callbackify(createFolder),
  182. promises: {
  183. newUpdate,
  184. deleteUpdate,
  185. createFolder,
  186. },
  187. }