TpdsController.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. const { expressify } = require('../../util/promises')
  2. const TpdsUpdateHandler = require('./TpdsUpdateHandler')
  3. const UpdateMerger = require('./UpdateMerger')
  4. const Errors = require('../Errors/Errors')
  5. const logger = require('@overleaf/logger')
  6. const Path = require('path')
  7. const metrics = require('@overleaf/metrics')
  8. const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
  9. const SessionManager = require('../Authentication/SessionManager')
  10. const HttpErrorHandler = require('../Errors/HttpErrorHandler')
  11. const TpdsQueueManager = require('./TpdsQueueManager')
  12. // mergeUpdate and deleteUpdate are used by Dropbox, where the project is only
  13. // passed as the name, as the first part of the file path. They have to check
  14. // the project exists, find it, and create it if not. They also ignore 'noisy'
  15. // files like .DS_Store, .gitignore, etc.
  16. async function mergeUpdate(req, res) {
  17. metrics.inc('tpds.merge-update')
  18. const { filePath, userId, projectName } = parseParams(req)
  19. const source = req.headers['x-sl-update-source'] || 'unknown'
  20. let metadata
  21. try {
  22. metadata = await TpdsUpdateHandler.promises.newUpdate(
  23. userId,
  24. projectName,
  25. filePath,
  26. req,
  27. source
  28. )
  29. } catch (err) {
  30. if (err.name === 'TooManyRequestsError') {
  31. logger.warn(
  32. { err, userId, filePath },
  33. 'tpds update failed to be processed, too many requests'
  34. )
  35. return res.sendStatus(429)
  36. } else if (err.message === 'project_has_too_many_files') {
  37. logger.warn(
  38. { err, userId, filePath },
  39. 'tpds trying to append to project over file limit'
  40. )
  41. NotificationsBuilder.tpdsFileLimit(userId).create(projectName)
  42. return res.sendStatus(400)
  43. } else {
  44. throw err
  45. }
  46. }
  47. if (metadata == null) {
  48. return res.json({ status: 'rejected' })
  49. }
  50. const payload = {
  51. status: 'applied',
  52. entityId: metadata.entityId.toString(),
  53. entityType: metadata.entityType,
  54. folderId: metadata.folderId.toString(),
  55. }
  56. // When the update is a doc edit, the update is merged in docupdater and
  57. // doesn't generate a new rev.
  58. if (metadata.rev != null) {
  59. payload.rev = metadata.rev.toString()
  60. }
  61. res.json(payload)
  62. }
  63. async function deleteUpdate(req, res) {
  64. metrics.inc('tpds.delete-update')
  65. const { filePath, userId, projectName } = parseParams(req)
  66. const source = req.headers['x-sl-update-source'] || 'unknown'
  67. await TpdsUpdateHandler.promises.deleteUpdate(
  68. userId,
  69. projectName,
  70. filePath,
  71. source
  72. )
  73. res.sendStatus(200)
  74. }
  75. /**
  76. * Update endpoint that accepts update details as JSON
  77. */
  78. async function updateFolder(req, res) {
  79. const userId = req.body.userId
  80. const { projectName, filePath } = splitPath(req.body.path)
  81. const metadata = await TpdsUpdateHandler.promises.createFolder(
  82. userId,
  83. projectName,
  84. filePath
  85. )
  86. if (metadata == null) {
  87. return HttpErrorHandler.conflict(req, res, 'Could not create folder', {
  88. userId,
  89. projectName,
  90. filePath,
  91. })
  92. }
  93. res.json({
  94. entityId: metadata.folderId.toString(),
  95. projectId: metadata.projectId.toString(),
  96. path: metadata.path,
  97. folderId: metadata.parentFolderId.toString(),
  98. })
  99. }
  100. // updateProjectContents and deleteProjectContents are used by GitHub. The
  101. // project_id is known so we can skip right ahead to creating/updating/deleting
  102. // the file. These methods will not ignore noisy files like .DS_Store,
  103. // .gitignore, etc because people are generally more explicit with the files
  104. // they want in git.
  105. async function updateProjectContents(req, res, next) {
  106. const projectId = req.params.project_id
  107. const path = `/${req.params[0]}` // UpdateMerger expects leading slash
  108. const source = req.headers['x-sl-update-source'] || 'unknown'
  109. try {
  110. await UpdateMerger.promises.mergeUpdate(null, projectId, path, req, source)
  111. } catch (error) {
  112. if (error.constructor === Errors.InvalidNameError) {
  113. return res.sendStatus(422)
  114. } else {
  115. throw error
  116. }
  117. }
  118. res.sendStatus(200)
  119. }
  120. async function deleteProjectContents(req, res, next) {
  121. const projectId = req.params.project_id
  122. const path = `/${req.params[0]}` // UpdateMerger expects leading slash
  123. const source = req.headers['x-sl-update-source'] || 'unknown'
  124. await UpdateMerger.promises.deleteUpdate(null, projectId, path, source)
  125. res.sendStatus(200)
  126. }
  127. async function getQueues(req, res, next) {
  128. const userId = SessionManager.getLoggedInUserId(req.session)
  129. res.json(await TpdsQueueManager.promises.getQueues(userId))
  130. }
  131. function parseParams(req) {
  132. const userId = req.params.user_id
  133. const { projectName, filePath } = splitPath(req.params[0])
  134. return { filePath, userId, projectName }
  135. }
  136. function splitPath(path) {
  137. let filePath, projectName
  138. path = Path.join('/', path)
  139. if (path.substring(1).indexOf('/') === -1) {
  140. filePath = '/'
  141. projectName = path.substring(1)
  142. } else {
  143. filePath = path.substring(path.indexOf('/', 1))
  144. projectName = path.substring(0, path.indexOf('/', 1))
  145. projectName = projectName.replace('/', '')
  146. }
  147. return { projectName, filePath }
  148. }
  149. module.exports = {
  150. mergeUpdate: expressify(mergeUpdate),
  151. deleteUpdate: expressify(deleteUpdate),
  152. updateFolder: expressify(updateFolder),
  153. updateProjectContents: expressify(updateProjectContents),
  154. deleteProjectContents: expressify(deleteProjectContents),
  155. getQueues: expressify(getQueues),
  156. // for tests only
  157. parseParams,
  158. }