TpdsUpdateHandler.mjs 5.7 KB

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