2021-11-10 08:40:18 -05:00
|
|
|
const logger = require('@overleaf/logger')
|
2020-08-11 05:35:08 -04:00
|
|
|
const OError = require('@overleaf/o-error')
|
2020-10-30 04:10:50 -04:00
|
|
|
const metrics = require('@overleaf/metrics')
|
2021-07-07 05:38:56 -04:00
|
|
|
const Settings = require('@overleaf/settings')
|
2020-09-23 04:49:26 -04:00
|
|
|
const { ObjectId } = require('mongodb')
|
2021-07-20 05:26:23 -04:00
|
|
|
const Features = require('../../infrastructure/Features')
|
2019-05-29 05:21:06 -04:00
|
|
|
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')
|
2021-04-27 04:59:46 -04:00
|
|
|
const path = require('path')
|
|
|
|
const { callbackify } = require('util')
|
2019-05-29 05:21:06 -04:00
|
|
|
const _ = require('underscore')
|
2020-11-04 08:35:37 -05:00
|
|
|
const AnalyticsManager = require('../Analytics/AnalyticsManager')
|
2022-10-05 05:27:19 -04:00
|
|
|
const TpdsUpdateSender = require('../ThirdPartyDataStore/TpdsUpdateSender')
|
2021-04-27 04:59:46 -04:00
|
|
|
|
|
|
|
const MONTH_NAMES = [
|
|
|
|
'January',
|
|
|
|
'February',
|
|
|
|
'March',
|
|
|
|
'April',
|
|
|
|
'May',
|
|
|
|
'June',
|
|
|
|
'July',
|
|
|
|
'August',
|
|
|
|
'September',
|
|
|
|
'October',
|
|
|
|
'November',
|
|
|
|
'December',
|
|
|
|
]
|
|
|
|
|
2022-07-27 07:25:36 -04:00
|
|
|
const templateProjectDir = Features.hasFeature('saas')
|
|
|
|
? 'example-project'
|
|
|
|
: 'example-project-sp'
|
|
|
|
|
2022-10-05 05:27:19 -04:00
|
|
|
async function createBlankProject(
|
|
|
|
ownerId,
|
|
|
|
projectName,
|
|
|
|
attributes = {},
|
|
|
|
options
|
|
|
|
) {
|
2021-04-27 04:59:46 -04:00
|
|
|
const isImport = attributes && attributes.overleaf
|
2022-10-05 05:27:19 -04:00
|
|
|
const project = await _createBlankProject(
|
|
|
|
ownerId,
|
|
|
|
projectName,
|
|
|
|
attributes,
|
|
|
|
options
|
|
|
|
)
|
2021-10-07 08:09:52 -04:00
|
|
|
const segmentation = _.pick(attributes, [
|
|
|
|
'fromV1TemplateId',
|
|
|
|
'fromV1TemplateVersionId',
|
|
|
|
])
|
2021-10-28 09:01:00 -04:00
|
|
|
Object.assign(segmentation, attributes.segmentation)
|
2021-10-07 08:09:52 -04:00
|
|
|
segmentation.projectId = project._id
|
2021-04-27 04:59:46 -04:00
|
|
|
if (isImport) {
|
2021-10-07 08:09:52 -04:00
|
|
|
AnalyticsManager.recordEventForUser(
|
|
|
|
ownerId,
|
|
|
|
'project-imported',
|
|
|
|
segmentation
|
|
|
|
)
|
2021-04-27 04:59:46 -04:00
|
|
|
} else {
|
2021-10-07 08:09:52 -04:00
|
|
|
AnalyticsManager.recordEventForUser(
|
|
|
|
ownerId,
|
|
|
|
'project-created',
|
|
|
|
segmentation
|
|
|
|
)
|
2021-04-27 04:59:46 -04:00
|
|
|
}
|
|
|
|
return project
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-04-27 04:59:46 -04:00
|
|
|
async function createProjectFromSnippet(ownerId, projectName, docLines) {
|
|
|
|
const project = await _createBlankProject(ownerId, projectName)
|
2021-09-10 04:30:01 -04:00
|
|
|
AnalyticsManager.recordEventForUser(ownerId, 'project-created', {
|
2021-04-27 04:59:46 -04:00
|
|
|
projectId: project._id,
|
|
|
|
})
|
|
|
|
await _createRootDoc(project, ownerId, docLines)
|
|
|
|
return project
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-04-27 04:59:46 -04:00
|
|
|
async function createBasicProject(ownerId, projectName) {
|
|
|
|
const project = await _createBlankProject(ownerId, projectName)
|
2021-08-19 05:37:25 -04:00
|
|
|
|
|
|
|
const docLines = await _buildTemplate('mainbasic.tex', ownerId, projectName)
|
|
|
|
await _createRootDoc(project, ownerId, docLines)
|
|
|
|
|
2021-09-10 04:30:01 -04:00
|
|
|
AnalyticsManager.recordEventForUser(ownerId, 'project-created', {
|
2021-04-27 04:59:46 -04:00
|
|
|
projectId: project._id,
|
|
|
|
})
|
2021-08-19 05:37:25 -04:00
|
|
|
|
2021-04-27 04:59:46 -04:00
|
|
|
return project
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-04-27 04:59:46 -04:00
|
|
|
async function createExampleProject(ownerId, projectName) {
|
|
|
|
const project = await _createBlankProject(ownerId, projectName)
|
|
|
|
|
2021-08-19 05:37:25 -04:00
|
|
|
await _addExampleProjectFiles(ownerId, projectName, project)
|
2021-04-27 04:59:46 -04:00
|
|
|
|
2021-09-10 04:30:01 -04:00
|
|
|
AnalyticsManager.recordEventForUser(ownerId, 'project-created', {
|
2021-07-30 04:57:32 -04:00
|
|
|
projectId: project._id,
|
|
|
|
})
|
2021-04-27 04:59:46 -04:00
|
|
|
|
|
|
|
return project
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-08-19 05:37:25 -04:00
|
|
|
async function _addExampleProjectFiles(ownerId, projectName, project) {
|
2021-04-27 04:59:46 -04:00
|
|
|
const mainDocLines = await _buildTemplate(
|
2022-07-27 07:25:36 -04:00
|
|
|
`${templateProjectDir}/main.tex`,
|
2021-04-27 04:59:46 -04:00
|
|
|
ownerId,
|
|
|
|
projectName
|
|
|
|
)
|
|
|
|
await _createRootDoc(project, ownerId, mainDocLines)
|
|
|
|
|
|
|
|
const bibDocLines = await _buildTemplate(
|
2022-07-27 07:25:36 -04:00
|
|
|
`${templateProjectDir}/sample.bib`,
|
2021-04-27 04:59:46 -04:00
|
|
|
ownerId,
|
|
|
|
projectName
|
|
|
|
)
|
|
|
|
await ProjectEntityUpdateHandler.promises.addDoc(
|
|
|
|
project._id,
|
|
|
|
project.rootFolder[0]._id,
|
|
|
|
'sample.bib',
|
|
|
|
bibDocLines,
|
2022-08-25 08:01:39 -04:00
|
|
|
ownerId,
|
|
|
|
null
|
2021-04-27 04:59:46 -04:00
|
|
|
)
|
|
|
|
|
2021-09-14 06:43:57 -04:00
|
|
|
const frogPath = path.join(
|
|
|
|
__dirname,
|
2022-07-27 07:25:36 -04:00
|
|
|
`/../../../templates/project_files/${templateProjectDir}/frog.jpg`
|
2021-04-27 04:59:46 -04:00
|
|
|
)
|
|
|
|
await ProjectEntityUpdateHandler.promises.addFile(
|
|
|
|
project._id,
|
|
|
|
project.rootFolder[0]._id,
|
|
|
|
'frog.jpg',
|
|
|
|
frogPath,
|
|
|
|
null,
|
2022-08-25 08:01:39 -04:00
|
|
|
ownerId,
|
|
|
|
null
|
2021-04-27 04:59:46 -04:00
|
|
|
)
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2022-10-05 05:27:19 -04:00
|
|
|
async function _createBlankProject(
|
|
|
|
ownerId,
|
|
|
|
projectName,
|
|
|
|
attributes = {},
|
|
|
|
{ skipCreatingInTPDS = false } = {}
|
|
|
|
) {
|
2021-04-27 04:59:46 -04:00
|
|
|
metrics.inc('project-creation')
|
2022-09-02 06:57:10 -04:00
|
|
|
const timer = new metrics.Timer('project-creation')
|
2021-04-27 04:59:46 -04:00
|
|
|
await ProjectDetailsHandler.promises.validateProjectName(projectName)
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-04-27 04:59:46 -04:00
|
|
|
if (!attributes.overleaf) {
|
|
|
|
const history = await HistoryManager.promises.initializeProject()
|
|
|
|
attributes.overleaf = {
|
|
|
|
history: { id: history ? history.overleaf_id : undefined },
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2021-04-27 04:59:46 -04:00
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-04-27 04:59:46 -04:00
|
|
|
const rootFolder = new Folder({ name: 'rootFolder' })
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-04-27 04:59:46 -04:00
|
|
|
attributes.lastUpdatedBy = attributes.owner_ref = new ObjectId(ownerId)
|
|
|
|
attributes.name = projectName
|
|
|
|
const project = new Project(attributes)
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-04-27 04:59:46 -04:00
|
|
|
Object.assign(project, attributes)
|
|
|
|
|
2021-07-12 06:40:45 -04:00
|
|
|
// 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'])
|
2021-07-20 05:26:23 -04:00
|
|
|
if (
|
|
|
|
Features.hasFeature('history-v1') &&
|
|
|
|
Settings.apis.project_history.displayHistoryForNewProjects &&
|
|
|
|
historyId
|
|
|
|
) {
|
2021-04-27 04:59:46 -04:00
|
|
|
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
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2021-04-27 04:59:46 -04:00
|
|
|
}
|
|
|
|
project.rootFolder[0] = rootFolder
|
|
|
|
const user = await User.findById(ownerId, 'ace.spellCheckLanguage')
|
|
|
|
project.spellCheckLanguage = user.ace.spellCheckLanguage
|
2022-09-02 06:57:10 -04:00
|
|
|
await project.save()
|
2022-10-05 05:27:19 -04:00
|
|
|
if (!skipCreatingInTPDS) {
|
|
|
|
await TpdsUpdateSender.promises.createProject({
|
|
|
|
projectId: project._id,
|
|
|
|
projectName,
|
|
|
|
ownerId,
|
|
|
|
userId: ownerId,
|
|
|
|
})
|
|
|
|
}
|
2022-09-02 06:57:10 -04:00
|
|
|
timer.done()
|
|
|
|
return project
|
2021-04-27 04:59:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
async function _createRootDoc(project, ownerId, docLines) {
|
|
|
|
try {
|
|
|
|
const { doc } = await ProjectEntityUpdateHandler.promises.addDoc(
|
2019-05-29 05:21:06 -04:00
|
|
|
project._id,
|
|
|
|
project.rootFolder[0]._id,
|
|
|
|
'main.tex',
|
|
|
|
docLines,
|
2022-08-25 08:01:39 -04:00
|
|
|
ownerId,
|
|
|
|
null
|
2019-05-29 05:21:06 -04:00
|
|
|
)
|
2021-04-27 04:59:46 -04:00
|
|
|
await ProjectEntityUpdateHandler.promises.setRootDoc(project._id, doc._id)
|
|
|
|
} catch (error) {
|
|
|
|
throw OError.tag(error, 'error adding root doc when creating project')
|
|
|
|
}
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-04-27 04:59:46 -04:00
|
|
|
async function _buildTemplate(templateName, userId, projectName) {
|
|
|
|
const user = await User.findById(userId, 'first_name last_name')
|
|
|
|
|
2021-09-14 06:43:57 -04:00
|
|
|
const templatePath = path.join(
|
|
|
|
__dirname,
|
|
|
|
`/../../../templates/project_files/${templateName}`
|
2021-04-27 04:59:46 -04:00
|
|
|
)
|
|
|
|
const template = fs.readFileSync(templatePath)
|
|
|
|
const data = {
|
|
|
|
project_name: projectName,
|
|
|
|
user,
|
|
|
|
year: new Date().getUTCFullYear(),
|
|
|
|
month: MONTH_NAMES[new Date().getUTCMonth()],
|
|
|
|
}
|
2021-05-07 06:00:34 -04:00
|
|
|
const output = _.template(template.toString())(data)
|
2021-04-27 04:59:46 -04:00
|
|
|
return output.split('\n')
|
|
|
|
}
|
2021-04-14 09:17:21 -04:00
|
|
|
|
2021-04-27 04:59:46 -04:00
|
|
|
module.exports = {
|
|
|
|
createBlankProject: callbackify(createBlankProject),
|
|
|
|
createProjectFromSnippet: callbackify(createProjectFromSnippet),
|
|
|
|
createBasicProject: callbackify(createBasicProject),
|
|
|
|
createExampleProject: callbackify(createExampleProject),
|
|
|
|
promises: {
|
|
|
|
createBlankProject,
|
|
|
|
createProjectFromSnippet,
|
|
|
|
createBasicProject,
|
|
|
|
createExampleProject,
|
2021-04-27 03:52:58 -04:00
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
metrics.timeAsyncMethod(
|
2021-04-27 04:59:46 -04:00
|
|
|
module.exports,
|
2019-05-29 05:21:06 -04:00
|
|
|
'createBlankProject',
|
|
|
|
'mongo.ProjectCreationHandler',
|
|
|
|
logger
|
|
|
|
)
|