const logger = require('@overleaf/logger') const OError = require('@overleaf/o-error') const metrics = require('@overleaf/metrics') const Settings = require('@overleaf/settings') const { ObjectId } = require('mongodb') const Features = require('../../infrastructure/Features') const { Project } = require('../../models/Project') const { Folder } = require('../../models/Folder') const ProjectEntityUpdateHandler = require('./ProjectEntityUpdateHandler') const ProjectDetailsHandler = require('./ProjectDetailsHandler') const HistoryManager = require('../History/HistoryManager') const { User } = require('../../models/User') const fs = require('fs') const path = require('path') const { callbackify } = require('util') const _ = require('underscore') const AnalyticsManager = require('../Analytics/AnalyticsManager') const MONTH_NAMES = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ] async function createBlankProject(ownerId, projectName, attributes = {}) { const isImport = attributes && attributes.overleaf const project = await _createBlankProject(ownerId, projectName, attributes) const segmentation = _.pick(attributes, [ 'fromV1TemplateId', 'fromV1TemplateVersionId', ]) Object.assign(segmentation, attributes.segmentation) segmentation.projectId = project._id if (isImport) { AnalyticsManager.recordEventForUser( ownerId, 'project-imported', segmentation ) } else { AnalyticsManager.recordEventForUser( ownerId, 'project-created', segmentation ) } return project } async function createProjectFromSnippet(ownerId, projectName, docLines) { const project = await _createBlankProject(ownerId, projectName) AnalyticsManager.recordEventForUser(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.recordEventForUser(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.recordEventForUser(ownerId, 'project-created', { projectId: project._id, }) return project } async function _addExampleProjectFiles(ownerId, projectName, project) { const mainDocLines = await _buildTemplate( 'example-project/main.tex', ownerId, projectName ) await _createRootDoc(project, ownerId, mainDocLines) const bibDocLines = await _buildTemplate( 'example-project/sample.bib', ownerId, projectName ) await ProjectEntityUpdateHandler.promises.addDoc( project._id, project.rootFolder[0]._id, 'sample.bib', bibDocLines, ownerId ) const frogPath = path.join( __dirname, '/../../../templates/project_files/example-project/frog.jpg' ) await ProjectEntityUpdateHandler.promises.addFile( project._id, project.rootFolder[0]._id, 'frog.jpg', frogPath, null, ownerId ) } async function _createBlankProject(ownerId, projectName, attributes = {}) { metrics.inc('project-creation') await ProjectDetailsHandler.promises.validateProjectName(projectName) if (!attributes.overleaf) { const history = await HistoryManager.promises.initializeProject() attributes.overleaf = { history: { id: history ? history.overleaf_id : undefined }, } } const rootFolder = new Folder({ name: 'rootFolder' }) attributes.lastUpdatedBy = attributes.owner_ref = new ObjectId(ownerId) attributes.name = projectName const project = new Project(attributes) Object.assign(project, attributes) // only display full project history when the project has the overleaf history id attribute // (to allow scripted creation of projects without full project history) const historyId = _.get(attributes, ['overleaf', 'history', 'id']) if ( Features.hasFeature('history-v1') && Settings.apis.project_history.displayHistoryForNewProjects && historyId ) { 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') project.spellCheckLanguage = user.ace.spellCheckLanguage return await project.save() } async function _createRootDoc(project, ownerId, docLines) { try { const { doc } = await ProjectEntityUpdateHandler.promises.addDoc( project._id, project.rootFolder[0]._id, 'main.tex', docLines, ownerId ) 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( __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') } module.exports = { createBlankProject: callbackify(createBlankProject), createProjectFromSnippet: callbackify(createProjectFromSnippet), createBasicProject: callbackify(createBasicProject), createExampleProject: callbackify(createExampleProject), promises: { createBlankProject, createProjectFromSnippet, createBasicProject, createExampleProject, }, } metrics.timeAsyncMethod( module.exports, 'createBlankProject', 'mongo.ProjectCreationHandler', logger )