feat: add routes to clone test folders and test cases (#301)
This commit is contained in:
74
backend/routes/cases/clone.js
Normal file
74
backend/routes/cases/clone.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import express from 'express';
|
||||||
|
const router = express.Router();
|
||||||
|
import { DataTypes } from 'sequelize';
|
||||||
|
import defineCase from '../../models/cases.js';
|
||||||
|
import defineStep from '../../models/steps.js';
|
||||||
|
import defineCaseStep from '../../models/caseSteps.js';
|
||||||
|
import authMiddleware from '../../middleware/auth.js';
|
||||||
|
import editableMiddleware from '../../middleware/verifyEditable.js';
|
||||||
|
|
||||||
|
export default function (sequelize) {
|
||||||
|
const { verifySignedIn } = authMiddleware(sequelize);
|
||||||
|
const { verifyProjectDeveloperFromProjectId } = editableMiddleware(sequelize);
|
||||||
|
const Case = defineCase(sequelize, DataTypes);
|
||||||
|
const Step = defineStep(sequelize, DataTypes);
|
||||||
|
const CaseStep = defineCaseStep(sequelize, DataTypes);
|
||||||
|
Case.belongsToMany(Step, { through: 'caseSteps' });
|
||||||
|
Step.belongsToMany(Case, { through: 'caseSteps' });
|
||||||
|
|
||||||
|
// TODO: Implement a safer middleware to check permissions based on the actual caseId (in this case, multiples case ids)
|
||||||
|
router.post('/clone', verifySignedIn, verifyProjectDeveloperFromProjectId, async (req, res) => {
|
||||||
|
const { caseIds, targetFolderId } = req.body;
|
||||||
|
|
||||||
|
if (!Array.isArray(caseIds) || caseIds.length === 0 || !targetFolderId) {
|
||||||
|
return res.status(400).json({ error: 'caseIds(array) and targetFolderId are required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const caseRecords = await Case.findAll({
|
||||||
|
where: { id: caseIds },
|
||||||
|
include: [{ model: Step, through: { attributes: ['stepNo'] } }],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (caseRecords.length !== caseIds.length) {
|
||||||
|
return res.status(404).json({ error: 'Some cases not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const cases = caseRecords.map((c) => c.get({ plain: true }));
|
||||||
|
|
||||||
|
const clonedCases = cases.map((c) => {
|
||||||
|
const { id: _id, createdAt: _createdAt, updatedAt: _updatedAt, ...clonedCase } = c;
|
||||||
|
return { ...clonedCase, folderId: targetFolderId };
|
||||||
|
});
|
||||||
|
|
||||||
|
await sequelize.transaction(async (t) => {
|
||||||
|
for (const c of clonedCases) {
|
||||||
|
const newCase = await Case.create(c, { transaction: t });
|
||||||
|
|
||||||
|
if (c.Steps) {
|
||||||
|
const clonedSteps = c.Steps.map((s) => {
|
||||||
|
const { id: _id, createdAt: _createdAt, updatedAt: _updatedAt, ...clonedStep } = s;
|
||||||
|
return clonedStep;
|
||||||
|
});
|
||||||
|
|
||||||
|
const newStep = await Step.bulkCreate(clonedSteps, { transaction: t });
|
||||||
|
const newCaseSteps = newStep.map((step, index) => ({
|
||||||
|
caseId: newCase.id,
|
||||||
|
stepId: step.id,
|
||||||
|
stepNo: clonedSteps[index].caseSteps.stepNo,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await CaseStep.bulkCreate(newCaseSteps, { transaction: t });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(200).json({ message: 'Cases cloned successfully' });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error cloning cases:', error);
|
||||||
|
res.status(500).send('Internal Server Error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return router;
|
||||||
|
}
|
||||||
106
backend/routes/folders/clone.js
Normal file
106
backend/routes/folders/clone.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import express from 'express';
|
||||||
|
const router = express.Router();
|
||||||
|
import { DataTypes } from 'sequelize';
|
||||||
|
import defineFolder from '../../models/folders.js';
|
||||||
|
import defineCase from '../../models/cases.js';
|
||||||
|
import defineStep from '../../models/steps.js';
|
||||||
|
import defineCaseStep from '../../models/caseSteps.js';
|
||||||
|
import authMiddleware from '../../middleware/auth.js';
|
||||||
|
import editableMiddleware from '../../middleware/verifyEditable.js';
|
||||||
|
|
||||||
|
export default function (sequelize) {
|
||||||
|
const { verifySignedIn } = authMiddleware(sequelize);
|
||||||
|
const { verifyProjectDeveloperFromFolderId } = editableMiddleware(sequelize);
|
||||||
|
|
||||||
|
const Folder = defineFolder(sequelize, DataTypes);
|
||||||
|
const Case = defineCase(sequelize, DataTypes);
|
||||||
|
const Step = defineStep(sequelize, DataTypes);
|
||||||
|
const CaseStep = defineCaseStep(sequelize, DataTypes);
|
||||||
|
Case.belongsTo(Folder);
|
||||||
|
Case.belongsToMany(Step, { through: 'caseSteps' });
|
||||||
|
Step.belongsToMany(Case, { through: 'caseSteps' });
|
||||||
|
|
||||||
|
async function _cloneFolderRecursive(sourceFolder, targetParent, transaction) {
|
||||||
|
const folderToCreate = {
|
||||||
|
name: sourceFolder.name,
|
||||||
|
detail: sourceFolder.detail,
|
||||||
|
parentFolderId: targetParent.id,
|
||||||
|
projectId: targetParent.projectId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const clonedFolder = await Folder.create(folderToCreate, { transaction });
|
||||||
|
|
||||||
|
await _cloneCasesAndSteps(sourceFolder.id, clonedFolder.id, transaction);
|
||||||
|
|
||||||
|
const childFolders = await Folder.findAll({
|
||||||
|
where: { parentFolderId: sourceFolder.id },
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const child of childFolders) {
|
||||||
|
await _cloneFolderRecursive(child, clonedFolder, transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clonedFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _cloneCasesAndSteps(folderId, targetFolderId, transaction) {
|
||||||
|
const folderCases = await Case.findAll({
|
||||||
|
where: { folderId },
|
||||||
|
include: [{ model: Step, through: { attributes: ['stepNo'] } }],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (folderCases.length === 0) return;
|
||||||
|
|
||||||
|
const cases = folderCases.map((c) => c.get({ plain: true }));
|
||||||
|
|
||||||
|
const clonedCases = cases.map((c) => {
|
||||||
|
const { id: _id, createdAt: _createdAt, updatedAt: _updatedAt, ...clonedCase } = c;
|
||||||
|
return { ...clonedCase, folderId: targetFolderId };
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const c of clonedCases) {
|
||||||
|
const newCase = await Case.create(c, { transaction });
|
||||||
|
|
||||||
|
if (c.Steps && c.Steps.length > 0) {
|
||||||
|
const clonedSteps = c.Steps.map((s) => {
|
||||||
|
const { id: _id, createdAt: _createdAt, updatedAt: _updatedAt, ...clonedStep } = s;
|
||||||
|
return clonedStep;
|
||||||
|
});
|
||||||
|
|
||||||
|
const newSteps = await Step.bulkCreate(clonedSteps, { transaction });
|
||||||
|
const caseSteps = newSteps.map((step, index) => ({
|
||||||
|
caseId: newCase.id,
|
||||||
|
stepId: step.id,
|
||||||
|
stepNo: clonedSteps[index].caseSteps.stepNo,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await CaseStep.bulkCreate(caseSteps, { transaction });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
router.post('/:folderId/clone', verifySignedIn, verifyProjectDeveloperFromFolderId, async (req, res) => {
|
||||||
|
const folderId = req.params.folderId;
|
||||||
|
const { targetFolderId } = req.body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sourceFolder = await Folder.findByPk(folderId);
|
||||||
|
const targetFolder = await Folder.findByPk(targetFolderId);
|
||||||
|
|
||||||
|
if (!sourceFolder || !targetFolder) {
|
||||||
|
return res.status(404).send('Folder or target folder not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
await sequelize.transaction(async (t) => {
|
||||||
|
await _cloneFolderRecursive(sourceFolder, targetFolder, t);
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(201).send({ message: 'Folder cloned successfully' });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).send('Internal Server Error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return router;
|
||||||
|
}
|
||||||
@@ -77,10 +77,12 @@ import foldersIndexRoute from './routes/folders/index.js';
|
|||||||
import foldersNewRoute from './routes/folders/new.js';
|
import foldersNewRoute from './routes/folders/new.js';
|
||||||
import foldersEditRoute from './routes/folders/edit.js';
|
import foldersEditRoute from './routes/folders/edit.js';
|
||||||
import foldersDeleteRoute from './routes/folders/delete.js';
|
import foldersDeleteRoute from './routes/folders/delete.js';
|
||||||
|
import foldersCloneRoute from './routes/folders/clone.js';
|
||||||
app.use('/folders', foldersIndexRoute(sequelize));
|
app.use('/folders', foldersIndexRoute(sequelize));
|
||||||
app.use('/folders', foldersNewRoute(sequelize));
|
app.use('/folders', foldersNewRoute(sequelize));
|
||||||
app.use('/folders', foldersEditRoute(sequelize));
|
app.use('/folders', foldersEditRoute(sequelize));
|
||||||
app.use('/folders', foldersDeleteRoute(sequelize));
|
app.use('/folders', foldersDeleteRoute(sequelize));
|
||||||
|
app.use('/folders', foldersCloneRoute(sequelize));
|
||||||
|
|
||||||
// "/cases"
|
// "/cases"
|
||||||
import casesDownloadRoute from './routes/cases/download.js';
|
import casesDownloadRoute from './routes/cases/download.js';
|
||||||
@@ -91,6 +93,7 @@ import casesShowRoute from './routes/cases/show.js';
|
|||||||
import casesNewRoute from './routes/cases/new.js';
|
import casesNewRoute from './routes/cases/new.js';
|
||||||
import casesEditRoute from './routes/cases/edit.js';
|
import casesEditRoute from './routes/cases/edit.js';
|
||||||
import casesDeleteRoute from './routes/cases/delete.js';
|
import casesDeleteRoute from './routes/cases/delete.js';
|
||||||
|
import casesCloneRoute from './routes/cases/clone.js';
|
||||||
app.use('/cases', casesDownloadRoute(sequelize));
|
app.use('/cases', casesDownloadRoute(sequelize));
|
||||||
app.use('/cases', casesMoveRoute(sequelize));
|
app.use('/cases', casesMoveRoute(sequelize));
|
||||||
app.use('/cases', casesIndexRoute(sequelize));
|
app.use('/cases', casesIndexRoute(sequelize));
|
||||||
@@ -99,6 +102,7 @@ app.use('/cases', casesShowRoute(sequelize));
|
|||||||
app.use('/cases', casesNewRoute(sequelize));
|
app.use('/cases', casesNewRoute(sequelize));
|
||||||
app.use('/cases', casesEditRoute(sequelize));
|
app.use('/cases', casesEditRoute(sequelize));
|
||||||
app.use('/cases', casesDeleteRoute(sequelize));
|
app.use('/cases', casesDeleteRoute(sequelize));
|
||||||
|
app.use('/cases', casesCloneRoute(sequelize));
|
||||||
|
|
||||||
// "/steps"
|
// "/steps"
|
||||||
import stepsEditRoute from './routes/steps/edit.js';
|
import stepsEditRoute from './routes/steps/edit.js';
|
||||||
|
|||||||
Reference in New Issue
Block a user