Keep several project files in user folder - Steps 5A-E (#684)

This commit is contained in:
Ary
2024-01-06 13:24:48 -07:00
committed by GitHub
parent c889d00e6b
commit 65a5aaa665
7 changed files with 324 additions and 1 deletions
+158 -1
View File
@@ -13,13 +13,21 @@ import { RequestHandler, Request, Response } from 'express';
import fs from 'fs';
import { networkInterfaces } from 'os';
import { join } from 'path';
import { copyFile, rename, writeFile } from 'fs/promises';
import { fileHandler } from '../utils/parser.js';
import { DataProvider } from '../classes/data-provider/DataProvider.js';
import { failEmptyObjects, failIsNotArray } from '../utils/routerUtils.js';
import { PlaybackService } from '../services/PlaybackService.js';
import { eventStore } from '../stores/EventStore.js';
import { getAppDataPath, isDocker, lastLoadedProjectConfigPath, resolveDbPath, resolveStylesPath } from '../setup.js';
import {
getAppDataPath,
isDocker,
lastLoadedProjectConfigPath,
resolveDbPath,
resolveStylesPath,
uploadsFolderPath,
} from '../setup.js';
import { oscIntegration } from '../services/integration-service/OscIntegration.js';
import { httpIntegration } from '../services/integration-service/HttpIntegration.js';
import { logger } from '../classes/Logger.js';
@@ -29,6 +37,10 @@ import { runtimeCacheStore } from '../stores/cachingStore.js';
import { delayedRundownCacheKey } from '../services/rundown-service/delayedRundown.utils.js';
import { integrationService } from '../services/integration-service/IntegrationService.js';
import { getProjectFiles } from '../utils/getFileListFromFolder.js';
import { configService } from '../services/ConfigService.js';
import { deleteFile } from '../utils/parserUtils.js';
import { validateProjectFiles } from './ontimeController.validate.js';
import { dbModel } from '../models/dataModel.js';
// Create controller for GET request to '/ontime/poll'
// Returns data for current state
@@ -511,3 +523,148 @@ export const loadProject: RequestHandler = async (req, res) => {
res.status(500).send({ message: error.toString() });
}
};
/**
* Duplicates a project file.
* Receives the original project filename (`filename`) from the request parameters
* and the filename for the duplicate (`newFilename`) from the request body.
*
* @param {Request} req - The express request object. Expects `filename` in the request parameters and `newFilename` in the request body.
* @param {Response} res - The express response object. Sends a 200 status with a success message upon successful duplication,
* a 409 status if there are validation errors,
* or a 500 status with an error message in case of an exception.
*/
export const duplicateProjectFile: RequestHandler = async (req, res) => {
try {
const { filename } = req.params;
const { newFilename } = req.body;
const projectFilePath = join(uploadsFolderPath, filename);
const duplicateProjectFilePath = join(uploadsFolderPath, newFilename);
const errors = validateProjectFiles({ filename, newFilename });
if (errors.length) {
return res.status(409).send({ message: errors.join(', ') });
}
await copyFile(projectFilePath, duplicateProjectFilePath);
res.status(200).send({
message: `Duplicated project ${filename} to ${newFilename}`,
});
} catch (error) {
res.status(500).send({ message: error.toString() });
}
};
/**
* Renames a project file.
* Receives the current filename (`filename`) from the request parameters
* and the new filename (`newFilename`) from the request body.
*
* @param {Request} req - The express request object. Expects `filename` in the request parameters and `newFilename` in the request body.
* @param {Response} res - The express response object. Sends a 200 status with a success message upon successful renaming,
* a 409 status if there are validation errors,
* or a 500 status with an error message in case of an exception.
*/
export const renameProjectFile: RequestHandler = async (req, res) => {
try {
const { newFilename } = req.body;
const { filename } = req.params;
const projectFilePath = join(uploadsFolderPath, filename);
const newProjectFilePath = join(uploadsFolderPath, newFilename);
const errors = validateProjectFiles({ filename, newFilename });
if (errors.length) {
return res.status(409).send({ message: errors.join(', ') });
}
// Rename the file
await rename(projectFilePath, newProjectFilePath);
// Update the last loaded project config if current loaded project is the one being renamed
const { lastLoadedProject } = await configService.getConfig();
if (lastLoadedProject === filename) {
await configService.updateDatabaseConfig(newFilename);
}
res.status(200).send({
message: `Renamed project ${filename} to ${newFilename}`,
});
} catch (error) {
res.status(500).send({ message: error.toString() });
}
};
/**
* Creates a new project file.
* Receives the project filename (`filename`) from the request body.
*
* @param {Request} req - The express request object. Expects `filename` in the request body.
* @param {Response} res - The express response object. Sends a 200 status with a success message upon successful creation,
* a 409 status if there are validation errors,
* or a 500 status with an error message in case of an exception.
*/
export const createProjectFile: RequestHandler = async (req, res) => {
try {
const { filename } = req.body;
const projectFilePath = join(uploadsFolderPath, filename);
const errors = validateProjectFiles({ newFilename: filename });
if (errors.length) {
return res.status(409).send({ message: errors.join(', ') });
}
await writeFile(projectFilePath, JSON.stringify(dbModel));
res.status(200).send({
message: `Created project ${filename}`,
});
} catch (error) {
res.status(500).send({ message: error.toString() });
}
};
/**
* Deletes an existing project file.
* Receives the project filename (`filename`) from the request parameters.
*
* @param {Request} req - The express request object. Expects `filename` in the request parameters.
* @param {Response} res - The express response object. Sends a 200 status with a success message upon successful deletion,
* a 403 status if attempting to delete the currently loaded project,
* a 409 status if there are validation errors,
* or a 500 status with an error message in case of an exception.
*/
export const deleteProjectFile: RequestHandler = async (req, res) => {
try {
const { filename } = req.params;
const { lastLoadedProject } = await configService.getConfig();
if (lastLoadedProject === filename) {
return res.status(403).send({ message: 'Cannot delete currently loaded project' });
}
const projectFilePath = join(uploadsFolderPath, filename);
const errors = validateProjectFiles({ filename: filename });
if (errors.length) {
return res.status(409).send({ message: errors.join(', ') });
}
await deleteFile(projectFilePath);
res.status(200).send({
message: `Deleted project ${filename}`,
});
} catch (error) {
res.status(500).send({ message: error.toString() });
}
};
@@ -1,9 +1,13 @@
import { body, check, validationResult } from 'express-validator';
import { join } from 'path';
import { existsSync } from 'fs';
import {
validateHttpSubscriptionObject,
validateOscSubscriptionObject,
validateOscSubscriptionCycle,
} from '../utils/parserFunctions.js';
import { uploadsFolderPath } from '../setup.js';
/**
* @description Validates object for POST /ontime/views
@@ -166,3 +170,99 @@ export const validateLoadProjectFile = [
next();
},
];
/**
* @description Validates the filenames for duplicating a project.
*/
export const validateProjectDuplicate = [
body('newFilename')
.exists()
.withMessage('New project filename is required')
.isString()
.withMessage('New project filename must be a string')
.isLength({ min: 1, max: 255 })
.withMessage('New project filename must be between 1 and 255 characters'),
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
next();
},
];
/**
* @description Validates the filenames for renaming a project.
*/
export const validateProjectRename = [
body('newFilename')
.exists()
.withMessage('Duplicate project filename is required')
.isString()
.withMessage('Duplicate project filename must be a string')
.isLength({ min: 1, max: 255 })
.withMessage('Duplicate project filename must be between 1 and 255 characters'),
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
next();
},
];
/**
* @description Validates the filename for creating a project file.
*/
export const validateProjectCreate = [
body('filename')
.exists()
.withMessage('Filename is required')
.isString()
.withMessage('Filename must be a string')
.isLength({ min: 1, max: 255 })
.withMessage('Filename must be between 1 and 255 characters'),
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
next();
},
];
/**
* @description Validates the existence of project files.
* @param {object} projectFiles
* @param {string} projectFiles.projectFilename
* @param {string} projectFiles.newFilename
*
* @returns {Promise<Array<string>>} Array of errors
*
*/
export const validateProjectFiles = (projectFiles: { filename?: string; newFilename?: string }): Array<string> => {
const errors = [];
if (projectFiles.filename) {
const projectFilePath = join(uploadsFolderPath, projectFiles.filename);
if (!existsSync(projectFilePath)) {
errors.push('Project file does not exist');
}
}
if (projectFiles.newFilename) {
const projectFilePath = join(uploadsFolderPath, projectFiles.newFilename);
if (existsSync(projectFilePath)) {
errors.push('New project file already exists');
}
}
return errors;
};