ProjectCreationHandler.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. const logger = require('@overleaf/logger')
  2. const OError = require('@overleaf/o-error')
  3. const metrics = require('@overleaf/metrics')
  4. const Settings = require('@overleaf/settings')
  5. const { ObjectId } = require('mongodb')
  6. const Features = require('../../infrastructure/Features')
  7. const { Project } = require('../../models/Project')
  8. const { Folder } = require('../../models/Folder')
  9. const ProjectEntityUpdateHandler = require('./ProjectEntityUpdateHandler')
  10. const ProjectDetailsHandler = require('./ProjectDetailsHandler')
  11. const HistoryManager = require('../History/HistoryManager')
  12. const { User } = require('../../models/User')
  13. const fs = require('fs')
  14. const path = require('path')
  15. const { callbackify } = require('util')
  16. const _ = require('underscore')
  17. const AnalyticsManager = require('../Analytics/AnalyticsManager')
  18. const MONTH_NAMES = [
  19. 'January',
  20. 'February',
  21. 'March',
  22. 'April',
  23. 'May',
  24. 'June',
  25. 'July',
  26. 'August',
  27. 'September',
  28. 'October',
  29. 'November',
  30. 'December',
  31. ]
  32. const templateProjectDir = Features.hasFeature('saas')
  33. ? 'example-project'
  34. : 'example-project-sp'
  35. async function createBlankProject(ownerId, projectName, attributes = {}) {
  36. const isImport = attributes && attributes.overleaf
  37. const project = await _createBlankProject(ownerId, projectName, attributes)
  38. const segmentation = _.pick(attributes, [
  39. 'fromV1TemplateId',
  40. 'fromV1TemplateVersionId',
  41. ])
  42. Object.assign(segmentation, attributes.segmentation)
  43. segmentation.projectId = project._id
  44. if (isImport) {
  45. AnalyticsManager.recordEventForUser(
  46. ownerId,
  47. 'project-imported',
  48. segmentation
  49. )
  50. } else {
  51. AnalyticsManager.recordEventForUser(
  52. ownerId,
  53. 'project-created',
  54. segmentation
  55. )
  56. }
  57. return project
  58. }
  59. async function createProjectFromSnippet(ownerId, projectName, docLines) {
  60. const project = await _createBlankProject(ownerId, projectName)
  61. AnalyticsManager.recordEventForUser(ownerId, 'project-created', {
  62. projectId: project._id,
  63. })
  64. await _createRootDoc(project, ownerId, docLines)
  65. return project
  66. }
  67. async function createBasicProject(ownerId, projectName) {
  68. const project = await _createBlankProject(ownerId, projectName)
  69. const docLines = await _buildTemplate('mainbasic.tex', ownerId, projectName)
  70. await _createRootDoc(project, ownerId, docLines)
  71. AnalyticsManager.recordEventForUser(ownerId, 'project-created', {
  72. projectId: project._id,
  73. })
  74. return project
  75. }
  76. async function createExampleProject(ownerId, projectName) {
  77. const project = await _createBlankProject(ownerId, projectName)
  78. await _addExampleProjectFiles(ownerId, projectName, project)
  79. AnalyticsManager.recordEventForUser(ownerId, 'project-created', {
  80. projectId: project._id,
  81. })
  82. return project
  83. }
  84. async function _addExampleProjectFiles(ownerId, projectName, project) {
  85. const mainDocLines = await _buildTemplate(
  86. `${templateProjectDir}/main.tex`,
  87. ownerId,
  88. projectName
  89. )
  90. await _createRootDoc(project, ownerId, mainDocLines)
  91. const bibDocLines = await _buildTemplate(
  92. `${templateProjectDir}/sample.bib`,
  93. ownerId,
  94. projectName
  95. )
  96. await ProjectEntityUpdateHandler.promises.addDoc(
  97. project._id,
  98. project.rootFolder[0]._id,
  99. 'sample.bib',
  100. bibDocLines,
  101. ownerId,
  102. null
  103. )
  104. const frogPath = path.join(
  105. __dirname,
  106. `/../../../templates/project_files/${templateProjectDir}/frog.jpg`
  107. )
  108. await ProjectEntityUpdateHandler.promises.addFile(
  109. project._id,
  110. project.rootFolder[0]._id,
  111. 'frog.jpg',
  112. frogPath,
  113. null,
  114. ownerId,
  115. null
  116. )
  117. }
  118. async function _createBlankProject(ownerId, projectName, attributes = {}) {
  119. metrics.inc('project-creation')
  120. const timer = new metrics.Timer('project-creation')
  121. await ProjectDetailsHandler.promises.validateProjectName(projectName)
  122. if (!attributes.overleaf) {
  123. const history = await HistoryManager.promises.initializeProject()
  124. attributes.overleaf = {
  125. history: { id: history ? history.overleaf_id : undefined },
  126. }
  127. }
  128. const rootFolder = new Folder({ name: 'rootFolder' })
  129. attributes.lastUpdatedBy = attributes.owner_ref = new ObjectId(ownerId)
  130. attributes.name = projectName
  131. const project = new Project(attributes)
  132. Object.assign(project, attributes)
  133. // only display full project history when the project has the overleaf history id attribute
  134. // (to allow scripted creation of projects without full project history)
  135. const historyId = _.get(attributes, ['overleaf', 'history', 'id'])
  136. if (
  137. Features.hasFeature('history-v1') &&
  138. Settings.apis.project_history.displayHistoryForNewProjects &&
  139. historyId
  140. ) {
  141. project.overleaf.history.display = true
  142. }
  143. if (Settings.currentImageName) {
  144. // avoid clobbering any imageName already set in attributes (e.g. importedImageName)
  145. if (!project.imageName) {
  146. project.imageName = Settings.currentImageName
  147. }
  148. }
  149. project.rootFolder[0] = rootFolder
  150. const user = await User.findById(ownerId, 'ace.spellCheckLanguage')
  151. project.spellCheckLanguage = user.ace.spellCheckLanguage
  152. await project.save()
  153. timer.done()
  154. return project
  155. }
  156. async function _createRootDoc(project, ownerId, docLines) {
  157. try {
  158. const { doc } = await ProjectEntityUpdateHandler.promises.addDoc(
  159. project._id,
  160. project.rootFolder[0]._id,
  161. 'main.tex',
  162. docLines,
  163. ownerId,
  164. null
  165. )
  166. await ProjectEntityUpdateHandler.promises.setRootDoc(project._id, doc._id)
  167. } catch (error) {
  168. throw OError.tag(error, 'error adding root doc when creating project')
  169. }
  170. }
  171. async function _buildTemplate(templateName, userId, projectName) {
  172. const user = await User.findById(userId, 'first_name last_name')
  173. const templatePath = path.join(
  174. __dirname,
  175. `/../../../templates/project_files/${templateName}`
  176. )
  177. const template = fs.readFileSync(templatePath)
  178. const data = {
  179. project_name: projectName,
  180. user,
  181. year: new Date().getUTCFullYear(),
  182. month: MONTH_NAMES[new Date().getUTCMonth()],
  183. }
  184. const output = _.template(template.toString())(data)
  185. return output.split('\n')
  186. }
  187. module.exports = {
  188. createBlankProject: callbackify(createBlankProject),
  189. createProjectFromSnippet: callbackify(createProjectFromSnippet),
  190. createBasicProject: callbackify(createBasicProject),
  191. createExampleProject: callbackify(createExampleProject),
  192. promises: {
  193. createBlankProject,
  194. createProjectFromSnippet,
  195. createBasicProject,
  196. createExampleProject,
  197. },
  198. }
  199. metrics.timeAsyncMethod(
  200. module.exports,
  201. 'createBlankProject',
  202. 'mongo.ProjectCreationHandler',
  203. logger
  204. )