LinkedFilesController.mjs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. /* eslint-disable
  2. max-len,
  3. */
  4. // TODO: This file was created by bulk-decaffeinate.
  5. // Fix any style issues and re-enable lint.
  6. /*
  7. * decaffeinate suggestions:
  8. * DS101: Remove unnecessary use of Array.from
  9. * DS102: Remove unnecessary code created because of implicit returns
  10. * DS207: Consider shorter variations of null checks
  11. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  12. */
  13. import SessionManager from '../Authentication/SessionManager.js'
  14. import Settings from '@overleaf/settings'
  15. import _ from 'lodash'
  16. import AnalyticsManager from '../../../../app/src/Features/Analytics/AnalyticsManager.js'
  17. import LinkedFilesHandler from './LinkedFilesHandler.js'
  18. import {
  19. CompileFailedError,
  20. UrlFetchFailedError,
  21. InvalidUrlError,
  22. AccessDeniedError,
  23. BadEntityTypeError,
  24. BadDataError,
  25. ProjectNotFoundError,
  26. V1ProjectNotFoundError,
  27. SourceFileNotFoundError,
  28. NotOriginalImporterError,
  29. FeatureNotAvailableError,
  30. RemoteServiceError,
  31. FileCannotRefreshError,
  32. } from './LinkedFilesErrors.js'
  33. import {
  34. OutputFileFetchFailedError,
  35. FileTooLargeError,
  36. OError,
  37. } from '../Errors/Errors.js'
  38. import Modules from '../../infrastructure/Modules.js'
  39. import { plainTextResponse } from '../../infrastructure/Response.js'
  40. import ReferencesHandler from '../References/ReferencesHandler.mjs'
  41. import EditorRealTimeController from '../Editor/EditorRealTimeController.js'
  42. import { expressify } from '@overleaf/promise-utils'
  43. import ProjectOutputFileAgent from './ProjectOutputFileAgent.mjs'
  44. import ProjectFileAgent from './ProjectFileAgent.js'
  45. import UrlAgent from './UrlAgent.mjs'
  46. let LinkedFilesController
  47. async function createLinkedFile(req, res, next) {
  48. const { project_id: projectId } = req.params
  49. const { name, provider, data, parent_folder_id: parentFolderId } = req.body
  50. const userId = SessionManager.getLoggedInUserId(req.session)
  51. const Agent = await LinkedFilesController._getAgent(provider)
  52. if (Agent == null) {
  53. return res.sendStatus(400)
  54. }
  55. data.provider = provider
  56. data.importedAt = new Date().toISOString()
  57. try {
  58. const newFileId = await Agent.promises.createLinkedFile(
  59. projectId,
  60. data,
  61. name,
  62. parentFolderId,
  63. userId
  64. )
  65. if (name.endsWith('.bib')) {
  66. AnalyticsManager.recordEventForUserInBackground(
  67. userId,
  68. 'linked-bib-file',
  69. {
  70. integration: provider,
  71. }
  72. )
  73. }
  74. return res.json({ new_file_id: newFileId })
  75. } catch (err) {
  76. return LinkedFilesController.handleError(err, req, res, next)
  77. }
  78. }
  79. async function refreshLinkedFile(req, res, next) {
  80. const { project_id: projectId, file_id: fileId } = req.params
  81. const { clientId } = req.body
  82. const userId = SessionManager.getLoggedInUserId(req.session)
  83. const { file, parentFolder } = await LinkedFilesHandler.promises.getFileById(
  84. projectId,
  85. fileId
  86. )
  87. if (file == null) {
  88. return res.sendStatus(404)
  89. }
  90. const { name } = file
  91. const { linkedFileData } = file
  92. if (
  93. linkedFileData == null ||
  94. (linkedFileData != null ? linkedFileData.provider : undefined) == null
  95. ) {
  96. return res.sendStatus(409)
  97. }
  98. const { provider } = linkedFileData
  99. const parentFolderId = parentFolder._id
  100. const Agent = await LinkedFilesController._getAgent(provider)
  101. if (Agent == null) {
  102. return res.sendStatus(400)
  103. }
  104. linkedFileData.importedAt = new Date().toISOString()
  105. let newFileId
  106. try {
  107. newFileId = await Agent.promises.refreshLinkedFile(
  108. projectId,
  109. linkedFileData,
  110. name,
  111. parentFolderId,
  112. userId
  113. )
  114. } catch (err) {
  115. return LinkedFilesController.handleError(err, req, res, next)
  116. }
  117. if (req.body.shouldReindexReferences) {
  118. let data
  119. try {
  120. data = await ReferencesHandler.promises.indexAll(projectId)
  121. } catch (error) {
  122. OError.tag(error, 'failed to index references', {
  123. projectId,
  124. })
  125. return next(error)
  126. }
  127. EditorRealTimeController.emitToRoom(
  128. projectId,
  129. 'references:keys:updated',
  130. data.keys,
  131. true,
  132. clientId
  133. )
  134. res.json({ new_file_id: newFileId })
  135. } else {
  136. res.json({ new_file_id: newFileId })
  137. }
  138. }
  139. export default LinkedFilesController = {
  140. Agents: null,
  141. async _cacheAgents() {
  142. if (!LinkedFilesController.Agents) {
  143. LinkedFilesController.Agents = _.extend(
  144. {
  145. url: UrlAgent,
  146. project_file: ProjectFileAgent,
  147. project_output_file: ProjectOutputFileAgent,
  148. },
  149. await Modules.linkedFileAgentsIncludes()
  150. )
  151. }
  152. },
  153. async _getAgent(provider) {
  154. await LinkedFilesController._cacheAgents()
  155. if (
  156. !Object.prototype.hasOwnProperty.call(
  157. LinkedFilesController.Agents,
  158. provider
  159. )
  160. ) {
  161. return null
  162. }
  163. if (!Array.from(Settings.enabledLinkedFileTypes).includes(provider)) {
  164. return null
  165. }
  166. return LinkedFilesController.Agents[provider]
  167. },
  168. createLinkedFile: expressify(createLinkedFile),
  169. refreshLinkedFile: expressify(refreshLinkedFile),
  170. handleError(error, req, res, next) {
  171. if (error instanceof AccessDeniedError) {
  172. res.status(403)
  173. plainTextResponse(
  174. res,
  175. res.locals.translate(
  176. 'the_project_that_contains_this_file_is_not_shared_with_you'
  177. )
  178. )
  179. } else if (error instanceof BadDataError) {
  180. res.status(400)
  181. plainTextResponse(res, 'The submitted data is not valid')
  182. } else if (error instanceof BadEntityTypeError) {
  183. res.status(400)
  184. plainTextResponse(res, 'The file is the wrong type')
  185. } else if (error instanceof SourceFileNotFoundError) {
  186. res.status(404)
  187. plainTextResponse(res, 'Source file not found')
  188. } else if (error instanceof ProjectNotFoundError) {
  189. res.status(404)
  190. plainTextResponse(res, 'Project not found')
  191. } else if (error instanceof V1ProjectNotFoundError) {
  192. res.status(409)
  193. plainTextResponse(
  194. res,
  195. 'Sorry, the source project is not yet imported to Overleaf v2. Please import it to Overleaf v2 to refresh this file'
  196. )
  197. } else if (error instanceof CompileFailedError) {
  198. res.status(422)
  199. plainTextResponse(
  200. res,
  201. res.locals.translate('generic_linked_file_compile_error')
  202. )
  203. } else if (error instanceof OutputFileFetchFailedError) {
  204. res.status(404)
  205. plainTextResponse(res, 'Could not get output file')
  206. } else if (error instanceof UrlFetchFailedError) {
  207. res.status(422)
  208. if (error.cause instanceof FileTooLargeError) {
  209. plainTextResponse(res, 'File too large')
  210. } else {
  211. plainTextResponse(
  212. res,
  213. `Your URL could not be reached (${
  214. error.info?.status || error.cause?.info?.status
  215. } status code). Please check it and try again.`
  216. )
  217. }
  218. } else if (error instanceof InvalidUrlError) {
  219. res.status(422)
  220. plainTextResponse(
  221. res,
  222. 'Your URL is not valid. Please check it and try again.'
  223. )
  224. } else if (error instanceof NotOriginalImporterError) {
  225. res.status(400)
  226. plainTextResponse(
  227. res,
  228. 'You are not the user who originally imported this file'
  229. )
  230. } else if (error instanceof FeatureNotAvailableError) {
  231. res.status(400)
  232. plainTextResponse(res, 'This feature is not enabled on your account')
  233. } else if (error instanceof RemoteServiceError) {
  234. if (error.info?.statusCode === 403) {
  235. res.status(400).json({ relink: true })
  236. } else {
  237. res.status(502)
  238. plainTextResponse(res, 'The remote service produced an error')
  239. }
  240. } else if (error instanceof FileCannotRefreshError) {
  241. res.status(400)
  242. plainTextResponse(res, 'This file cannot be refreshed')
  243. } else if (error.message === 'project_has_too_many_files') {
  244. res.status(400)
  245. plainTextResponse(res, 'too many files')
  246. } else if (/\bECONNREFUSED\b/.test(error.message)) {
  247. res.status(500)
  248. plainTextResponse(res, 'Importing references is not currently available')
  249. } else if (error instanceof FileTooLargeError) {
  250. res.status(422)
  251. plainTextResponse(res, 'File too large')
  252. } else {
  253. next(error)
  254. }
  255. },
  256. }