TemplatesManager.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. const { Project } = require('../../models/Project')
  2. const OError = require('@overleaf/o-error')
  3. const ProjectDetailsHandler = require('../Project/ProjectDetailsHandler')
  4. const ProjectOptionsHandler = require('../Project/ProjectOptionsHandler')
  5. const ProjectRootDocManager = require('../Project/ProjectRootDocManager')
  6. const ProjectUploadManager = require('../Uploads/ProjectUploadManager')
  7. const FileWriter = require('../../infrastructure/FileWriter')
  8. const async = require('async')
  9. const fs = require('fs')
  10. const util = require('util')
  11. const logger = require('@overleaf/logger')
  12. const { fetchJson, RequestFailedError } = require('@overleaf/fetch-utils')
  13. const request = require('request')
  14. const settings = require('@overleaf/settings')
  15. const crypto = require('crypto')
  16. const Errors = require('../Errors/Errors')
  17. const _ = require('underscore')
  18. const TemplatesManager = {
  19. createProjectFromV1Template(
  20. brandVariationId,
  21. compiler,
  22. mainFile,
  23. templateId,
  24. templateName,
  25. templateVersionId,
  26. userId,
  27. imageName,
  28. _callback
  29. ) {
  30. const callback = _.once(_callback)
  31. const zipUrl = `${settings.apis.v1.url}/api/v1/sharelatex/templates/${templateVersionId}`
  32. const zipReq = request(zipUrl, {
  33. auth: {
  34. user: settings.apis.v1.user,
  35. pass: settings.apis.v1.pass,
  36. },
  37. timeout: settings.apis.v1.timeout,
  38. })
  39. zipReq.on('error', function (err) {
  40. logger.warn({ err }, 'error getting zip from template API')
  41. return callback(err)
  42. })
  43. FileWriter.ensureDumpFolderExists(function (err) {
  44. if (err) {
  45. return callback(err)
  46. }
  47. const projectName = ProjectDetailsHandler.fixProjectName(templateName)
  48. const dumpPath = `${settings.path.dumpFolder}/${crypto.randomUUID()}`
  49. const writeStream = fs.createWriteStream(dumpPath)
  50. const attributes = {
  51. fromV1TemplateId: templateId,
  52. fromV1TemplateVersionId: templateVersionId,
  53. }
  54. writeStream.on('close', function () {
  55. if (zipReq.response.statusCode !== 200) {
  56. logger.warn(
  57. { uri: zipUrl, statusCode: zipReq.response.statusCode },
  58. 'non-success code getting zip from template API'
  59. )
  60. return callback(new Error('get zip failed'))
  61. }
  62. ProjectUploadManager.createProjectFromZipArchiveWithName(
  63. userId,
  64. projectName,
  65. dumpPath,
  66. attributes,
  67. function (err, project) {
  68. if (err) {
  69. OError.tag(err, 'problem building project from zip', {
  70. zipReq,
  71. })
  72. return callback(err)
  73. }
  74. async.series(
  75. [
  76. cb => TemplatesManager._setCompiler(project._id, compiler, cb),
  77. cb => TemplatesManager._setImage(project._id, imageName, cb),
  78. cb => TemplatesManager._setMainFile(project._id, mainFile, cb),
  79. cb =>
  80. TemplatesManager._setBrandVariationId(
  81. project._id,
  82. brandVariationId,
  83. cb
  84. ),
  85. ],
  86. function (err) {
  87. if (err) {
  88. return callback(err)
  89. }
  90. fs.unlink(dumpPath, function (err) {
  91. if (err) {
  92. return logger.err({ err }, 'error unlinking template zip')
  93. }
  94. })
  95. const update = {
  96. fromV1TemplateId: templateId,
  97. fromV1TemplateVersionId: templateVersionId,
  98. }
  99. Project.updateOne(
  100. { _id: project._id },
  101. update,
  102. {},
  103. function (err) {
  104. if (err) {
  105. return callback(err)
  106. }
  107. callback(null, project)
  108. }
  109. )
  110. }
  111. )
  112. }
  113. )
  114. })
  115. zipReq.pipe(writeStream)
  116. })
  117. },
  118. _setCompiler(projectId, compiler, callback) {
  119. if (compiler == null) {
  120. return callback()
  121. }
  122. ProjectOptionsHandler.setCompiler(projectId, compiler, callback)
  123. },
  124. _setImage(projectId, imageName, callback) {
  125. if (!imageName) {
  126. imageName = 'wl_texlive:2018.1'
  127. }
  128. ProjectOptionsHandler.setImageName(projectId, imageName, callback)
  129. },
  130. _setMainFile(projectId, mainFile, callback) {
  131. if (mainFile == null) {
  132. return callback()
  133. }
  134. ProjectRootDocManager.setRootDocFromName(projectId, mainFile, callback)
  135. },
  136. _setBrandVariationId(projectId, brandVariationId, callback) {
  137. if (brandVariationId == null) {
  138. return callback()
  139. }
  140. ProjectOptionsHandler.setBrandVariationId(
  141. projectId,
  142. brandVariationId,
  143. callback
  144. )
  145. },
  146. promises: {
  147. async fetchFromV1(templateId) {
  148. const url = new URL(
  149. `/api/v2/templates/${templateId}`,
  150. settings.apis.v1.url
  151. )
  152. try {
  153. return await fetchJson(url, {
  154. basicAuth: {
  155. user: settings.apis.v1.user,
  156. password: settings.apis.v1.pass,
  157. },
  158. signal: AbortSignal.timeout(settings.apis.v1.timeout),
  159. })
  160. } catch (err) {
  161. if (err instanceof RequestFailedError && err.response.status === 404) {
  162. throw new Errors.NotFoundError()
  163. } else {
  164. throw err
  165. }
  166. }
  167. },
  168. },
  169. }
  170. TemplatesManager.fetchFromV1 = util.callbackify(
  171. TemplatesManager.promises.fetchFromV1
  172. )
  173. module.exports = TemplatesManager