Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Commit

Permalink
feat(TestCase) : Testcase file manage + api spec
Browse files Browse the repository at this point in the history
  • Loading branch information
kasterra committed May 18, 2024
1 parent a6076eb commit 3baec73
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 10 deletions.
47 changes: 43 additions & 4 deletions app/API/testCase.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { API_SERVER_URL } from "~/util/constant";
import toast from "react-hot-toast";
import { TestcaseResponse } from "~/types/APIResponse";
import { handle401 } from "~/util";

export async function postNewTestcase(
problemId: number,
Expand Down Expand Up @@ -32,7 +33,7 @@ export async function postNewTestcase(
toast.error("입력값 검증이 실패하였습니다");
break;
case 401:
toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요");
handle401();
break;
case 403:
toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요");
Expand Down Expand Up @@ -60,7 +61,7 @@ export async function getTestcaseById(

switch (response.status) {
case 401:
toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요");
handle401();
break;
case 403:
toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요");
Expand Down Expand Up @@ -99,7 +100,7 @@ export async function updateTestcase(
toast.error("JWT 토큰이 없거나 입력값 검증이 실패하였습니다");
break;
case 401:
toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요");
handle401();
break;
case 403:
toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요");
Expand All @@ -122,7 +123,7 @@ export async function deleteTestcase(testcaseId: number, token: string) {

switch (response.status) {
case 401:
toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요");
handle401();
break;
case 403:
toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요");
Expand All @@ -136,3 +137,41 @@ export async function deleteTestcase(testcaseId: number, token: string) {
}
return { status: response.status };
}

export async function deleteFileInputFromTestCase(
testcaseId: number,
token: string,
fileName: string
) {
const response = { status: 404 };
switch (response.status) {
case 401:
handle401();
break;
case 403:
throw new Error("강의 소유 권한이 없습니다. 다시 확인해 주세요");
case 404:
throw new Error("현재 구현되어 있지 않은 API 입니다");
case 500:
throw new Error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요");
}
}

export async function deleteFileOutputFromTestCase(
testcaseId: number,
token: string,
fileName: string
) {
const response = { status: 404 };
switch (response.status) {
case 401:
handle401();
break;
case 403:
throw new Error("강의 소유 권한이 없습니다. 다시 확인해 주세요");
case 404:
throw new Error("현재 구현되어 있지 않은 API 입니다");
case 500:
throw new Error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ interface Props {
const TestCaseAddModal = ({ isOpen, onClose, problemId }: Props) => {
const auth = useAuth();
const [argvList, setArgvList] = useState<string[]>([]);
const [inputFiles, setInputFiles] = useState<FileList | null>(null);
const [outputFiles, setOutputFiles] = useState<FileList | null>(null);
return (
<Modal
title="테스트 케이스 추가"
Expand All @@ -34,6 +36,15 @@ const TestCaseAddModal = ({ isOpen, onClose, problemId }: Props) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);

inputFiles &&
[...inputFiles].forEach((file) =>
formData.append("file_inputs", file)
);
outputFiles &&
[...outputFiles].forEach((file) =>
formData.append("file_outputs", file)
);

argvList.forEach((argv) => formData.append("argv", argv));

const resposnse = await postNewTestcase(
Expand Down Expand Up @@ -132,12 +143,18 @@ const TestCaseAddModal = ({ isOpen, onClose, problemId }: Props) => {
/>
<MultipleFileInput
title="파일 입력"
name="file_inputs"
name="f_i"
placeholder="텍스트, 이진 파일 업로드"
onFileUpload={async (files) => {
setInputFiles(files);
}}
/>
<MultipleFileInput
title="파일 출력"
name="file_outputs"
name="f_o"
onFileUpload={async (files) => {
setOutputFiles(files);
}}
placeholder="텍스트, 이진 파일 업로드"
/>
<button type="submit" className={formStyles["primary-button"]}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import Modal from "~/components/Modal";
import styles from "./modal.module.css";
import formStyles from "~/components/common/form.module.css";
import { useEffect, useState } from "react";
import { getTestcaseById, updateTestcase } from "~/API/testCase";
import {
deleteFileInputFromTestCase,
deleteFileOutputFromTestCase,
getTestcaseById,
updateTestcase,
} from "~/API/testCase";
import { useAuth } from "~/contexts/AuthContext";
import {
SuccessTestcaseResponse,
Expand All @@ -16,6 +21,10 @@ import TextArea from "~/components/Input/TextArea";
import toast from "react-hot-toast";
import plusW from "~/assets/plus-w.svg";
import minusW from "~/assets/minus-w.svg";
import trash from "~/assets/trash.svg";
import download from "~/assets/download.svg";
import pkg from "file-saver";
const { saveAs } = pkg;

interface Props {
isOpen: boolean;
Expand All @@ -30,6 +39,8 @@ const TestCaseEditModal = ({ isOpen, onClose, testCaseId }: Props) => {
{} as TestcaseType
);
const [argvList, setArgvList] = useState<string[]>([]);
const [newInputFiles, setNewInputFiles] = useState<FileList | null>(null);
const [newOutputFiles, setNewOutputFiles] = useState<FileList | null>(null);

useEffect(() => {
async function getTestCaseFromServer() {
Expand All @@ -44,7 +55,7 @@ const TestCaseEditModal = ({ isOpen, onClose, testCaseId }: Props) => {
}

getTestCaseFromServer();
}, []);
}, [isLoading]);
return (
<Modal
title="테스트 케이스 수정"
Expand All @@ -63,6 +74,15 @@ const TestCaseEditModal = ({ isOpen, onClose, testCaseId }: Props) => {

argvList.forEach((argv) => formData.append("argv", argv));

newInputFiles &&
[...newInputFiles].forEach((file) =>
formData.append("file_inputs", file)
);
newOutputFiles &&
[...newOutputFiles].forEach((file) =>
formData.append("file_outputs", file)
);

const response = await updateTestcase(
testCaseId,
formData,
Expand Down Expand Up @@ -166,15 +186,105 @@ const TestCaseEditModal = ({ isOpen, onClose, testCaseId }: Props) => {
height={200}
defaultValue={testCaseData.stdout}
/>
<div className={styles.area}>
<h3 className={styles.title}>기존 파일 입력</h3>
<div className={styles["file-rows"]}>
{testCaseData.file_input!.map((file) => (
<div
className={styles["file-row"]}
onClick={() => {
saveAs(new File([file.content], file.name), file.name);
}}
>
<span className={styles["file-name"]}>{file.name}</span>
<div className={styles["icon-area"]}>
<div className={styles["icon"]}>
<img src={download} alt="download icon" />
</div>
<div
className={styles.icon}
onClick={async (e) => {
e.stopPropagation();
await toast.promise(
deleteFileInputFromTestCase(
testCaseId,
file.name,
auth.token
),
{
loading: "TC 파일 삭제중...",
success: "TC 파일 삭제완료!",
error: (err) => err.toString(),
}
);
setIsLoading(true);
}}
>
<img src={trash} alt="delete icon" />
</div>
</div>
</div>
))}
</div>
</div>
<MultipleFileInput
title="파일 입력"
title="파일 입력 추가"
name="file_inputs"
placeholder="텍스트, 이진 파일 업로드"
onFileUpload={async (files) => {
setNewInputFiles(files);
}}
description="기존 TC에 파일을 추가합니다. 이 입력으로는 기존 파일은 변경되지 않습니다"
/>
<div className={styles.area}>
<h3 className={styles.title}>기존 파일 출력</h3>
<div className={styles["file-rows"]}>
{testCaseData.file_output!.map((file) => (
<div
className={styles["file-row"]}
onClick={() => {
saveAs(new File([file.content], file.name), file.name);
}}
>
<span className={styles["file-name"]}>{file.name}</span>
<div className={styles["icon-area"]}>
<div className={styles["icon"]}>
<img src={download} alt="download icon" />
</div>
<div
className={styles.icon}
onClick={async (e) => {
e.stopPropagation();
await toast.promise(
deleteFileOutputFromTestCase(
testCaseId,
file.name,
auth.token
),
{
loading: "TC 파일 삭제중...",
success: "TC 파일 삭제완료!",
error: (err) => err.toString(),
}
);
setIsLoading(true);
}}
>
<img src={trash} alt="delete icon" />
</div>
</div>
</div>
))}
</div>
</div>
<MultipleFileInput
title="파일 출력"
title="파일 출력 추가"
name="file_outputs"
placeholder="텍스트, 이진 파일 업로드"
onFileUpload={async (files) => {
setNewOutputFiles(files);
}}
description="기존 TC에 파일을 추가합니다. 이 입력으로는 기존 파일은 변경되지 않습니다"
/>
<button type="submit" className={formStyles["primary-button"]}>
TC 저장
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,55 @@
width: 100%;
height: 100%;
}

.area {
display: flex;
flex-direction: column;
width: 100%;
gap: 10px;
}

.title {
color: #756f86;
font-family: IBMPlexSans;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: normal;
}

.file-rows {
display: flex;
flex-direction: column;
width: 100%;
gap: 2px;
}

.file-row {
display: flex;
height: 20px;
width: 100%;
justify-content: space-between;
align-items: center;
}

.file-name {
font-size: 20px;
}

.icon-area {
display: flex;
gap: 4px;
align-items: center;
}

.icon {
width: 30px;
height: 30px;
cursor: pointer;
}

.icon img {
width: 100%;
height: 100%;
}

0 comments on commit 3baec73

Please sign in to comment.