diff --git a/apps/server/src/api-data/db/db.controller.ts b/apps/server/src/api-data/db/db.controller.ts index 344709d26..1ec6084d1 100644 --- a/apps/server/src/api-data/db/db.controller.ts +++ b/apps/server/src/api-data/db/db.controller.ts @@ -8,13 +8,10 @@ import { } from 'ontime-types'; import { getErrorMessage } from 'ontime-utils'; -import { join } from 'path'; -import { existsSync } from 'fs'; import type { Request, Response } from 'express'; import { failEmptyObjects } from '../../utils/routerUtils.js'; -import { resolveDbDirectory } from '../../setup/index.js'; -import { handleUploaded } from '../../services/project-service/projectServiceUtils.js'; +import { doesProjectExist, handleUploaded } from '../../services/project-service/projectServiceUtils.js'; import * as projectService from '../../services/project-service/ProjectService.js'; export async function patchPartialProjectFile(req: Request, res: Response) { @@ -73,10 +70,8 @@ export async function createProjectFile(req: Request, res: Response<{ filename: */ export async function projectDownload(req: Request, res: Response) { const { filename } = req.body; - const pathToFile = join(resolveDbDirectory, filename); - - // Check if the file exists before attempting to download - if (!existsSync(pathToFile)) { + const pathToFile = await doesProjectExist(filename); + if (!pathToFile) { return res.status(404).send({ message: `Project ${filename} not found.` }); } diff --git a/apps/server/src/services/project-service/ProjectService.ts b/apps/server/src/services/project-service/ProjectService.ts index 5c9894b0b..d7e1abd52 100644 --- a/apps/server/src/services/project-service/ProjectService.ts +++ b/apps/server/src/services/project-service/ProjectService.ts @@ -37,12 +37,11 @@ function init() { * Loads a data from a file into the runtime */ export async function loadProjectFile(name: string) { - if (!(await doesProjectExist(name))) { + const filePath = await doesProjectExist(name); + if (filePath === null) { throw new Error('Project file not found'); } - const filePath = getPathToProject(name); - // when loading a project file, we allow parsing to fail and interrupt the process const fileData = await parseProjectFile(filePath); const result = parseJson(fileData); @@ -96,45 +95,45 @@ export async function getProjectList(): Promise { * Duplicates an existing project file */ export async function duplicateProjectFile(originalFile: string, newFilename: string) { - if (!(await doesProjectExist(originalFile))) { + const projectFilePath = await doesProjectExist(originalFile); + if (projectFilePath === null) { throw new Error('Project file not found'); } - if (await doesProjectExist(newFilename)) { + const duplicateProjectFilePath = await doesProjectExist(newFilename); + if (duplicateProjectFilePath !== null) { throw new Error(`Project file with name ${newFilename} already exists`); } - const projectFilePath = getPathToProject(originalFile); - const duplicateProjectFilePath = getPathToProject(newFilename); - - return copyFile(projectFilePath, duplicateProjectFilePath); + const pathToDuplicate = getPathToProject(newFilename); + return copyFile(projectFilePath, pathToDuplicate); } /** * Renames an existing project file */ export async function renameProjectFile(originalFile: string, newFilename: string) { - if (!(await doesProjectExist(originalFile))) { + const projectFilePath = await doesProjectExist(originalFile); + if (projectFilePath === null) { throw new Error('Project file not found'); } - if (await doesProjectExist(newFilename)) { + const newProjectFilePath = await doesProjectExist(newFilename); + if (newProjectFilePath !== null) { throw new Error(`Project file with name ${newFilename} already exists`); } - const projectFilePath = getPathToProject(originalFile); - const newProjectFilePath = getPathToProject(newFilename); - - await rename(projectFilePath, newProjectFilePath); + const pathToRenamed = getPathToProject(newFilename); + await rename(projectFilePath, pathToRenamed); // Update the last loaded project config if current loaded project is the one being renamed const isLoaded = await appStateProvider.isLastLoadedProject(originalFile); if (isLoaded) { - const fileData = await parseProjectFile(newProjectFilePath); + const fileData = await parseProjectFile(pathToRenamed); const result = parseJson(fileData); // change LowDB to point to new file - await switchDb(newProjectFilePath, result.data); + await switchDb(pathToRenamed, result.data); logger.info(LogOrigin.Server, `Loaded project ${newFilename}`); // persist the project selection @@ -191,11 +190,11 @@ export async function deleteProjectFile(filename: string) { throw new Error('Cannot delete currently loaded project'); } - if (!(await doesProjectExist(filename))) { + const projectFilePath = await doesProjectExist(filename); + if (projectFilePath === null) { throw new Error('Project file not found'); } - const projectFilePath = getPathToProject(filename); await deleteFile(projectFilePath); } diff --git a/apps/server/src/services/project-service/__tests__/ProjectService.test.ts b/apps/server/src/services/project-service/__tests__/ProjectService.test.ts index 2c31a8646..7c72aced4 100644 --- a/apps/server/src/services/project-service/__tests__/ProjectService.test.ts +++ b/apps/server/src/services/project-service/__tests__/ProjectService.test.ts @@ -1,8 +1,10 @@ -import { deleteProjectFile, duplicateProjectFile, renameProjectFile } from '../ProjectService.js'; -import { appStateProvider } from '../../app-state-service/AppStateService.js'; -import { doesProjectExist } from '../projectServiceUtils.js'; import { Mock } from 'vitest'; +import { appStateProvider } from '../../app-state-service/AppStateService.js'; + +import { deleteProjectFile, duplicateProjectFile, renameProjectFile } from '../ProjectService.js'; +import { doesProjectExist } from '../projectServiceUtils.js'; + // stop the database loading from initiating vi.mock('../../../setup/loadDb.js', () => { return { @@ -33,23 +35,23 @@ describe('deleteProjectFile', () => { it('throws an error if the project file does not exist', async () => { (appStateProvider.isLastLoadedProject as Mock).mockResolvedValue(false); - (doesProjectExist as Mock).mockReturnValue(false); + (doesProjectExist as Mock).mockReturnValue(null); await expect(deleteProjectFile('nonexistentProject')).rejects.toThrow('Project file not found'); }); }); describe('duplicateProjectFile', () => { it('throws an error if origin project does not exist', async () => { - (doesProjectExist as Mock).mockReturnValue(false); + (doesProjectExist as Mock).mockReturnValue(null); await expect(duplicateProjectFile('does not exist', 'doesnt matter')).rejects.toThrow('Project file not found'); }); - it('throws an error if new file name is already a project', async () => { + it('throws an error if new file name is already a project', () => { // current project exists - (doesProjectExist as Mock).mockReturnValueOnce(true); + (doesProjectExist as Mock).mockReturnValueOnce('thisoneexists'); // new project exists - (doesProjectExist as Mock).mockReturnValueOnce(true); - expect(duplicateProjectFile('nonexistentProject', 'existingproject')).rejects.toThrow( + (doesProjectExist as Mock).mockReturnValueOnce('existingproject'); + expect(duplicateProjectFile('thisoneexists', 'existingproject')).rejects.toThrow( 'Project file with name existingproject already exists', ); }); @@ -57,16 +59,16 @@ describe('duplicateProjectFile', () => { describe('renameProjectFile', () => { it('throws an error if origin project does not exist', async () => { - (doesProjectExist as Mock).mockReturnValue(false); + (doesProjectExist as Mock).mockReturnValue(null); await expect(renameProjectFile('does not exist', 'doesnt matter')).rejects.toThrow('Project file not found'); }); it('throws an error if new file name is already a project', async () => { // current project exists - (doesProjectExist as Mock).mockReturnValueOnce(true); + (doesProjectExist as Mock).mockReturnValueOnce('this one exists'); // new project exists - (doesProjectExist as Mock).mockReturnValueOnce(true); - expect(renameProjectFile('nonexistentProject', 'existingproject')).rejects.toThrow( + (doesProjectExist as Mock).mockReturnValueOnce('existingproject'); + expect(renameProjectFile('this one exists', 'existingproject')).rejects.toThrow( 'Project file with name existingproject already exists', ); }); diff --git a/apps/server/src/services/project-service/projectServiceUtils.ts b/apps/server/src/services/project-service/projectServiceUtils.ts index 61938336e..7f7bc2cad 100644 --- a/apps/server/src/services/project-service/projectServiceUtils.ts +++ b/apps/server/src/services/project-service/projectServiceUtils.ts @@ -1,4 +1,4 @@ -import { ProjectFile } from 'ontime-types'; +import { MaybeString, ProjectFile } from 'ontime-types'; import { access, rename, stat } from 'fs/promises'; import { join } from 'path'; @@ -51,13 +51,13 @@ export async function getProjectFiles(): Promise { * Checks whether a project of a given name exists * @param name */ -export async function doesProjectExist(name: string): Promise { +export async function doesProjectExist(name: string): Promise { try { - const projectFilePath = join(resolveProjectsDirectory, name); + const projectFilePath = getPathToProject(name); await access(projectFilePath); - return true; + return projectFilePath; } catch (_) { - return false; + return null; } }