diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js index c223d65..29452a9 100644 --- a/backend/middleware/auth.js +++ b/backend/middleware/auth.js @@ -5,6 +5,7 @@ const defineUser = require('../models/users'); const defineMember = require('../models/members'); const defineProject = require('../models/projects'); const defineFolder = require('../models/folders'); +const defineCase = require('../models/cases'); function authMiddleware(sequelize) { /** @@ -56,12 +57,24 @@ function authMiddleware(sequelize) { */ async function verifyProjectVisible(req, res, next) { const Project = defineProject(sequelize, DataTypes); + const Folder = defineFolder(sequelize, DataTypes); const Member = defineMember(sequelize, DataTypes); Project.hasMany(Member, { foreignKey: 'projectId' }); - const projectId = req.params.projectId || req.query.projectId; + let projectId = req.params.projectId || req.query.projectId; + const folderId = req.params.folderId || req.query.folderId; + if (!projectId && !folderId) { + return res.status(400).json({ error: 'projectId or folderId is required' }); + } + if (!projectId) { - return res.status(400).json({ error: 'projectId is required' }); + // find project id from folderId + const folder = await Folder.findByPk(folderId); + if (folder && folder.projectId) { + projectId = folder.projectId; + } else { + return res.status(404).send('failed to find project from folderId'); + } } const project = await Project.findOne({ @@ -173,22 +186,40 @@ function authMiddleware(sequelize) { async function verifyProjectDeveloper(req, res, next) { const Project = defineProject(sequelize, DataTypes); const Folder = defineFolder(sequelize, DataTypes); + const Case = defineCase(sequelize, DataTypes); const Member = defineMember(sequelize, DataTypes); Project.hasMany(Member, { foreignKey: 'projectId' }); + Folder.hasMany(Case, { foreignKey: 'folderId' }); let projectId = req.params.projectId || req.query.projectId; const folderId = req.params.folderId || req.query.folderId; - if (!projectId && !folderId) { - return res.status(400).json({ error: 'projectId or folderId is required' }); + const caseId = req.params.caseId || req.query.caseId; + if (!projectId && !folderId && !caseId) { + return res.status(400).json({ error: 'projectId, folderId or caseId is required' }); } if (!projectId) { - // find project id from folderId - const folder = await Folder.findByPk(folderId); - if (folder && folder.projectId) { - projectId = folder.projectId; + if (folderId) { + // find project id from folderId + const folder = await Folder.findByPk(folderId); + if (folder && folder.projectId) { + projectId = folder.projectId; + } else { + return res.status(404).send('failed to find project from folderId'); + } + } + } else if (caseId) { + // find project id from caseId + const testCase = await Case.findByPk(caseId, { + include: { + model: Folder, + include: Project, + }, + }); + if (testCase && testCase.Folder && testCase.Folder.Project) { + projectId = testCase.Folder.Project.id; } else { - return res.status(404).send('failed to find project from folderId'); + return res.status(404).send('Failed to find project from caseId'); } } diff --git a/backend/routes/cases/new.js b/backend/routes/cases/new.js index d83643c..d9239c6 100644 --- a/backend/routes/cases/new.js +++ b/backend/routes/cases/new.js @@ -3,7 +3,7 @@ const router = express.Router(); const defineCase = require('../../models/cases'); const { DataTypes } = require('sequelize'); -const requiredFields = ['title', 'state', 'priority', 'type', 'automationStatus', 'template', 'folderId']; +const requiredFields = ['title', 'state', 'priority', 'type', 'automationStatus', 'template']; function isEmpty(value) { if (value === null || value === undefined) { @@ -18,6 +18,8 @@ module.exports = function (sequelize) { const Case = defineCase(sequelize, DataTypes); router.post('/', verifySignedIn, verifyProjectDeveloper, async (req, res) => { + const folderId = req.query.folderId; + try { if ( requiredFields.some((field) => { @@ -25,22 +27,12 @@ module.exports = function (sequelize) { }) ) { return res.status(400).json({ - error: 'Title, state, priority, type, automationStatus, template, and folderId are required', + error: 'Title, state, priority, type, automationStatus, and template are required', }); } - const { - title, - state, - priority, - type, - automationStatus, - description, - template, - preConditions, - expectedResults, - folderId, - } = req.body; + const { title, state, priority, type, automationStatus, description, template, preConditions, expectedResults } = + req.body; const newCase = await Case.create({ title, diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/CasesPane.tsx b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/CasesPane.tsx index 65355bb..069c8af 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/CasesPane.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/CasesPane.tsx @@ -24,8 +24,6 @@ export default function CasesPane({ projectId, folderId, messages, locale }: Pro } try { const data = await fetchCases(context.token.access_token, folderId); - console.log(data); - setCases(data); } catch (error: any) { console.error('Error in effect:', error.message); @@ -50,19 +48,19 @@ export default function CasesPane({ projectId, folderId, messages, locale }: Pro setDeleteCaseIds([]); }; - const onDeleteCases = (deleteCaseIds: number[]) => { - setDeleteCaseIds(deleteCaseIds); - setIsDeleteConfirmDialogOpen(true); - }; - const onDeleteCase = async (deleteCaseId: number) => { setDeleteCaseIds([deleteCaseId]); setIsDeleteConfirmDialogOpen(true); }; + const onDeleteCases = (deleteCaseIds: number[]) => { + setDeleteCaseIds(deleteCaseIds); + setIsDeleteConfirmDialogOpen(true); + }; + const onConfirm = async () => { if (deleteCaseIds.length > 0) { - await deleteCases(context.token.access_token, deleteCaseIds); + await deleteCases(context.token.access_token, deleteCaseIds, projectId); setCases(cases.filter((entry) => !deleteCaseIds.includes(entry.id))); closeDeleteConfirmDialog(); } @@ -74,8 +72,8 @@ export default function CasesPane({ projectId, folderId, messages, locale }: Pro projectId={projectId} cases={cases} onCreateCase={() => handleCreateCase(folderId)} - onDeleteCase={() => onDeleteCase} - onDeleteCases={() => onDeleteCases} + onDeleteCase={onDeleteCase} + onDeleteCases={onDeleteCases} messages={messages} locale={locale} /> diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/TestCaseTable.tsx b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/TestCaseTable.tsx index 6a61c37..c55d04e 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/TestCaseTable.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/TestCaseTable.tsx @@ -24,7 +24,7 @@ type Props = { cases: CaseType[]; onCreateCase: () => void; onDeleteCase: (caseId: number) => void; - onDeleteCases: (selectedCases: string[]) => void; + onDeleteCases: (caseIds: number[]) => void; messages: CasesMessages; locale: string; }; @@ -61,6 +61,10 @@ export default function TestCaseTable({ }); }, [sortDescriptor, cases]); + const handleDeleteCase = (deleteCaseId: number) => { + onDeleteCase(deleteCaseId); + }; + const renderCell = useCallback((testCase: Case, columnKey: Key) => { const cellValue = testCase[columnKey as keyof Case]; @@ -95,7 +99,7 @@ export default function TestCaseTable({ - onDeleteCase(testCase.id)}> + handleDeleteCase(testCase.id)}> {messages.deleteCase} @@ -106,6 +110,17 @@ export default function TestCaseTable({ } }, []); + const handleDeleteCases = () => { + let deleteCaseIds: number[]; + if (selectedKeys === 'all') { + deleteCaseIds = sortedItems.map((item) => item.id); + } else { + deleteCaseIds = Array.from(selectedKeys).map(Number); + } + onDeleteCases(deleteCaseIds); + setSelectedKeys(new Set([])); + }; + const classNames = useMemo( () => ({ wrapper: ['max-w-3xl'], @@ -125,16 +140,6 @@ export default function TestCaseTable({ [] ); - const onDeleteCasesClick = async () => { - if (selectedKeys === 'all') { - const allKeys = sortedItems.map((item) => item.id); - onDeleteCases(allKeys); - } else { - onDeleteCases([...selectedKeys]); - } - setSelectedKeys(new Set([])); - }; - return ( <>
@@ -146,12 +151,12 @@ export default function TestCaseTable({ size="sm" color="danger" className="me-2" - onClick={onDeleteCasesClick} + onPress={handleDeleteCases} > {messages.delete} )} -
diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/caseControl.ts b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/caseControl.ts index f6bfc28..a2870e7 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/caseControl.ts +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/caseControl.ts @@ -59,7 +59,6 @@ async function createCase(jwt: string, folderId: string) { template: 0, preConditions: '', expectedResults: '', - folderId: folderId, }; const fetchOptions = { @@ -71,7 +70,7 @@ async function createCase(jwt: string, folderId: string) { body: JSON.stringify(newCase), }; - const url = `${apiServer}/cases`; + const url = `${apiServer}/cases?folderId=${folderId}`; try { const response = await fetch(url, fetchOptions); @@ -111,29 +110,7 @@ async function updateCase(jwt: string, updateCaseData: CaseType) { } } -// async function deleteCase(jwt: string, caseId: number) { -// const fetchOptions = { -// method: 'DELETE', -// headers: { -// 'Content-Type': 'application/json', -// Authorization: `Bearer ${jwt}`, -// }, -// }; - -// const url = `${apiServer}/cases/${caseId}`; - -// try { -// const response = await fetch(url, fetchOptions); -// if (!response.ok) { -// throw new Error(`HTTP error! Status: ${response.status}`); -// } -// } catch (error: any) { -// console.error('Error deleting case:', error); -// throw error; -// } -// } - -async function deleteCases(jwt: string, deleteCaseIds: number[]) { +async function deleteCases(jwt: string, deleteCaseIds: number[], projectId: number) { const fetchOptions = { method: 'POST', headers: { @@ -143,7 +120,7 @@ async function deleteCases(jwt: string, deleteCaseIds: number[]) { body: JSON.stringify({ caseIds: deleteCaseIds }), }; - const url = `${apiServer}/cases/bulkdelete`; + const url = `${apiServer}/cases/bulkdelete?projectId=${projectId}`; try { const response = await fetch(url, fetchOptions);