TpdsUpdateHandler.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  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. const {
  15. BackgroundTaskTracker,
  16. } = require('../../infrastructure/GracefulShutdown')
  17. const ROOT_DOC_TIMEOUT_LENGTH = 30 * 1000
  18. const rootDocResets = new BackgroundTaskTracker('root doc resets')
  19. async function newUpdate(userId, projectName, path, updateRequest, source) {
  20. const project = await getOrCreateProject(userId, projectName)
  21. if (project == null) {
  22. return
  23. }
  24. const projectIsOnCooldown =
  25. await CooldownManager.promises.isProjectOnCooldown(project._id)
  26. if (projectIsOnCooldown) {
  27. throw new Errors.TooManyRequestsError('project on cooldown')
  28. }
  29. const shouldIgnore = await FileTypeManager.promises.shouldIgnore(path)
  30. if (shouldIgnore) {
  31. return
  32. }
  33. await UpdateMerger.promises.mergeUpdate(
  34. userId,
  35. project._id,
  36. path,
  37. updateRequest,
  38. source
  39. )
  40. }
  41. async function deleteUpdate(userId, projectName, path, source) {
  42. logger.debug({ userId, filePath: path }, 'handling delete update from tpds')
  43. const projects = await ProjectGetter.promises.findUsersProjectsByName(
  44. userId,
  45. projectName
  46. )
  47. const activeProjects = projects.filter(
  48. project => !ProjectHelper.isArchivedOrTrashed(project, userId)
  49. )
  50. if (activeProjects.length === 0) {
  51. logger.debug(
  52. { userId, filePath: path, projectName },
  53. 'project not found from tpds update, ignoring folder or project'
  54. )
  55. return
  56. }
  57. if (projects.length > 1) {
  58. // There is more than one project with that name, and one of them is
  59. // active (previous condition)
  60. await handleDuplicateProjects(userId, projectName)
  61. return
  62. }
  63. const project = activeProjects[0]
  64. if (path === '/') {
  65. logger.debug(
  66. { userId, filePath: path, projectName, project_id: project._id },
  67. 'project found for delete update, path is root so marking project as deleted'
  68. )
  69. await ProjectDeleter.promises.markAsDeletedByExternalSource(project._id)
  70. } else {
  71. await UpdateMerger.promises.deleteUpdate(userId, project._id, path, source)
  72. }
  73. }
  74. async function getOrCreateProject(userId, projectName) {
  75. const projects = await ProjectGetter.promises.findUsersProjectsByName(
  76. userId,
  77. projectName
  78. )
  79. if (projects.length === 0) {
  80. // No project with that name -- active, archived or trashed -- has been
  81. // found. Create one.
  82. const project = await ProjectCreationHandler.promises.createBlankProject(
  83. userId,
  84. projectName
  85. )
  86. // have a crack at setting the root doc after a while, on creation
  87. // we won't have it yet, but should have been sent it it within 30
  88. // seconds
  89. rootDocResets.add()
  90. setTimeout(() => {
  91. ProjectRootDocManager.promises
  92. .setRootDocAutomatically(project._id)
  93. .then(() => {
  94. rootDocResets.done()
  95. })
  96. .catch(err => {
  97. logger.warn({ err }, 'failed to set root doc after project creation')
  98. })
  99. }, ROOT_DOC_TIMEOUT_LENGTH)
  100. return project
  101. }
  102. const activeProjects = projects.filter(
  103. project => !ProjectHelper.isArchivedOrTrashed(project, userId)
  104. )
  105. if (activeProjects.length === 0) {
  106. // All projects with that name are archived or trashed. Ignore.
  107. return null
  108. }
  109. if (projects.length > 1) {
  110. // There is more than one project with that name, and one of them is
  111. // active (previous condition)
  112. await handleDuplicateProjects(userId, projectName)
  113. return null
  114. }
  115. return activeProjects[0]
  116. }
  117. async function handleDuplicateProjects(userId, projectName) {
  118. await Modules.promises.hooks.fire(
  119. 'removeDropbox',
  120. userId,
  121. 'duplicate-projects'
  122. )
  123. await NotificationsBuilder.promises
  124. .dropboxDuplicateProjectNames(userId)
  125. .create(projectName)
  126. }
  127. module.exports = {
  128. newUpdate: callbackify(newUpdate),
  129. deleteUpdate: callbackify(deleteUpdate),
  130. promises: {
  131. newUpdate,
  132. deleteUpdate,
  133. },
  134. }