TpdsUpdateHandler.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. const { callbackify } = require('util')
  2. const UpdateMerger = require('./UpdateMerger')
  3. const logger = require('@overleaf/logger')
  4. const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
  5. const ProjectCreationHandler = require('../Project/ProjectCreationHandler')
  6. const ProjectDeleter = require('../Project/ProjectDeleter')
  7. const ProjectGetter = require('../Project/ProjectGetter')
  8. const ProjectHelper = require('../Project/ProjectHelper')
  9. const ProjectRootDocManager = require('../Project/ProjectRootDocManager')
  10. const FileTypeManager = require('../Uploads/FileTypeManager')
  11. const CooldownManager = require('../Cooldown/CooldownManager')
  12. const Errors = require('../Errors/Errors')
  13. const Modules = require('../../infrastructure/Modules')
  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 = await FileTypeManager.promises.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 = await FileTypeManager.promises.shouldIgnore(path)
  159. if (shouldIgnore) {
  160. return null
  161. }
  162. const folder = await UpdateMerger.promises.createFolder(project._id, path)
  163. return {
  164. folderId: folder._id,
  165. parentFolderId: folder.parentFolder_id,
  166. projectId: project._id,
  167. path,
  168. }
  169. }
  170. module.exports = {
  171. newUpdate: callbackify(newUpdate),
  172. deleteUpdate: callbackify(deleteUpdate),
  173. createFolder: callbackify(createFolder),
  174. promises: {
  175. newUpdate,
  176. deleteUpdate,
  177. createFolder,
  178. },
  179. }