ConversionController.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import logger from '@overleaf/logger'
  2. import { expressify } from '@overleaf/promise-utils'
  3. import fs from 'node:fs/promises'
  4. import fsSync from 'node:fs'
  5. import ConversionManager from './ConversionManager.js'
  6. import ResourceWriter from './ResourceWriter.js'
  7. import RequestParser from './RequestParser.js'
  8. import { pipeline } from 'node:stream/promises'
  9. import Settings from '@overleaf/settings'
  10. import Path from 'node:path'
  11. const SUPPORTED_CONVERSION_TYPES = new Map([['docx', 'docx']])
  12. async function convertDocxToLaTeX(req, res) {
  13. const { path } = req.file
  14. if (!Settings.enablePandocConversions) {
  15. await fs.unlink(path).catch(() => {})
  16. return res.sendStatus(404)
  17. }
  18. logger.debug({ path }, 'received file for conversion')
  19. const conversionId = crypto.randomUUID()
  20. let zipPath
  21. try {
  22. zipPath = await ConversionManager.promises.convertDocxToLaTeXWithLock(
  23. conversionId,
  24. path
  25. )
  26. } finally {
  27. await fs.unlink(path).catch(() => {})
  28. }
  29. try {
  30. const zipStat = await fs.stat(zipPath)
  31. res.setHeader('Content-Length', zipStat.size)
  32. res.attachment('conversion.zip')
  33. res.setHeader('X-Content-Type-Options', 'nosniff')
  34. const readStream = fsSync.createReadStream(zipPath)
  35. await pipeline(readStream, res)
  36. } finally {
  37. await fs
  38. .rm(Path.dirname(zipPath), { recursive: true, force: true })
  39. .catch(() => {})
  40. }
  41. }
  42. async function convertProjectToDocument(req, res) {
  43. if (!Settings.enablePandocConversions) {
  44. return res.sendStatus(404)
  45. }
  46. const type = req.query.type
  47. const extension = SUPPORTED_CONVERSION_TYPES.get(type)
  48. if (!extension) {
  49. return res.sendStatus(400)
  50. }
  51. const request = await RequestParser.promises.parse(req.body)
  52. request.project_id = req.params.project_id
  53. request.user_id = req.params.user_id
  54. request.metricsOpts = {}
  55. const conversionId = crypto.randomUUID()
  56. const conversionDir = Path.join(Settings.path.compilesDir, conversionId)
  57. logger.debug(
  58. {
  59. projectId: request.project_id,
  60. userId: request.user_id,
  61. rootResourcePath: request.rootResourcePath,
  62. type,
  63. },
  64. 'syncing resources for project-to-document conversion'
  65. )
  66. try {
  67. await ResourceWriter.promises.syncResourcesToDisk(request, conversionDir)
  68. const documentPath =
  69. await ConversionManager.promises.convertLaTeXToDocumentInDirWithLock(
  70. conversionId,
  71. conversionDir,
  72. request.rootResourcePath,
  73. type,
  74. extension
  75. )
  76. const documentStat = await fs.stat(documentPath)
  77. res.setHeader('Content-Length', documentStat.size)
  78. res.attachment(`output.${extension}`)
  79. res.setHeader('X-Content-Type-Options', 'nosniff')
  80. const readStream = fsSync.createReadStream(documentPath)
  81. await pipeline(readStream, res)
  82. } finally {
  83. await fs.rm(conversionDir, { recursive: true, force: true }).catch(() => {})
  84. }
  85. }
  86. export default {
  87. convertDocxToLaTeX: expressify(convertDocxToLaTeX),
  88. convertProjectToDocument: expressify(convertProjectToDocument),
  89. }