ProjectCreationHandler.js 6.2 KB

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