ProjectCreationHandler.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. const logger = require('logger-sharelatex')
  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 { Project } = require('../../models/Project')
  7. const { Folder } = require('../../models/Folder')
  8. const ProjectEntityUpdateHandler = require('./ProjectEntityUpdateHandler')
  9. const ProjectDetailsHandler = require('./ProjectDetailsHandler')
  10. const HistoryManager = require('../History/HistoryManager')
  11. const { User } = require('../../models/User')
  12. const fs = require('fs')
  13. const path = require('path')
  14. const { callbackify } = require('util')
  15. const _ = require('underscore')
  16. const AnalyticsManager = require('../Analytics/AnalyticsManager')
  17. const SplitTestHandler = require('../SplitTests/SplitTestHandler')
  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 EXAMPLE_PROJECT_SPLITTEST_ID = 'example-project-v2'
  33. async function createBlankProject(ownerId, projectName, attributes = {}) {
  34. const isImport = attributes && attributes.overleaf
  35. const project = await _createBlankProject(ownerId, projectName, attributes)
  36. if (isImport) {
  37. AnalyticsManager.recordEvent(ownerId, 'project-imported', {
  38. projectId: project._id,
  39. attributes,
  40. })
  41. } else {
  42. AnalyticsManager.recordEvent(ownerId, 'project-created', {
  43. projectId: project._id,
  44. attributes,
  45. })
  46. }
  47. return project
  48. }
  49. async function createProjectFromSnippet(ownerId, projectName, docLines) {
  50. const project = await _createBlankProject(ownerId, projectName)
  51. AnalyticsManager.recordEvent(ownerId, 'project-created', {
  52. projectId: project._id,
  53. })
  54. await _createRootDoc(project, ownerId, docLines)
  55. return project
  56. }
  57. async function createBasicProject(ownerId, projectName) {
  58. const project = await _createBlankProject(ownerId, projectName)
  59. AnalyticsManager.recordEvent(ownerId, 'project-created', {
  60. projectId: project._id,
  61. })
  62. const docLines = await _buildTemplate('mainbasic.tex', ownerId, projectName)
  63. await _createRootDoc(project, ownerId, docLines)
  64. return project
  65. }
  66. async function createExampleProject(ownerId, projectName) {
  67. const project = await _createBlankProject(ownerId, projectName)
  68. const testSegmentation = await SplitTestHandler.promises.getTestSegmentation(
  69. ownerId,
  70. EXAMPLE_PROJECT_SPLITTEST_ID
  71. )
  72. if (testSegmentation.variant === 'example-frog') {
  73. await _addSplitTestExampleProjectFiles(ownerId, projectName, project)
  74. } else {
  75. await _addDefaultExampleProjectFiles(ownerId, projectName, project)
  76. }
  77. if (testSegmentation.enabled) {
  78. AnalyticsManager.recordEvent(ownerId, 'project-created', {
  79. projectId: project._id,
  80. splitTestId: EXAMPLE_PROJECT_SPLITTEST_ID,
  81. splitTestVariantId: testSegmentation.variant,
  82. })
  83. } else {
  84. AnalyticsManager.recordEvent(ownerId, 'project-created', {
  85. projectId: project._id,
  86. })
  87. }
  88. return project
  89. }
  90. async function _addDefaultExampleProjectFiles(ownerId, projectName, project) {
  91. const mainDocLines = await _buildTemplate('main.tex', ownerId, projectName)
  92. await _createRootDoc(project, ownerId, mainDocLines)
  93. const referenceDocLines = await _buildTemplate(
  94. 'references.bib',
  95. ownerId,
  96. projectName
  97. )
  98. await ProjectEntityUpdateHandler.promises.addDoc(
  99. project._id,
  100. project.rootFolder[0]._id,
  101. 'references.bib',
  102. referenceDocLines,
  103. ownerId
  104. )
  105. const universePath = path.resolve(
  106. __dirname + '/../../../templates/project_files/universe.jpg'
  107. )
  108. await ProjectEntityUpdateHandler.promises.addFile(
  109. project._id,
  110. project.rootFolder[0]._id,
  111. 'universe.jpg',
  112. universePath,
  113. null,
  114. ownerId
  115. )
  116. }
  117. async function _addSplitTestExampleProjectFiles(ownerId, projectName, project) {
  118. const mainDocLines = await _buildTemplate(
  119. 'test-example-project/main.tex',
  120. ownerId,
  121. projectName
  122. )
  123. await _createRootDoc(project, ownerId, mainDocLines)
  124. const bibDocLines = await _buildTemplate(
  125. 'test-example-project/sample.bib',
  126. ownerId,
  127. projectName
  128. )
  129. await ProjectEntityUpdateHandler.promises.addDoc(
  130. project._id,
  131. project.rootFolder[0]._id,
  132. 'sample.bib',
  133. bibDocLines,
  134. ownerId
  135. )
  136. const frogPath = path.resolve(
  137. __dirname +
  138. '/../../../templates/project_files/test-example-project/frog.jpg'
  139. )
  140. await ProjectEntityUpdateHandler.promises.addFile(
  141. project._id,
  142. project.rootFolder[0]._id,
  143. 'frog.jpg',
  144. frogPath,
  145. null,
  146. ownerId
  147. )
  148. }
  149. async function _createBlankProject(ownerId, projectName, attributes = {}) {
  150. metrics.inc('project-creation')
  151. await ProjectDetailsHandler.promises.validateProjectName(projectName)
  152. if (!attributes.overleaf) {
  153. const history = await HistoryManager.promises.initializeProject()
  154. attributes.overleaf = {
  155. history: { id: history ? history.overleaf_id : undefined },
  156. }
  157. }
  158. const rootFolder = new Folder({ name: 'rootFolder' })
  159. attributes.lastUpdatedBy = attributes.owner_ref = new ObjectId(ownerId)
  160. attributes.name = projectName
  161. const project = new Project(attributes)
  162. Object.assign(project, attributes)
  163. // only display full project history when the project has the overleaf history id attribute
  164. // (to allow scripted creation of projects without full project history)
  165. const historyId = _.get(attributes, ['overleaf', 'history', 'id'])
  166. if (Settings.apis.project_history.displayHistoryForNewProjects && historyId) {
  167. project.overleaf.history.display = true
  168. }
  169. if (Settings.currentImageName) {
  170. // avoid clobbering any imageName already set in attributes (e.g. importedImageName)
  171. if (!project.imageName) {
  172. project.imageName = Settings.currentImageName
  173. }
  174. }
  175. project.rootFolder[0] = rootFolder
  176. const user = await User.findById(ownerId, 'ace.spellCheckLanguage')
  177. project.spellCheckLanguage = user.ace.spellCheckLanguage
  178. return await project.save()
  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. )
  189. await ProjectEntityUpdateHandler.promises.setRootDoc(project._id, doc._id)
  190. } catch (error) {
  191. throw OError.tag(error, 'error adding root doc when creating project')
  192. }
  193. }
  194. async function _buildTemplate(templateName, userId, projectName) {
  195. const user = await User.findById(userId, 'first_name last_name')
  196. const templatePath = path.resolve(
  197. __dirname + `/../../../templates/project_files/${templateName}`
  198. )
  199. const template = fs.readFileSync(templatePath)
  200. const data = {
  201. project_name: projectName,
  202. user,
  203. year: new Date().getUTCFullYear(),
  204. month: MONTH_NAMES[new Date().getUTCMonth()],
  205. }
  206. const output = _.template(template.toString())(data)
  207. return output.split('\n')
  208. }
  209. module.exports = {
  210. createBlankProject: callbackify(createBlankProject),
  211. createProjectFromSnippet: callbackify(createProjectFromSnippet),
  212. createBasicProject: callbackify(createBasicProject),
  213. createExampleProject: callbackify(createExampleProject),
  214. promises: {
  215. createBlankProject,
  216. createProjectFromSnippet,
  217. createBasicProject,
  218. createExampleProject,
  219. },
  220. }
  221. metrics.timeAsyncMethod(
  222. module.exports,
  223. 'createBlankProject',
  224. 'mongo.ProjectCreationHandler',
  225. logger
  226. )