feat: inplement auth for cases
This commit is contained in:
@@ -66,13 +66,11 @@ const casesShowRoute = require('./routes/cases/show')(sequelize);
|
|||||||
const casesNewRoute = require('./routes/cases/new')(sequelize);
|
const casesNewRoute = require('./routes/cases/new')(sequelize);
|
||||||
const casesEditRoute = require('./routes/cases/edit')(sequelize);
|
const casesEditRoute = require('./routes/cases/edit')(sequelize);
|
||||||
const casesDeleteRoute = require('./routes/cases/delete')(sequelize);
|
const casesDeleteRoute = require('./routes/cases/delete')(sequelize);
|
||||||
const casesBulkDeleteRoute = require('./routes/cases/bulkDelete')(sequelize);
|
|
||||||
app.use('/cases', casesIndexRoute);
|
app.use('/cases', casesIndexRoute);
|
||||||
app.use('/cases', casesShowRoute);
|
app.use('/cases', casesShowRoute);
|
||||||
app.use('/cases', casesNewRoute);
|
app.use('/cases', casesNewRoute);
|
||||||
app.use('/cases', casesEditRoute);
|
app.use('/cases', casesEditRoute);
|
||||||
app.use('/cases', casesDeleteRoute);
|
app.use('/cases', casesDeleteRoute);
|
||||||
app.use('/cases', casesBulkDeleteRoute);
|
|
||||||
|
|
||||||
// "/steps"
|
// "/steps"
|
||||||
const stepsNewRoute = require('./routes/steps/new')(sequelize);
|
const stepsNewRoute = require('./routes/steps/new')(sequelize);
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
const express = require('express');
|
|
||||||
const router = express.Router();
|
|
||||||
const defineCase = require('../../models/cases');
|
|
||||||
const { DataTypes } = require('sequelize');
|
|
||||||
|
|
||||||
module.exports = function (sequelize) {
|
|
||||||
const Case = defineCase(sequelize, DataTypes);
|
|
||||||
|
|
||||||
router.post('/bulkdelete', async (req, res) => {
|
|
||||||
const { caseIds } = req.body;
|
|
||||||
if (!caseIds || !Array.isArray(caseIds)) {
|
|
||||||
return res.status(400).send('Invalid caseIds array');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await Case.destroy({ where: { id: caseIds } });
|
|
||||||
res.status(204).send();
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
res.status(500).send('Internal Server Error');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return router;
|
|
||||||
};
|
|
||||||
@@ -4,16 +4,17 @@ const defineCase = require('../../models/cases');
|
|||||||
const { DataTypes } = require('sequelize');
|
const { DataTypes } = require('sequelize');
|
||||||
|
|
||||||
module.exports = function (sequelize) {
|
module.exports = function (sequelize) {
|
||||||
|
const { verifySignedIn, verifyProjectDeveloper } = require('../../middleware/auth')(sequelize);
|
||||||
const Case = defineCase(sequelize, DataTypes);
|
const Case = defineCase(sequelize, DataTypes);
|
||||||
|
|
||||||
router.delete('/:caseId', async (req, res) => {
|
router.post('/bulkdelete', verifySignedIn, verifyProjectDeveloper, async (req, res) => {
|
||||||
const caseId = req.params.caseId;
|
const { caseIds } = req.body;
|
||||||
|
if (!caseIds || !Array.isArray(caseIds)) {
|
||||||
|
return res.status(400).send('Invalid caseIds array');
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const testcase = await Case.findByPk(caseId);
|
await Case.destroy({ where: { id: caseIds } });
|
||||||
if (!testcase) {
|
|
||||||
return res.status(404).send('Case not found');
|
|
||||||
}
|
|
||||||
await testcase.destroy();
|
|
||||||
res.status(204).send();
|
res.status(204).send();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ const defineStep = require('../../models/steps');
|
|||||||
const { DataTypes } = require('sequelize');
|
const { DataTypes } = require('sequelize');
|
||||||
|
|
||||||
module.exports = function (sequelize) {
|
module.exports = function (sequelize) {
|
||||||
|
const { verifySignedIn, verifyProjectDeveloper } = require('../../middleware/auth')(sequelize);
|
||||||
const Case = defineCase(sequelize, DataTypes);
|
const Case = defineCase(sequelize, DataTypes);
|
||||||
const Step = defineStep(sequelize, DataTypes);
|
const Step = defineStep(sequelize, DataTypes);
|
||||||
|
|
||||||
router.put('/:caseId', async (req, res) => {
|
router.put('/:caseId', verifySignedIn, verifyProjectDeveloper, async (req, res) => {
|
||||||
const caseId = req.params.caseId;
|
const caseId = req.params.caseId;
|
||||||
const updateCase = req.body;
|
const updateCase = req.body;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ const { DataTypes } = require('sequelize');
|
|||||||
|
|
||||||
module.exports = function (sequelize) {
|
module.exports = function (sequelize) {
|
||||||
const Case = defineCase(sequelize, DataTypes);
|
const Case = defineCase(sequelize, DataTypes);
|
||||||
|
const { verifySignedIn, verifyProjectVisible } = require('../../middleware/auth')(sequelize);
|
||||||
|
|
||||||
router.get('/', async (req, res) => {
|
router.get('/', verifySignedIn, verifyProjectVisible, async (req, res) => {
|
||||||
const { folderId } = req.query;
|
const { folderId } = req.query;
|
||||||
|
|
||||||
if (!folderId) {
|
if (!folderId) {
|
||||||
|
|||||||
@@ -14,9 +14,10 @@ function isEmpty(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = function (sequelize) {
|
module.exports = function (sequelize) {
|
||||||
|
const { verifySignedIn, verifyProjectDeveloper } = require('../../middleware/auth')(sequelize);
|
||||||
const Case = defineCase(sequelize, DataTypes);
|
const Case = defineCase(sequelize, DataTypes);
|
||||||
|
|
||||||
router.post('/', async (req, res) => {
|
router.post('/', verifySignedIn, verifyProjectDeveloper, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
requiredFields.some((field) => {
|
requiredFields.some((field) => {
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ module.exports = function (sequelize) {
|
|||||||
Step.belongsToMany(Case, { through: 'caseSteps' });
|
Step.belongsToMany(Case, { through: 'caseSteps' });
|
||||||
Case.belongsToMany(Attachment, { through: 'caseAttachments' });
|
Case.belongsToMany(Attachment, { through: 'caseAttachments' });
|
||||||
Attachment.belongsToMany(Case, { through: 'caseAttachments' });
|
Attachment.belongsToMany(Case, { through: 'caseAttachments' });
|
||||||
|
const { verifySignedIn, verifyProjectVisible } = require('../../middleware/auth')(sequelize);
|
||||||
|
|
||||||
router.get('/:caseId', async (req, res) => {
|
router.get('/:caseId', verifySignedIn, verifyProjectVisible, async (req, res) => {
|
||||||
const caseId = req.params.caseId;
|
const caseId = req.params.caseId;
|
||||||
|
|
||||||
if (!caseId) {
|
if (!caseId) {
|
||||||
|
|||||||
@@ -154,6 +154,8 @@
|
|||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
"delete_case": "Delete test case",
|
"delete_case": "Delete test case",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
|
"close": "Close",
|
||||||
|
"are_you_sure": "Are you sure you want to delete test cases?",
|
||||||
"new_test_case": "New Test Case",
|
"new_test_case": "New Test Case",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"critical": "Critical",
|
"critical": "Critical",
|
||||||
|
|||||||
@@ -153,6 +153,8 @@
|
|||||||
"actions": "アクション",
|
"actions": "アクション",
|
||||||
"delete_case": "テストケースを削除",
|
"delete_case": "テストケースを削除",
|
||||||
"delete": "削除",
|
"delete": "削除",
|
||||||
|
"close": "閉じる",
|
||||||
|
"are_you_sure": "テストケースを削除してもよろしいですか?",
|
||||||
"new_test_case": "新規テストケース",
|
"new_test_case": "新規テストケース",
|
||||||
"status": "ステータス",
|
"status": "ステータス",
|
||||||
"critical": "致",
|
"critical": "致",
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { useEffect, useState } from 'react';
|
import { useState, useEffect, useContext } from 'react';
|
||||||
|
import { TokenContext } from '@/utils/TokenProvider';
|
||||||
import TestCaseTable from './TestCaseTable';
|
import TestCaseTable from './TestCaseTable';
|
||||||
import { fetchCases, createCase, deleteCase, deleteCases } from './caseControl';
|
import { fetchCases, createCase, deleteCases } from './caseControl';
|
||||||
import { CasesMessages } from '@/types/case';
|
import { CaseType, CasesMessages } from '@/types/case';
|
||||||
|
import DeleteConfirmDialog from '@/components/DeleteConfirmDialog';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@@ -12,11 +14,18 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function CasesPane({ projectId, folderId, messages, locale }: Props) {
|
export default function CasesPane({ projectId, folderId, messages, locale }: Props) {
|
||||||
const [cases, setCases] = useState([]);
|
const [cases, setCases] = useState<CaseType[]>([]);
|
||||||
|
const context = useContext(TokenContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchDataEffect() {
|
async function fetchDataEffect() {
|
||||||
|
if (!context.isSignedIn()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const data = await fetchCases(folderId);
|
const data = await fetchCases(context.token.access_token, folderId);
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
setCases(data);
|
setCases(data);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error in effect:', error.message);
|
console.error('Error in effect:', error.message);
|
||||||
@@ -24,25 +33,39 @@ export default function CasesPane({ projectId, folderId, messages, locale }: Pro
|
|||||||
}
|
}
|
||||||
|
|
||||||
fetchDataEffect();
|
fetchDataEffect();
|
||||||
}, []);
|
}, [context, folderId]);
|
||||||
|
|
||||||
const handleCreateCase = async (folderId: string) => {
|
const handleCreateCase = async (folderId: string) => {
|
||||||
const newCase = await createCase(folderId);
|
const newCase = await createCase(context.token.access_token, folderId);
|
||||||
const updateCases = [...cases];
|
const updateCases = [...cases];
|
||||||
updateCases.push(newCase);
|
updateCases.push(newCase);
|
||||||
setCases(updateCases);
|
setCases(updateCases);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteCase = async (caseId: number) => {
|
// Delete confirm dialog
|
||||||
await deleteCase(caseId);
|
const [isDeleteConfirmDialogOpen, setIsDeleteConfirmDialogOpen] = useState(false);
|
||||||
const data = await fetchCases(folderId);
|
const [deleteCaseIds, setDeleteCaseIds] = useState<number[]>([]);
|
||||||
setCases(data);
|
const closeDeleteConfirmDialog = () => {
|
||||||
|
setIsDeleteConfirmDialogOpen(false);
|
||||||
|
setDeleteCaseIds([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteCases = async (deleteCaseIds: string[]) => {
|
const onDeleteCases = (deleteCaseIds: number[]) => {
|
||||||
await deleteCases(deleteCaseIds);
|
setDeleteCaseIds(deleteCaseIds);
|
||||||
const data = await fetchCases(folderId);
|
setIsDeleteConfirmDialogOpen(true);
|
||||||
setCases(data);
|
};
|
||||||
|
|
||||||
|
const onDeleteCase = async (deleteCaseId: number) => {
|
||||||
|
setDeleteCaseIds([deleteCaseId]);
|
||||||
|
setIsDeleteConfirmDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onConfirm = async () => {
|
||||||
|
if (deleteCaseIds.length > 0) {
|
||||||
|
await deleteCases(context.token.access_token, deleteCaseIds);
|
||||||
|
setCases(cases.filter((entry) => !deleteCaseIds.includes(entry.id)));
|
||||||
|
closeDeleteConfirmDialog();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -51,11 +74,20 @@ export default function CasesPane({ projectId, folderId, messages, locale }: Pro
|
|||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
cases={cases}
|
cases={cases}
|
||||||
onCreateCase={() => handleCreateCase(folderId)}
|
onCreateCase={() => handleCreateCase(folderId)}
|
||||||
onDeleteCase={handleDeleteCase}
|
onDeleteCase={() => onDeleteCase}
|
||||||
onDeleteCases={handleDeleteCases}
|
onDeleteCases={() => onDeleteCases}
|
||||||
messages={messages}
|
messages={messages}
|
||||||
locale={locale}
|
locale={locale}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<DeleteConfirmDialog
|
||||||
|
isOpen={isDeleteConfirmDialogOpen}
|
||||||
|
onCancel={closeDeleteConfirmDialog}
|
||||||
|
onConfirm={onConfirm}
|
||||||
|
closeText={messages.close}
|
||||||
|
confirmText={messages.areYouSure}
|
||||||
|
deleteText={messages.delete}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,28 +16,12 @@ import {
|
|||||||
} from '@nextui-org/react';
|
} from '@nextui-org/react';
|
||||||
import { Link, NextUiLinkClasses } from '@/src/navigation';
|
import { Link, NextUiLinkClasses } from '@/src/navigation';
|
||||||
import { Plus, MoreVertical, Trash, Circle } from 'lucide-react';
|
import { Plus, MoreVertical, Trash, Circle } from 'lucide-react';
|
||||||
import { CasesMessages } from '@/types/case';
|
import { CaseType, CasesMessages } from '@/types/case';
|
||||||
import { priorities } from '@/config/selection';
|
import { priorities } from '@/config/selection';
|
||||||
|
|
||||||
type Case = {
|
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
state: number;
|
|
||||||
priority: number;
|
|
||||||
type: number;
|
|
||||||
automationStatus: number;
|
|
||||||
description: string;
|
|
||||||
template: number;
|
|
||||||
preConditions: string;
|
|
||||||
expectedResults: string;
|
|
||||||
folderId: number;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
cases: Case[];
|
cases: CaseType[];
|
||||||
onCreateCase: () => void;
|
onCreateCase: () => void;
|
||||||
onDeleteCase: (caseId: number) => void;
|
onDeleteCase: (caseId: number) => void;
|
||||||
onDeleteCases: (selectedCases: string[]) => void;
|
onDeleteCases: (selectedCases: string[]) => void;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import Config from '@/config/config';
|
|||||||
const apiServer = Config.apiServer;
|
const apiServer = Config.apiServer;
|
||||||
import { CaseType } from '@/types/case';
|
import { CaseType } from '@/types/case';
|
||||||
|
|
||||||
async function fetchCase(caseId: number) {
|
async function fetchCase(jwt: string, caseId: number) {
|
||||||
const url = `${apiServer}/cases/${caseId}`;
|
const url = `${apiServer}/cases/${caseId}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -10,6 +10,7 @@ async function fetchCase(caseId: number) {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ async function fetchCase(caseId: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchCases(folderId: string) {
|
async function fetchCases(jwt: string, folderId: string) {
|
||||||
const url = `${apiServer}/cases?folderId=${folderId}`;
|
const url = `${apiServer}/cases?folderId=${folderId}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -32,6 +33,7 @@ async function fetchCases(folderId: string) {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -46,7 +48,7 @@ async function fetchCases(folderId: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createCase(folderId: string) {
|
async function createCase(jwt: string, folderId: string) {
|
||||||
const newCase = {
|
const newCase = {
|
||||||
title: 'untitled case',
|
title: 'untitled case',
|
||||||
state: 0,
|
state: 0,
|
||||||
@@ -64,6 +66,7 @@ async function createCase(folderId: string) {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(newCase),
|
body: JSON.stringify(newCase),
|
||||||
};
|
};
|
||||||
@@ -83,11 +86,12 @@ async function createCase(folderId: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateCase(updateCaseData: CaseType) {
|
async function updateCase(jwt: string, updateCaseData: CaseType) {
|
||||||
const fetchOptions = {
|
const fetchOptions = {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(updateCaseData),
|
body: JSON.stringify(updateCaseData),
|
||||||
};
|
};
|
||||||
@@ -107,34 +111,36 @@ async function updateCase(updateCaseData: CaseType) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteCase(caseId: number) {
|
// async function deleteCase(jwt: string, caseId: number) {
|
||||||
const fetchOptions = {
|
// const fetchOptions = {
|
||||||
method: 'DELETE',
|
// method: 'DELETE',
|
||||||
headers: {
|
// headers: {
|
||||||
'Content-Type': 'application/json',
|
// 'Content-Type': 'application/json',
|
||||||
},
|
// Authorization: `Bearer ${jwt}`,
|
||||||
};
|
// },
|
||||||
|
// };
|
||||||
|
|
||||||
const url = `${apiServer}/cases/${caseId}`;
|
// const url = `${apiServer}/cases/${caseId}`;
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
const response = await fetch(url, fetchOptions);
|
// const response = await fetch(url, fetchOptions);
|
||||||
if (!response.ok) {
|
// if (!response.ok) {
|
||||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
// throw new Error(`HTTP error! Status: ${response.status}`);
|
||||||
}
|
// }
|
||||||
} catch (error: any) {
|
// } catch (error: any) {
|
||||||
console.error('Error deleting case:', error);
|
// console.error('Error deleting case:', error);
|
||||||
throw error;
|
// throw error;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
async function deleteCases(deleteCases: string[]) {
|
async function deleteCases(jwt: string, deleteCaseIds: number[]) {
|
||||||
const fetchOptions = {
|
const fetchOptions = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${jwt}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ caseIds: deleteCases }),
|
body: JSON.stringify({ caseIds: deleteCaseIds }),
|
||||||
};
|
};
|
||||||
|
|
||||||
const url = `${apiServer}/cases/bulkdelete`;
|
const url = `${apiServer}/cases/bulkdelete`;
|
||||||
@@ -150,4 +156,4 @@ async function deleteCases(deleteCases: string[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { fetchCase, fetchCases, updateCase, createCase, deleteCase, deleteCases };
|
export { fetchCase, fetchCases, updateCase, createCase, deleteCases };
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export default function Page({ params }: { params: { projectId: string; folderId
|
|||||||
actions: t('actions'),
|
actions: t('actions'),
|
||||||
deleteCase: t('delete_case'),
|
deleteCase: t('delete_case'),
|
||||||
delete: t('delete'),
|
delete: t('delete'),
|
||||||
|
close: t('close'),
|
||||||
|
areYouSure: t('are_you_sure'),
|
||||||
newTestCase: t('new_test_case'),
|
newTestCase: t('new_test_case'),
|
||||||
status: t('status'),
|
status: t('status'),
|
||||||
critical: t('critical'),
|
critical: t('critical'),
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ export type CasesMessages = {
|
|||||||
priority: string;
|
priority: string;
|
||||||
actions: string;
|
actions: string;
|
||||||
deleteCase: string;
|
deleteCase: string;
|
||||||
|
close: string;
|
||||||
|
areYouSure: string;
|
||||||
delete: string;
|
delete: string;
|
||||||
newTestCase: string;
|
newTestCase: string;
|
||||||
status: string;
|
status: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user