TpdsUpdateSender.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. const { ObjectId } = require('../../infrastructure/mongojs')
  2. const _ = require('lodash')
  3. const { callbackify } = require('util')
  4. const logger = require('logger-sharelatex')
  5. const metrics = require('metrics-sharelatex')
  6. const path = require('path')
  7. const request = require('request-promise-native')
  8. const settings = require('settings-sharelatex')
  9. const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
  10. .promises
  11. const UserGetter = require('../User/UserGetter.js').promises
  12. const tpdsUrl = _.get(settings, ['apis', 'thirdPartyDataStore', 'url'])
  13. async function addDoc(options) {
  14. metrics.inc('tpds.add-doc')
  15. options.streamOrigin =
  16. settings.apis.docstore.pubUrl +
  17. path.join(
  18. `/project/${options.project_id}`,
  19. `/doc/${options.doc_id}`,
  20. '/raw'
  21. )
  22. return addEntity(options)
  23. }
  24. async function addEntity(options) {
  25. const projectUserIds = await getProjectUsersIds(options.project_id)
  26. for (const userId of projectUserIds) {
  27. const job = {
  28. method: 'post',
  29. headers: {
  30. sl_entity_rev: options.rev,
  31. sl_project_id: options.project_id,
  32. sl_all_user_ids: JSON.stringify([userId])
  33. },
  34. uri: buildTpdsUrl(userId, options.project_name, options.path),
  35. title: 'addFile',
  36. streamOrigin: options.streamOrigin
  37. }
  38. await enqueue(userId, 'pipeStreamFrom', job)
  39. }
  40. }
  41. async function addFile(options) {
  42. metrics.inc('tpds.add-file')
  43. options.streamOrigin =
  44. settings.apis.filestore.url +
  45. path.join(`/project/${options.project_id}`, `/file/${options.file_id}`)
  46. return addEntity(options)
  47. }
  48. function buildMovePaths(options) {
  49. if (options.newProjectName) {
  50. return {
  51. startPath: path.join('/', options.project_name, '/'),
  52. endPath: path.join('/', options.newProjectName, '/')
  53. }
  54. } else {
  55. return {
  56. startPath: path.join('/', options.project_name, '/', options.startPath),
  57. endPath: path.join('/', options.project_name, '/', options.endPath)
  58. }
  59. }
  60. }
  61. function buildTpdsUrl(userId, projectName, filePath) {
  62. const projectPath = encodeURIComponent(path.join(projectName, '/', filePath))
  63. return `${tpdsUrl}/user/${userId}/entity/${projectPath}`
  64. }
  65. async function deleteEntity(options) {
  66. metrics.inc('tpds.delete-entity')
  67. const projectUserIds = await getProjectUsersIds(options.project_id)
  68. for (const userId of projectUserIds) {
  69. const job = {
  70. method: 'delete',
  71. headers: {
  72. sl_project_id: options.project_id,
  73. sl_all_user_ids: JSON.stringify([userId])
  74. },
  75. uri: buildTpdsUrl(userId, options.project_name, options.path),
  76. title: 'deleteEntity',
  77. sl_all_user_ids: JSON.stringify([userId])
  78. }
  79. await enqueue(userId, 'standardHttpRequest', job)
  80. }
  81. }
  82. async function enqueue(group, method, job) {
  83. const tpdsWorkerUrl = _.get(settings, ['apis', 'tpdsworker', 'url'])
  84. // silently do nothing if worker url is not in settings
  85. if (!tpdsWorkerUrl) {
  86. return
  87. }
  88. try {
  89. const response = await request({
  90. uri: `${tpdsWorkerUrl}/enqueue/web_to_tpds_http_requests`,
  91. json: { group, job, method },
  92. method: 'post',
  93. timeout: 5 * 1000
  94. })
  95. return response
  96. } catch (err) {
  97. // log error and continue
  98. logger.error({ err, group, job, method }, 'error enqueueing tpdsworker job')
  99. }
  100. }
  101. async function getProjectUsersIds(projectId) {
  102. // get list of all user ids with access to project. project owner
  103. // will always be the first entry in the list.
  104. const projectUserIds = await CollaboratorsGetter.getInvitedMemberIds(
  105. projectId
  106. )
  107. // filter list to only return users with dropbox linked
  108. // skip the first user id, which is always returned
  109. const dropboxUsers = await UserGetter.getUsers(
  110. {
  111. _id: { $in: projectUserIds.slice(1).map(id => ObjectId(id)) },
  112. 'dropbox.access_token.uid': { $ne: null }
  113. },
  114. {
  115. _id: 1
  116. }
  117. )
  118. const dropboxUserIds = dropboxUsers.map(user => user._id)
  119. return [projectUserIds[0], ...dropboxUserIds]
  120. }
  121. async function moveEntity(options) {
  122. metrics.inc('tpds.move-entity')
  123. const projectUserIds = await getProjectUsersIds(options.project_id)
  124. const { endPath, startPath } = buildMovePaths(options)
  125. for (const userId of projectUserIds) {
  126. const job = {
  127. method: 'put',
  128. title: 'moveEntity',
  129. uri: `${tpdsUrl}/user/${userId}/entity`,
  130. headers: {
  131. sl_project_id: options.project_id,
  132. sl_entity_rev: options.rev,
  133. sl_all_user_ids: JSON.stringify([userId])
  134. },
  135. json: {
  136. user_id: userId,
  137. endPath,
  138. startPath
  139. }
  140. }
  141. await enqueue(userId, 'standardHttpRequest', job)
  142. }
  143. }
  144. async function pollDropboxForUser(userId) {
  145. metrics.inc('tpds.poll-dropbox')
  146. const job = {
  147. method: 'post',
  148. uri: `${tpdsUrl}/user/poll`,
  149. json: {
  150. user_ids: [userId]
  151. }
  152. }
  153. return enqueue(`poll-dropbox:${userId}`, 'standardHttpRequest', job)
  154. }
  155. const TpdsUpdateSender = {
  156. addDoc: callbackify(addDoc),
  157. addEntity: callbackify(addEntity),
  158. addFile: callbackify(addFile),
  159. deleteEntity: callbackify(deleteEntity),
  160. enqueue: callbackify(enqueue),
  161. moveEntity: callbackify(moveEntity),
  162. pollDropboxForUser: callbackify(pollDropboxForUser),
  163. promises: {
  164. addDoc,
  165. addEntity,
  166. addFile,
  167. deleteEntity,
  168. enqueue,
  169. moveEntity,
  170. pollDropboxForUser
  171. }
  172. }
  173. module.exports = TpdsUpdateSender