ProjectCreationHandler.js 6.7 KB

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