diff --git a/services/web/app/src/Features/Project/ProjectEntityUpdateHandler.mjs b/services/web/app/src/Features/Project/ProjectEntityUpdateHandler.mjs index 9d243a555c..f26b8fb876 100644 --- a/services/web/app/src/Features/Project/ProjectEntityUpdateHandler.mjs +++ b/services/web/app/src/Features/Project/ProjectEntityUpdateHandler.mjs @@ -221,6 +221,7 @@ async function setRootDoc(projectId, newRootDocID) { 'invalid file extension for root doc' ) } + return newRootDocID } async function unsetRootDoc(projectId) { diff --git a/services/web/app/src/Features/Project/ProjectOptionsHandler.mjs b/services/web/app/src/Features/Project/ProjectOptionsHandler.mjs index b73cc8da42..8004d9b1f3 100644 --- a/services/web/app/src/Features/Project/ProjectOptionsHandler.mjs +++ b/services/web/app/src/Features/Project/ProjectOptionsHandler.mjs @@ -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, {}) }, diff --git a/services/web/app/src/Features/Templates/TemplatesManager.mjs b/services/web/app/src/Features/Templates/TemplatesManager.mjs index aefab9ff13..06490aa94c 100644 --- a/services/web/app/src/Features/Templates/TemplatesManager.mjs +++ b/services/web/app/src/Features/Templates/TemplatesManager.mjs @@ -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) { diff --git a/services/web/app/src/Features/Uploads/ProjectUploadManager.mjs b/services/web/app/src/Features/Uploads/ProjectUploadManager.mjs index e0ee4e2ffe..1936a0c28b 100644 --- a/services/web/app/src/Features/Uploads/ProjectUploadManager.mjs +++ b/services/web/app/src/Features/Uploads/ProjectUploadManager.mjs @@ -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 diff --git a/services/web/test/unit/src/Templates/TemplatesManager.test.mjs b/services/web/test/unit/src/Templates/TemplatesManager.test.mjs index 83f4eee752..276165af57 100644 --- a/services/web/test/unit/src/Templates/TemplatesManager.test.mjs +++ b/services/web/test/unit/src/Templates/TemplatesManager.test.mjs @@ -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' - ) }) }) })