feat: inplement auth for cases

This commit is contained in:
Takeshi Kimata
2024-06-03 22:51:26 +09:00
parent e4db79047a
commit fe8b82cb4b
14 changed files with 106 additions and 98 deletions

View File

@@ -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);

View File

@@ -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;
};

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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) => {

View File

@@ -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) {

View File

@@ -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",

View File

@@ -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": "致",

View File

@@ -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}
/>
</> </>
); );
} }

View File

@@ -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;

View File

@@ -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 };

View File

@@ -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'),

View File

@@ -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;