[web] inline trivial mongo queries when creating project from template (#30601)

GitOrigin-RevId: 7aefee892d491b1ec191d07bf1871317b897dec8
This commit is contained in:
Jakob Ackermann
2026-01-12 11:55:48 +00:00
committed by Copybot
parent 72ad614b25
commit 292230b1db
5 changed files with 70 additions and 76 deletions

View File

@@ -221,6 +221,7 @@ async function setRootDoc(projectId, newRootDocID) {
'invalid file extension for root doc'
)
}
return newRootDocID
}
async function unsetRootDoc(projectId) {

View File

@@ -9,22 +9,35 @@ const safeCompilers = ['xelatex', 'pdflatex', 'latex', 'lualatex']
const { ReturnDocument } = mongodb
const ProjectOptionsHandler = {
async setCompiler(projectId, compiler) {
if (!compiler) {
return
}
/**
* @param {string} compiler
* @return {string}
*/
normalizeCompiler(compiler) {
compiler = compiler.toLowerCase()
if (!safeCompilers.includes(compiler)) {
throw new Error(`invalid compiler: ${compiler}`)
}
return compiler
},
async setCompiler(projectId, compiler) {
if (!compiler) {
return
}
compiler = ProjectOptionsHandler.normalizeCompiler(compiler)
const conditions = { _id: projectId }
const update = { compiler }
return Project.updateOne(conditions, update, {})
},
async setImageName(projectId, imageName) {
/**
* @param {string} imageName
* @return {string | undefined}
*/
normalizeImageName(imageName) {
if (!imageName || !Array.isArray(settings.allowedImageNames)) {
return
return undefined
}
imageName = imageName.toLowerCase()
const isAllowed = settings.allowedImageNames.find(
@@ -33,8 +46,16 @@ const ProjectOptionsHandler = {
if (!isAllowed) {
throw new Error(`invalid imageName: ${imageName}`)
}
return settings.imageRoot + '/' + imageName
},
async setImageName(projectId, imageName) {
imageName = ProjectOptionsHandler.normalizeImageName(imageName)
if (!imageName) {
return
}
const conditions = { _id: projectId }
const update = { imageName: settings.imageRoot + '/' + imageName }
const update = { imageName }
return Project.updateOne(conditions, update, {})
},

View File

@@ -1,4 +1,3 @@
import { Project } from '../../models/Project.mjs'
import ProjectDetailsHandler from '../Project/ProjectDetailsHandler.mjs'
import ProjectOptionsHandlerModule from '../Project/ProjectOptionsHandler.mjs'
import ProjectRootDocManagerModule from '../Project/ProjectRootDocManager.mjs'
@@ -31,6 +30,11 @@ const TemplatesManager = {
userId,
imageName
) {
compiler = ProjectOptionsHandler.normalizeCompiler(compiler || 'pdflatex')
imageName = ProjectOptionsHandler.normalizeImageName(
imageName || 'wl_texlive:2018.1'
)
const zipUrl = `${settings.apis.v1.url}/api/v1/overleaf/templates/${templateVersionId}`
const zipReq = await fetchStreamWithResponse(zipUrl, {
basicAuth: {
@@ -47,7 +51,11 @@ const TemplatesManager = {
const attributes = {
fromV1TemplateId: templateId,
fromV1TemplateVersionId: templateVersionId,
compiler,
imageName,
}
if (brandVariationId) attributes.brandVariationId = brandVariationId
await pipeline(zipReq.stream, writeStream)
if (zipReq.response.status !== 200) {
@@ -76,16 +84,7 @@ const TemplatesManager = {
)
})
await TemplatesManager._setCompiler(project._id, compiler)
await TemplatesManager._setImage(project._id, imageName)
await TemplatesManager._setMainFile(project._id, mainFile)
await TemplatesManager._setBrandVariationId(project._id, brandVariationId)
const update = {
fromV1TemplateId: templateId,
fromV1TemplateVersionId: templateVersionId,
}
await Project.updateOne({ _id: project._id }, update, {})
await TemplatesManager._setMainFile(project, mainFile)
await prepareClsiCacheInBackground
@@ -95,33 +94,15 @@ const TemplatesManager = {
}
},
async _setCompiler(projectId, compiler) {
if (compiler == null) {
return
}
await ProjectOptionsHandler.setCompiler(projectId, compiler)
},
async _setImage(projectId, imageName) {
if (!imageName) {
imageName = 'wl_texlive:2018.1'
}
await ProjectOptionsHandler.setImageName(projectId, imageName)
},
async _setMainFile(projectId, mainFile) {
async _setMainFile(project, mainFile) {
if (mainFile == null) {
return
}
await ProjectRootDocManager.setRootDocFromName(projectId, mainFile)
},
async _setBrandVariationId(projectId, brandVariationId) {
if (brandVariationId == null) {
return
}
await ProjectOptionsHandler.setBrandVariationId(projectId, brandVariationId)
const rootDocId = await ProjectRootDocManager.setRootDocFromName(
project._id,
mainFile
)
if (rootDocId) project.rootDoc_id = rootDocId
},
async fetchFromV1(templateId) {

View File

@@ -87,7 +87,11 @@ async function createProjectFromZipArchiveWithName(
try {
await _initializeProjectWithZipContents(ownerId, project, contentsPath)
await ProjectRootDocManager.promises.setRootDocAutomatically(project._id)
const rootDocId =
await ProjectRootDocManager.promises.setRootDocAutomatically(
project._id
)
if (rootDocId) project.rootDoc_id = rootDocId
} catch (err) {
// no need to wait for the cleanup here
ProjectDeleter.promises

View File

@@ -47,6 +47,8 @@ describe('TemplatesManager', function () {
setCompiler: sinon.stub().resolves(),
setImageName: sinon.stub().resolves(),
setBrandVariationId: sinon.stub().resolves(),
normalizeCompiler: sinon.stub().returnsArg(0),
normalizeImageName: sinon.stub().returnsArg(0),
},
}
ctx.ProjectRootDocManager = {
@@ -182,6 +184,9 @@ describe('TemplatesManager', function () {
{
fromV1TemplateId: ctx.templateId,
fromV1TemplateVersionId: ctx.templateVersionId,
compiler: ctx.compiler,
imageName: ctx.imageName,
brandVariationId: ctx.brandVariationId,
}
)
})
@@ -190,33 +195,11 @@ describe('TemplatesManager', function () {
ctx.fs.promises.unlink.should.have.been.calledWith(ctx.dumpPath)
})
it('should set project options when passed', function (ctx) {
ctx.ProjectOptionsHandler.promises.setCompiler.should.have.been.calledWithMatch(
ctx.project_id,
ctx.compiler
)
ctx.ProjectOptionsHandler.promises.setImageName.should.have.been.calledWithMatch(
ctx.project_id,
ctx.imageName
)
it('should set project rootDoc when passed', function (ctx) {
ctx.ProjectRootDocManager.promises.setRootDocFromName.should.have.been.calledWithMatch(
ctx.project_id,
ctx.mainFile
)
ctx.ProjectOptionsHandler.promises.setBrandVariationId.should.have.been.calledWithMatch(
ctx.project_id,
ctx.brandVariationId
)
})
it('should update project', function (ctx) {
ctx.Project.updateOne.should.have.been.calledWithMatch(
{ _id: ctx.project_id },
{
fromV1TemplateId: ctx.templateId,
fromV1TemplateVersionId: ctx.templateVersionId,
}
)
})
})
@@ -234,20 +217,24 @@ describe('TemplatesManager', function () {
)
})
it('should not set missing project options', function (ctx) {
ctx.ProjectOptionsHandler.promises.setCompiler.called.should.equal(
false
it('should create project', function (ctx) {
ctx.ProjectUploadManager.promises.createProjectFromZipArchiveWithName.should.have.been.calledWithMatch(
ctx.user_id,
ctx.templateName,
ctx.dumpPath,
{
fromV1TemplateId: ctx.templateId,
fromV1TemplateVersionId: ctx.templateVersionId,
compiler: 'pdflatex',
imageName: 'wl_texlive:2018.1',
}
)
})
it('should not set missing project options', function (ctx) {
ctx.ProjectRootDocManager.promises.setRootDocFromName.called.should.equal(
false
)
ctx.ProjectOptionsHandler.promises.setBrandVariationId.called.should.equal(
false
)
ctx.ProjectOptionsHandler.promises.setImageName.should.have.been.calledWithMatch(
ctx.project_id,
'wl_texlive:2018.1'
)
})
})
})