| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- import OError from '@overleaf/o-error'
- import metrics from '@overleaf/metrics'
- import Settings from '@overleaf/settings'
- import mongodb from 'mongodb-legacy'
- import Features from '../../infrastructure/Features.js'
- import { Project } from '../../models/Project.mjs'
- import { Folder } from '../../models/Folder.mjs'
- import ProjectEntityUpdateHandler from './ProjectEntityUpdateHandler.mjs'
- import ProjectDetailsHandler from './ProjectDetailsHandler.mjs'
- import HistoryManager from '../History/HistoryManager.mjs'
- import { User } from '../../models/User.mjs'
- import fs from 'node:fs'
- import path from 'node:path'
- import { callbackify } from 'node:util'
- import _ from 'lodash'
- import AnalyticsManager from '../Analytics/AnalyticsManager.mjs'
- import TpdsUpdateSender from '../ThirdPartyDataStore/TpdsUpdateSender.mjs'
- import SplitTestHandler from '../SplitTests/SplitTestHandler.mjs'
- const { ObjectId } = mongodb
- const MONTH_NAMES = [
- 'January',
- 'February',
- 'March',
- 'April',
- 'May',
- 'June',
- 'July',
- 'August',
- 'September',
- 'October',
- 'November',
- 'December',
- ]
- const templateProjectDir = Features.hasFeature('saas')
- ? 'example-project'
- : 'example-project-sp'
- async function createBlankProject(
- ownerId,
- projectName,
- attributes = {},
- options
- ) {
- const isImport = attributes && attributes.overleaf
- const project = await _createBlankProject(
- ownerId,
- projectName,
- attributes,
- options
- )
- const segmentation = _.pick(attributes, [
- 'fromV1TemplateId',
- 'fromV1TemplateVersionId',
- ])
- Object.assign(segmentation, attributes.segmentation)
- segmentation.projectId = project._id
- if (isImport) {
- AnalyticsManager.recordEventForUserInBackground(
- ownerId,
- 'project-imported',
- segmentation
- )
- } else {
- AnalyticsManager.recordEventForUserInBackground(
- ownerId,
- 'project-created',
- segmentation
- )
- }
- return project
- }
- async function createProjectFromSnippet(ownerId, projectName, docLines) {
- const project = await _createBlankProject(ownerId, projectName)
- AnalyticsManager.recordEventForUserInBackground(ownerId, 'project-created', {
- projectId: project._id,
- })
- await _createRootDoc(project, ownerId, docLines)
- return project
- }
- async function createBasicProject(ownerId, projectName) {
- const project = await _createBlankProject(ownerId, projectName)
- const docLines = await _buildTemplate('mainbasic.tex', ownerId, projectName)
- await _createRootDoc(project, ownerId, docLines)
- AnalyticsManager.recordEventForUserInBackground(ownerId, 'project-created', {
- projectId: project._id,
- })
- return project
- }
- async function createExampleProject(ownerId, projectName) {
- const project = await _createBlankProject(ownerId, projectName)
- await _addExampleProjectFiles(ownerId, projectName, project)
- AnalyticsManager.recordEventForUserInBackground(ownerId, 'project-created', {
- projectId: project._id,
- })
- return project
- }
- async function _addExampleProjectFiles(ownerId, projectName, project) {
- const mainDocLines = await _buildTemplate(
- `${templateProjectDir}/main.tex`,
- ownerId,
- projectName
- )
- await _createRootDoc(project, ownerId, mainDocLines)
- const bibDocLines = await _buildTemplate(
- `${templateProjectDir}/sample.bib`,
- ownerId,
- projectName
- )
- await ProjectEntityUpdateHandler.promises.addDoc(
- project._id,
- project.rootFolder[0]._id,
- 'sample.bib',
- bibDocLines,
- ownerId,
- null
- )
- const frogPath = path.join(
- import.meta.dirname,
- `/../../../templates/project_files/${templateProjectDir}/frog.jpg`
- )
- await ProjectEntityUpdateHandler.promises.addFile(
- project._id,
- project.rootFolder[0]._id,
- 'frog.jpg',
- frogPath,
- null,
- ownerId,
- null
- )
- }
- async function _createBlankProject(
- ownerId,
- projectName,
- attributes = {},
- { skipCreatingInTPDS = false } = {}
- ) {
- metrics.inc('project-creation')
- const timer = new metrics.Timer('project-creation')
- await ProjectDetailsHandler.promises.validateProjectName(projectName)
- const rootFolder = new Folder({ name: 'rootFolder' })
- attributes.lastUpdatedBy = attributes.owner_ref = new ObjectId(ownerId)
- attributes.name = projectName
- const project = new Project(attributes)
- // Initialise the history unless the caller has overridden it in the attributes
- // (to allow scripted creation of projects without full project history)
- if (project.overleaf.history.id == null && !attributes.overleaf) {
- const historyId = await HistoryManager.promises.initializeProject(
- project._id
- )
- if (historyId != null) {
- project.overleaf.history.id = historyId
- }
- }
- // All the projects are initialised with Full Project History. This property
- // is still set for backwards compatibility: Server Pro requires all projects
- // have it set to `true` since SP 4.0
- project.overleaf.history.display = true
- if (Settings.currentImageName) {
- // avoid clobbering any imageName already set in attributes (e.g. importedImageName)
- if (!project.imageName) {
- project.imageName = Settings.currentImageName
- }
- }
- project.rootFolder[0] = rootFolder
- const user = await User.findById(ownerId, {
- 'ace.spellCheckLanguage': 1,
- _id: 1,
- })
- project.spellCheckLanguage = user.ace.spellCheckLanguage
- const historyRangesSupportAssignment =
- await SplitTestHandler.promises.getAssignmentForUser(
- user._id,
- 'history-ranges-support'
- )
- if (historyRangesSupportAssignment.variant === 'enabled') {
- project.overleaf.history.rangesSupportEnabled = true
- }
- await project.save()
- if (!skipCreatingInTPDS) {
- await TpdsUpdateSender.promises.createProject({
- projectId: project._id,
- projectName,
- ownerId,
- userId: ownerId,
- })
- }
- timer.done()
- return project
- }
- async function _createRootDoc(project, ownerId, docLines) {
- try {
- const { doc } = await ProjectEntityUpdateHandler.promises.addDoc(
- project._id,
- project.rootFolder[0]._id,
- 'main.tex',
- docLines,
- ownerId,
- null
- )
- await ProjectEntityUpdateHandler.promises.setRootDoc(project._id, doc._id)
- } catch (error) {
- throw OError.tag(error, 'error adding root doc when creating project')
- }
- }
- async function _buildTemplate(templateName, userId, projectName) {
- const user = await User.findById(userId, 'first_name last_name')
- const templatePath = path.join(
- import.meta.dirname,
- `/../../../templates/project_files/${templateName}`
- )
- const template = fs.readFileSync(templatePath)
- const data = {
- project_name: projectName,
- user,
- year: new Date().getUTCFullYear(),
- month: MONTH_NAMES[new Date().getUTCMonth()],
- }
- const output = _.template(template.toString())(data)
- return output.split('\n')
- }
- export default {
- createBlankProject: callbackify(createBlankProject),
- createProjectFromSnippet: callbackify(createProjectFromSnippet),
- createBasicProject: callbackify(createBasicProject),
- createExampleProject: callbackify(createExampleProject),
- promises: {
- createBlankProject,
- createProjectFromSnippet,
- createBasicProject,
- createExampleProject,
- },
- }
|