From 121fa7de550176567de060200258d801939a8eb1 Mon Sep 17 00:00:00 2001 From: Takeshi Kimata <117462761+kimatata@users.noreply.github.com> Date: Sat, 8 Jun 2024 18:46:23 +0900 Subject: [PATCH] feat: inplement auth case steps --- backend/routes/steps/delete.js | 7 +-- backend/routes/steps/new.js | 6 ++- .../cases/[caseId]/CaseAttachmentsEditor.tsx | 47 +++++++++++++++++-- .../[folderId]/cases/[caseId]/CaseEditor.tsx | 30 +++--------- .../cases/[caseId]/CaseStepsEditor.tsx | 5 +- .../[folderId]/cases/[caseId]/stepControl.ts | 10 ++-- frontend/types/case.ts | 1 + 7 files changed, 70 insertions(+), 36 deletions(-) diff --git a/backend/routes/steps/delete.js b/backend/routes/steps/delete.js index f007129..95360be 100644 --- a/backend/routes/steps/delete.js +++ b/backend/routes/steps/delete.js @@ -7,11 +7,12 @@ const { DataTypes, Op } = require('sequelize'); module.exports = function (sequelize) { const Step = defineStep(sequelize, DataTypes); const CaseStep = defineCaseStep(sequelize, DataTypes); + const { verifySignedIn } = require('../../middleware/auth')(sequelize); + const { verifyProjectDeveloperFromCaseId } = require('../../middleware/verifyEditable')(sequelize); - router.delete('/:stepId', async (req, res) => { + router.delete('/:stepId', verifySignedIn, verifyProjectDeveloperFromCaseId, async (req, res) => { const stepId = req.params.stepId; - // TODO The caseId should not be specified from the front end, but should be traced from stepId by association. - const caseId = req.query.parentCaseId; + const caseId = req.query.caseId; const t = await sequelize.transaction(); diff --git a/backend/routes/steps/new.js b/backend/routes/steps/new.js index 825897a..0089553 100644 --- a/backend/routes/steps/new.js +++ b/backend/routes/steps/new.js @@ -7,10 +7,12 @@ const { DataTypes, Op } = require('sequelize'); module.exports = function (sequelize) { const Step = defineStep(sequelize, DataTypes); const CaseStep = defineCaseStep(sequelize, DataTypes); + const { verifySignedIn } = require('../../middleware/auth')(sequelize); + const { verifyProjectDeveloperFromCaseId } = require('../../middleware/verifyEditable')(sequelize); - router.post('/', async (req, res) => { + router.post('/', verifySignedIn, verifyProjectDeveloperFromCaseId, async (req, res) => { const newStepNo = req.query.newStepNo; - const caseId = req.query.parentCaseId; + const caseId = req.query.caseId; const t = await sequelize.transaction(); diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseAttachmentsEditor.tsx b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseAttachmentsEditor.tsx index 3ad2008..0255db7 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseAttachmentsEditor.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseAttachmentsEditor.tsx @@ -1,23 +1,30 @@ import { Image, Button, Tooltip, Card, CardBody } from '@nextui-org/react'; import { AttachmentType, CaseMessages } from '@/types/case'; -import { Trash, ArrowDownToLine } from 'lucide-react'; +import { Trash, ArrowDownToLine, ArrowUpFromLine } from 'lucide-react'; import { isImage } from './isImage'; +import { ChangeEvent, DragEvent } from 'react'; type Props = { + isDisabled: boolean; attachments: AttachmentType[]; onAttachmentDownload: (attachmentId: number, downloadFileName: string) => void; onAttachmentDelete: (attachmentId: number) => void; + onFilesDrop: (event: DragEvent) => void; + onFilesInput: (event: ChangeEvent) => void; messages: CaseMessages; }; export default function CaseAttachmentsEditor({ + isDisabled = false, attachments = [], onAttachmentDownload, onAttachmentDelete, + onFilesDrop, + onFilesInput, messages, }: Props) { - let images = []; - let others = []; + let images: AttachmentType[] = []; + let others: AttachmentType[] = []; attachments.forEach((attachment) => { if (isImage(attachment)) { @@ -39,6 +46,7 @@ export default function CaseAttachmentsEditor({ onAttachmentDelete(image.id)} > @@ -82,6 +90,39 @@ export default function CaseAttachmentsEditor({ ))} + + { + if (isDisabled) { + return; + } + onFilesDrop(event); + }} + onDragOver={(event) => event.preventDefault()} + > + + + + + {messages.clickToUpload} + {messages.orDragAndDrop} + + {messages.maxFileSize}: 50 MB + + onFilesInput(e)} + multiple + /> + + > ); } diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseEditor.tsx b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseEditor.tsx index 589abb3..b5cba96 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseEditor.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseEditor.tsx @@ -2,7 +2,7 @@ import { useState, useEffect, useContext } from 'react'; import { Input, Textarea, Select, SelectItem, Button, Divider, Tooltip } from '@nextui-org/react'; import { useRouter } from '@/src/navigation'; -import { Save, Plus, ArrowLeft, ArrowUpFromLine, Circle } from 'lucide-react'; +import { Save, Plus, ArrowLeft, Circle } from 'lucide-react'; import { priorities, testTypes, templates } from '@/config/selection'; import CaseStepsEditor from './CaseStepsEditor'; import CaseAttachmentsEditor from './CaseAttachmentsEditor'; @@ -46,7 +46,7 @@ export default function CaseEditor({ projectId, folderId, caseId, messages, loca const router = useRouter(); const onPlusClick = async (newStepNo: number) => { - const newStep = await fetchCreateStep(newStepNo, Number(caseId)); + const newStep = await fetchCreateStep(context.token.access_token, newStepNo, Number(caseId)); if (newStep) { newStep.caseSteps = { stepNo: newStepNo }; const updatedSteps = testCase.Steps.map((step) => { @@ -80,7 +80,7 @@ export default function CaseEditor({ projectId, folderId, caseId, messages, loca const deletedStepNo = deletedStep.caseSteps.stepNo; // delete request - await fetchDeleteStep(stepId, caseId); + await fetchDeleteStep(context.token.access_token, stepId, Number(caseId)); const updatedSteps = testCase.Steps.map((step) => { if (step.caseSteps.stepNo > deletedStepNo) { @@ -326,6 +326,7 @@ export default function CaseEditor({ projectId, folderId, caseId, messages, loca { setTestCase({ @@ -349,33 +350,16 @@ export default function CaseEditor({ projectId, folderId, caseId, messages, loca {messages.attachments} fetchDownloadAttachment(attachmentId, downloadFileName) } onAttachmentDelete={onAttachmentDelete} + onFilesDrop={handleDrop} + onFilesInput={handleInput} messages={messages} /> - event.preventDefault()} - > - - - - - {messages.clickToUpload} - {messages.orDragAndDrop} - - {messages.maxFileSize}: 50 MB - - - - > ); diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseStepsEditor.tsx b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseStepsEditor.tsx index 4f53030..77cf918 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseStepsEditor.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseStepsEditor.tsx @@ -3,6 +3,7 @@ import { CaseMessages, StepType } from '@/types/case'; import { Plus, Trash } from 'lucide-react'; type Props = { + isDisabled: boolean; steps: StepType[]; onStepUpdate: (stepId: number, step: StepType) => void; onStepPlus: (newStepNo: number) => void; @@ -10,7 +11,7 @@ type Props = { messages: CaseMessages; }; -export default function StepsEditor({ steps, onStepUpdate, onStepPlus, onStepDelete, messages }: Props) { +export default function StepsEditor({ isDisabled, steps, onStepUpdate, onStepPlus, onStepDelete, messages }: Props) { // sort steps by junction table's column const sortedSteps = steps.slice().sort((a, b) => { const stepNoA = a.caseSteps.stepNo; @@ -51,6 +52,7 @@ export default function StepsEditor({ steps, onStepUpdate, onStepPlus, onStepDel onStepDelete(step.id)} > @@ -60,6 +62,7 @@ export default function StepsEditor({ steps, onStepUpdate, onStepPlus, onStepDel onStepPlus(step.caseSteps.stepNo + 1)} diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/stepControl.ts b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/stepControl.ts index 83147fe..7058a48 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/stepControl.ts +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/stepControl.ts @@ -1,15 +1,16 @@ import Config from '@/config/config'; const apiServer = Config.apiServer; -async function fetchCreateStep(newStepNo: number, parentCaseId: number) { +async function fetchCreateStep(jwt: string, newStepNo: number, parentCaseId: number) { const fetchOptions = { method: 'POST', headers: { 'Content-Type': 'application/json', + Authorization: `Bearer ${jwt}`, }, }; - const url = `${apiServer}/steps?newStepNo=${newStepNo}&parentCaseId=${parentCaseId}`; + const url = `${apiServer}/steps?newStepNo=${newStepNo}&caseId=${parentCaseId}`; try { const response = await fetch(url, fetchOptions); @@ -23,15 +24,16 @@ async function fetchCreateStep(newStepNo: number, parentCaseId: number) { } } -async function fetchDeleteStep(stepId: number, parentCaseId: number) { +async function fetchDeleteStep(jwt: string, stepId: number, parentCaseId: number) { const fetchOptions = { method: 'DELETE', headers: { 'Content-Type': 'application/json', + Authorization: `Bearer ${jwt}`, }, }; - const url = `${apiServer}/steps/${stepId}?parentCaseId=${parentCaseId}`; + const url = `${apiServer}/steps/${stepId}?caseId=${parentCaseId}`; try { const response = await fetch(url, fetchOptions); diff --git a/frontend/types/case.ts b/frontend/types/case.ts index 1d67fba..5b53466 100644 --- a/frontend/types/case.ts +++ b/frontend/types/case.ts @@ -21,6 +21,7 @@ type CaseStepType = { updatedAt: Date; CaseId: number; StepId: number; + stepNo: number; }; type StepType = {
+ {messages.clickToUpload} + {messages.orDragAndDrop} +
{messages.maxFileSize}: 50 MB
- {messages.clickToUpload} - {messages.orDragAndDrop} -