diff --git a/apps/client/src/common/api/db.ts b/apps/client/src/common/api/db.ts index 7e004327fb..41de27a24b 100644 --- a/apps/client/src/common/api/db.ts +++ b/apps/client/src/common/api/db.ts @@ -123,7 +123,7 @@ export async function renameProject(filename: string, newFilename: string): Prom const url = `${dbPath}/${filename}/rename`; const decodedUrl = decodeURIComponent(url); const res = await axios.put(decodedUrl, { - filename: newFilename, + newFilename, }); return res.data; } diff --git a/apps/server/src/api-data/db/db.controller.ts b/apps/server/src/api-data/db/db.controller.ts index d3f923407c..e24cde4695 100644 --- a/apps/server/src/api-data/db/db.controller.ts +++ b/apps/server/src/api-data/db/db.controller.ts @@ -15,11 +15,11 @@ import type { Request, Response } from 'express'; import { failEmptyObjects } from '../../utils/routerUtils.js'; import { resolveDbDirectory } from '../../setup/index.js'; -import * as projectService from '../../services/project-service/ProjectService.js'; -import { doesProjectExist, upload, validateProjectFiles } from '../../services/project-service/projectServiceUtils.js'; +import { doesProjectExist, upload } from '../../services/project-service/projectServiceUtils.js'; import { oscIntegration } from '../../services/integration-service/OscIntegration.js'; import { httpIntegration } from '../../services/integration-service/HttpIntegration.js'; import { DataProvider } from '../../classes/data-provider/DataProvider.js'; +import * as projectService from '../../services/project-service/ProjectService.js'; export async function patchPartialProjectFile(req: Request, res: Response) { // all fields are optional in validation @@ -208,16 +208,9 @@ export async function duplicateProjectFile(req: Request, res: Response) { try { - const { filename: newFilename } = req.body; + const { newFilename } = req.body; const { filename } = req.params; - const errors = validateProjectFiles({ filename, newFilename }); - - if (errors.length) { - return res.status(409).send({ message: errors.join(', ') }); - } - - // Rename the file await projectService.renameProjectFile(filename, newFilename); res.status(201).send({ @@ -225,6 +218,10 @@ export async function renameProjectFile(req: Request, res: Response { + async setLastLoadedProject(filename: string): Promise { if (isTest) return; if (!this.didInit) { diff --git a/apps/server/src/services/project-service/ProjectService.ts b/apps/server/src/services/project-service/ProjectService.ts index 3170a77902..87c5a73285 100644 --- a/apps/server/src/services/project-service/ProjectService.ts +++ b/apps/server/src/services/project-service/ProjectService.ts @@ -14,7 +14,6 @@ import { dbModel } from '../../models/dataModel.js'; import { deleteFile } from '../../utils/parserUtils.js'; import { switchDb } from '../../setup/loadDb.js'; import { doesProjectExist, getPathToProject, getProjectFiles } from './projectServiceUtils.js'; -import { parseJson } from '../../utils/parser.js'; import { generateUniqueFileName } from '../../utils/generateUniqueFilename.js'; // init dependencies @@ -38,16 +37,14 @@ export async function applyProjectFile(name: string, options?: Options) { const filePath = getPathToProject(name); const data = parseProjectFile(filePath); - const result = parseJson(data); - // change LowDB to point to new file - await switchDb(filePath, result.data); + await switchDb(filePath); // apply data model await applyDataModel(data, options); // persist the project selection - await appStateProvider.updateDatabaseConfig(name); + await appStateProvider.setLastLoadedProject(name); } /** @@ -66,17 +63,17 @@ export async function getProjectList(): Promise { /** * Duplicates an existing project file */ -export async function duplicateProjectFile(originalFile: string, newFileName: string) { +export async function duplicateProjectFile(originalFile: string, newFilename: string) { if (!doesProjectExist(originalFile)) { throw new Error('Project file not found'); } - if (doesProjectExist(newFileName)) { - throw new Error(`Project file with name ${newFileName} already exists`); + if (doesProjectExist(newFilename)) { + throw new Error(`Project file with name ${newFilename} already exists`); } const projectFilePath = getPathToProject(originalFile); - const duplicateProjectFilePath = getPathToProject(newFileName); + const duplicateProjectFilePath = getPathToProject(newFilename); return copyFile(projectFilePath, duplicateProjectFilePath); } @@ -84,17 +81,24 @@ export async function duplicateProjectFile(originalFile: string, newFileName: st /** * Renames an existing project file */ -export async function renameProjectFile(existingProjectFile: string, newName: string) { - const projectFilePath = getPathToProject(existingProjectFile); - const newProjectFilePath = getPathToProject(newName); +export async function renameProjectFile(originalFile: string, newFilename: string) { + if (!doesProjectExist(originalFile)) { + throw new Error('Project file not found'); + } + + if (doesProjectExist(newFilename)) { + throw new Error(`Project file with name ${newFilename} already exists`); + } + + const projectFilePath = getPathToProject(originalFile); + const newProjectFilePath = getPathToProject(newFilename); await rename(projectFilePath, newProjectFilePath); // Update the last loaded project config if current loaded project is the one being renamed - const lastLoadedProject = await appStateProvider.getLastLoadedProject(); - - if (lastLoadedProject === existingProjectFile) { - await appStateProvider.updateDatabaseConfig(newName); + const isLoaded = await appStateProvider.isLastLoadedProject(originalFile); + if (isLoaded) { + await applyProjectFile(newFilename); } } @@ -121,7 +125,7 @@ export async function createProject(filename: string, projectData: ProjectData) await applyDataModel(data); // update app state to point to new value - appStateProvider.updateDatabaseConfig(uniqueFileName); + appStateProvider.setLastLoadedProject(uniqueFileName); return uniqueFileName; } 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 d9bb401d5e..2c31a8646c 100644 --- a/apps/server/src/services/project-service/__tests__/ProjectService.test.ts +++ b/apps/server/src/services/project-service/__tests__/ProjectService.test.ts @@ -1,4 +1,4 @@ -import { deleteProjectFile, duplicateProjectFile } from '../ProjectService.js'; +import { deleteProjectFile, duplicateProjectFile, renameProjectFile } from '../ProjectService.js'; import { appStateProvider } from '../../app-state-service/AppStateService.js'; import { doesProjectExist } from '../projectServiceUtils.js'; import { Mock } from 'vitest'; @@ -54,3 +54,20 @@ describe('duplicateProjectFile', () => { ); }); }); + +describe('renameProjectFile', () => { + it('throws an error if origin project does not exist', async () => { + (doesProjectExist as Mock).mockReturnValue(false); + 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); + // new project exists + (doesProjectExist as Mock).mockReturnValueOnce(true); + expect(renameProjectFile('nonexistentProject', '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 6eec6f7d6f..5ad7315f86 100644 --- a/apps/server/src/services/project-service/projectServiceUtils.ts +++ b/apps/server/src/services/project-service/projectServiceUtils.ts @@ -59,35 +59,6 @@ export function doesProjectExist(name: string): boolean { return existsSync(projectFilePath); } -/** - * @description Validates the existence of project files. - * @param {object} projectFiles - * @param {string} projectFiles.projectFilename - * @param {string} projectFiles.newFilename - * - * @returns {Promise>} Array of errors - * - */ -export const validateProjectFiles = (projectFiles: { filename?: string; newFilename?: string }): Array => { - const errors: string[] = []; - - // current project must exist - if (projectFiles.filename) { - if (!doesProjectExist(projectFiles.filename)) { - errors.push('Project file does not exist'); - } - } - - // new project must NOT exist - if (projectFiles.newFilename) { - if (doesProjectExist(projectFiles.newFilename)) { - errors.push('New project file already exists'); - } - } - - return errors; -}; - /** * Returns the absolute path to a project file */ diff --git a/apps/server/src/setup/loadDb.ts b/apps/server/src/setup/loadDb.ts index 9bd928ba55..d2d26ee1d6 100644 --- a/apps/server/src/setup/loadDb.ts +++ b/apps/server/src/setup/loadDb.ts @@ -52,13 +52,14 @@ const populateDb = (directory: string, filename: string): string => { async function loadDb(directory: string, filename: string) { const dbInDisk = populateDb(directory, filename); + // TODO: should this be passed in somewhere? let newData: DatabaseModel = dbModel; try { const maybeProjectFile = parseProjectFile(dbInDisk); const result = parseJson(maybeProjectFile); - await appStateProvider.updateDatabaseConfig(filename); + await appStateProvider.setLastLoadedProject(filename); newData = result.data; } catch (error) { @@ -88,10 +89,14 @@ const init = async () => { /** * Allows to switch the database to a new file */ -export const switchDb = async (filePath: string, data: DatabaseModel) => { - const newDb = await JSONFilePreset(filePath, data); +export const switchDb = async (filePath: string, initialData: DatabaseModel = dbModel) => { + const newDb = await JSONFilePreset(filePath, initialData); + + // Read the database to initialize it + await newDb.read(); + db = newDb; - data = newDb.data; + data = db.data; }; init();