From 63e79db929c3d05b3aa4c52fbe4bf8f6f7b355ee Mon Sep 17 00:00:00 2001 From: kasterra Date: Mon, 20 May 2024 17:35:19 +0900 Subject: [PATCH] refactor: decouple error handling from API utility functions - API utility functions now throw errors instead of handling toasts - Error rendering moved to toast.promise in components - Improved separation of concerns and reusability - Updated all relevant API functions & components to adhere to the new error handling strategy --- app/API/admin.ts | 25 +- app/API/lecture.ts | 251 +++++++++++------- app/API/media.ts | 29 +- app/API/practice.ts | 66 +++-- app/API/problem.ts | 103 ++++--- app/API/submission.ts | 130 +++++---- app/API/testCase.ts | 96 ++++--- app/API/user.ts | 171 +++++++++--- app/components/Header/UserInfo.tsx | 19 +- app/contexts/AuthContext.tsx | 18 +- app/routes/_noAuthOnly+/_index.tsx | 8 +- .../$practiceId+/SubmissionRecordModal.tsx | 33 +-- .../grade+/$lectureId+/$practiceId+/index.tsx | 95 ++++--- .../_procted+/grade+/$lectureId+/_layout.tsx | 13 +- .../_procted+/grade+/$lectureId+/index.tsx | 71 +++-- app/routes/_procted+/grade+/index.tsx | 75 +++--- .../$practiceId+/$labId/SubmitModal.tsx | 12 +- .../$lectureId+/$practiceId+/$labId/index.tsx | 32 +-- .../_layout/DownloadMyCodesModal.tsx | 80 +++--- .../_layout/ImportPracticeModal.tsx | 44 ++- .../$lectureId+/_layout/PracticeEditModal.tsx | 18 +- .../$lectureId+/_layout/ProblemAddModal.tsx | 62 +++-- .../$lectureId+/_layout/ProblemEditModal.tsx | 95 ++++--- .../$lectureId+/_layout/TestCaseAddModal.tsx | 18 +- .../$lectureId+/_layout/TestCaseEditModal.tsx | 43 +-- .../lectures+/$lectureId+/_layout/index.tsx | 112 ++++---- .../lectures+/_index/LectureAddModal.tsx | 42 +-- .../lectures+/_index/LectureEditModal.tsx | 47 ++-- .../_procted+/lectures+/_index/index.tsx | 91 ++++--- .../history+/SubmissionDetailModal.tsx | 14 +- .../$lectureId+/$labId+/history+/index.tsx | 156 +++++------ .../_procted+/students+/$lectureId+/Table.tsx | 112 +++----- .../students+/$lectureId+/UserAddModal.tsx | 40 +-- app/routes/_procted+/students+/index.tsx | 22 +- app/routes/admin+/_layout.tsx | 16 +- app/routes/admin+/semester-manage.tsx | 23 +- .../admin+/users/AdminTableRowDataContext.tsx | 2 +- app/routes/admin+/users/Table.tsx | 81 ++---- app/routes/admin+/users/UserAddModal.tsx | 28 +- app/types/APIResponse.ts | 115 ++++---- app/types/index.d.ts | 16 ++ app/util/errors.ts | 57 ++++ app/util/getMyAllCodes.ts | 7 +- 43 files changed, 1375 insertions(+), 1213 deletions(-) create mode 100644 app/util/errors.ts diff --git a/app/API/admin.ts b/app/API/admin.ts index d94352c..31b1f78 100644 --- a/app/API/admin.ts +++ b/app/API/admin.ts @@ -1,7 +1,12 @@ import { API_SERVER_URL } from "~/util/constant"; -import toast from "react-hot-toast"; +import { handle401 } from "~/util"; +import { EmptyResponse } from "~/types/APIResponse"; +import { BadRequestError, ForbiddenError } from "~/util/errors"; -export async function setSemester(semester: number, token: string) { +export async function setSemester( + semester: number, + token: string +): Promise { const response = await fetch(`${API_SERVER_URL}/semester`, { method: "PUT", headers: { @@ -13,19 +18,21 @@ export async function setSemester(semester: number, token: string) { switch (response.status) { case 400: - toast.error("JWT토큰이 없거나 입력값 검증 실패"); + throw new BadRequestError("JWT토큰이 없거나 입력값 검증 실패"); break; case 401: - toast.error("유효하지 않은 JWT 토큰"); + handle401(); break; case 403: - toast.error("관리자만 접근할 수 있는 API 입니다"); + throw new ForbiddenError("관리자만 접근할 수 있는 API 입니다"); break; } return { ...(await response.json()), status: response.status }; } -export async function getSemester(token: string) { +export async function getSemester( + token: string +): Promise<{ semester: number }> { const response = await fetch(`${API_SERVER_URL}/semester`, { method: "GET", headers: { @@ -36,13 +43,13 @@ export async function getSemester(token: string) { switch (response.status) { case 400: - toast.error("JWT토큰이 없거나 입력값 검증 실패"); + throw new BadRequestError("JWT토큰이 없거나 입력값 검증 실패"); break; case 401: - toast.error("유효하지 않은 JWT 토큰"); + handle401(); break; case 403: - toast.error("관리자만 접근할 수 있는 API 입니다"); + throw new ForbiddenError("관리자만 접근할 수 있는 API 입니다"); break; } return { ...(await response.json()), status: response.status }; diff --git a/app/API/lecture.ts b/app/API/lecture.ts index 60663fb..0d2adf6 100644 --- a/app/API/lecture.ts +++ b/app/API/lecture.ts @@ -1,13 +1,22 @@ import { API_SERVER_URL } from "~/util/constant"; import { - AllPracticeResponse, + AllPracticesResponse, + EmptyResponse, + LectureResponse, LecturesResponse, ProblemDetailResponse, UserSearchResponse, } from "~/types/APIResponse"; -import toast from "react-hot-toast"; import { studentRow } from "~/types"; import { handle401 } from "~/util"; +import { + BadRequestError, + ConflictError, + ForbiddenError, + InternalServerError, + NotFoundError, + UnauthorizedError, +} from "~/util/errors"; export async function getFutureSemesterLectures( userId: string, @@ -24,24 +33,27 @@ export async function getFutureSemesterLectures( } ); + const data = await response.json(); + switch (response.status) { case 400: - toast.error("올바르지 않은 입력입니다!"); - break; + throw new BadRequestError(data.message); case 401: handle401(); break; case 403: - toast.error("권한이 없습니다!"); + throw new UnauthorizedError(data.message); break; case 404: - toast.error("요청하는 사용자의 ID가 존재하지 않습니다"); + throw new NotFoundError("요청하는 사용자의 ID가 존재하지 않습니다"); break; case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); break; } - return { ...(await response.json()), status: response.status }; + return data; } export async function getCurrentSemesterLectures( @@ -59,24 +71,26 @@ export async function getCurrentSemesterLectures( } ); + const data = await response.json(); + switch (response.status) { case 400: - toast.error("올바르지 않은 입력입니다!"); + throw new BadRequestError(data.message); break; case 401: handle401(); break; case 403: - toast.error("권한이 없습니다!"); + throw new UnauthorizedError(data.message); break; case 404: - toast.error("요청하는 사용자의 ID가 존재하지 않습니다"); + throw new Error("요청하는 사용자의 ID가 존재하지 않습니다"); break; case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new Error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); break; } - return { ...(await response.json()), status: response.status }; + return data; } export async function getPreviousSemesterLectures( @@ -94,24 +108,24 @@ export async function getPreviousSemesterLectures( } ); + const data = await response.json(); + switch (response.status) { case 400: - toast.error("올바르지 않은 입력입니다!"); - break; + throw new BadRequestError("올바르지 않은 입력입니다!"); case 401: handle401(); break; case 403: - toast.error("권한이 없습니다!"); - break; + throw new UnauthorizedError("권한이 없습니다!"); case 404: - toast.error("요청하는 사용자의 ID가 존재하지 않습니다"); - break; + throw new NotFoundError("요청하는 사용자의 ID가 존재하지 않습니다"); case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); - break; + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); } - return { ...(await response.json()), status: response.status }; + return data; } export async function postNewLecture( @@ -120,22 +134,18 @@ export async function postNewLecture( semester: number, title: string, token: string -) { +): Promise { if (!code) { - toast.error("강의 코드는 필수 입력 필드입니다"); - return { status: 400, message: "Declined by FE" }; + throw new BadRequestError("강의 코드는 필수 입력 필드입니다"); } if (!language) { - toast.error("사용 언어는 필수 입력 필드입니다"); - return { status: 400, message: "Declined by FE" }; + throw new BadRequestError("사용 언어는 필수 입력 필드입니다"); } if (!semester) { - toast.error("학기 정보는 필수 입력 필드입니다"); - return { status: 400, message: "Declined by FE" }; + throw new BadRequestError("학기 정보는 필수 입력 필드입니다"); } if (!title) { - toast.error("강의 제목은 필수 입력 필드입니다"); - return { status: 400, message: "Declined by FE" }; + throw new BadRequestError("강의 제목은 필수 입력 필드입니다"); } const response = await fetch(`${API_SERVER_URL}/lecture`, { method: "POST", @@ -146,7 +156,25 @@ export async function postNewLecture( body: JSON.stringify({ code, language, semester, title }), }); - return { ...(await response.json()), status: response.status }; + const data = await response.json(); + + switch (response.status) { + case 400: + throw new BadRequestError(data.message); + case 401: + handle401(); + break; + case 403: + throw new UnauthorizedError(data.message); + case 409: + throw new ConflictError(data.message); + case 500: + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); + } + + return data; } export async function UpdateLecture( @@ -156,22 +184,18 @@ export async function UpdateLecture( semester: number, title: string, token: string -) { +): Promise { if (!code) { - toast.error("강의 코드는 필수 입력 필드입니다"); - return { status: 400, message: "Declined by FE" }; + throw new BadRequestError("강의 코드는 필수 입력 필드입니다"); } if (!language) { - toast.error("사용 언어는 필수 입력 필드입니다"); - return { status: 400, message: "Declined by FE" }; + throw new BadRequestError("사용 언어는 필수 입력 필드입니다"); } if (!semester) { - toast.error("학기 정보는 필수 입력 필드입니다"); - return { status: 400, message: "Declined by FE" }; + throw new BadRequestError("학기 정보는 필수 입력 필드입니다"); } if (!title) { - toast.error("강의 제목은 필수 입력 필드입니다"); - return { status: 400, message: "Declined by FE" }; + throw new BadRequestError("강의 제목은 필수 입력 필드입니다"); } const response = await fetch(`${API_SERVER_URL}/lecture/${lectureId}`, { method: "PUT", @@ -182,30 +206,35 @@ export async function UpdateLecture( body: JSON.stringify({ code, language, semester, title }), }); + const data = await response.json(); + switch (response.status) { case 400: - toast.error("입력값 검증 실패"); + throw new BadRequestError("입력값 검증 실패"); break; case 401: - toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요"); + handle401(); break; case 403: - toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); + throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요"); break; case 404: - toast.error("해당 강의 ID가 존재하지 않습니다"); + throw new NotFoundError("해당 강의 ID가 존재하지 않습니다"); break; case 409: - toast.error("강의가 중복됩니다! 입력값을 확인해 주세요"); + throw new Error("강의가 중복됩니다! 입력값을 확인해 주세요"); break; case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new Error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); } - return { ...(await response.json()), status: response.status }; + return data; } -export async function deleteLecture(lectureId: number, token: string) { +export async function deleteLecture( + lectureId: number, + token: string +): Promise { const response = await fetch(`${API_SERVER_URL}/lecture/${lectureId}`, { method: "DELETE", headers: { @@ -214,32 +243,31 @@ export async function deleteLecture(lectureId: number, token: string) { }, }); + const data = await response.json(); + switch (response.status) { - case 204: - toast.success("성공적으로 삭제되었습니다"); - break; case 401: - toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요"); + handle401(); break; case 403: - toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); + throw new Error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); break; case 404: - toast.error("해당 강의 ID가 존재하지 않습니다"); + throw new Error("해당 강의 ID가 존재하지 않습니다"); break; case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new Error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); break; } - return { status: response.status }; + return data; } export async function addUserInLecture( lectureId: number, userInfo: studentRow, token: string -) { +): Promise { const { userId, isTutor, userName } = userInfo; const response = await fetch(`${API_SERVER_URL}/lecture/${lectureId}/users`, { method: "POST", @@ -251,28 +279,31 @@ export async function addUserInLecture( data: [{ id: userId, is_tutor: isTutor, name: userName }], }), }); + + const data = await response.json(); + switch (response.status) { case 401: - toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요"); + handle401(); break; case 403: - toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); + throw new Error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); break; case 404: - toast.error("해당 강의 ID가 존재하지 않습니다"); + throw new Error("해당 강의 ID가 존재하지 않습니다"); break; case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new Error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); break; } - return { ...(await response.json()), status: response.status }; + return data; } export async function addUsersInLecture( lecurteId: number, usersInfo: studentRow[], token: string -) { +): Promise { const response = await fetch(`${API_SERVER_URL}/lecture/${lecurteId}/users`, { method: "POST", headers: { @@ -287,27 +318,30 @@ export async function addUsersInLecture( })), }), }); + const data = await response.json(); switch (response.status) { case 401: - toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요"); + handle401(); break; case 403: - toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); + throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요"); break; case 404: - toast.error("해당 강의 ID가 존재하지 않습니다"); + throw new NotFoundError("해당 강의 ID가 존재하지 않습니다"); break; case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); } - return { ...(await response.json()), status: response.status }; + return data; } export async function removeUserInLecture( lectureId: string, userId: string, token: string -) { +): Promise { const response = await fetch( `${API_SERVER_URL}/lecture/${lectureId}/user/${userId}`, { @@ -318,20 +352,25 @@ export async function removeUserInLecture( }, } ); + + const data = await response.json(); + switch (response.status) { case 401: - toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요"); + handle401(); break; case 403: - toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); + throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요"); break; case 404: - toast.error("해당 강의 ID 또는 학생 ID가 존재하지 않습니다"); + throw new NotFoundError("해당 강의 ID 또는 학생 ID가 존재하지 않습니다"); break; case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); } - return { status: response.status }; + return data; } export async function getUsersInLecture( @@ -345,26 +384,29 @@ export async function getUsersInLecture( Authorization: `Bearer ${token}`, }, }); + const data = await response.json(); switch (response.status) { case 401: - toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요"); + handle401(); break; case 403: - toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); + throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요"); break; case 404: - toast.error("해당 강의 ID가 존재하지 않습니다"); + throw new NotFoundError("해당 강의 ID가 존재하지 않습니다"); break; case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); } - return { ...(await response.json()), status: response.status }; + return data; } export async function getLectureWithLectureId( lectureId: string, token: string -) { +): Promise { const response = await fetch(`${API_SERVER_URL}/lecture/${lectureId}`, { method: "GET", headers: { @@ -372,21 +414,26 @@ export async function getLectureWithLectureId( Authorization: `Bearer ${token}`, }, }); + const data = await response.json(); switch (response.status) { case 401: - toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요"); + handle401(); break; case 403: - toast.error("강의에 소속이 되어있지 않습니다. 다시 확인해 주세요"); + throw new ForbiddenError( + "강의에 소속이 되어있지 않습니다. 다시 확인해 주세요" + ); break; case 404: - toast.error("해당 강의 ID가 존재하지 않습니다"); + throw new NotFoundError("해당 강의 ID가 존재하지 않습니다"); break; case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); break; } - return { ...(await response.json()), status: response.status }; + return data; } export async function getProblemWithProblemId( @@ -400,27 +447,32 @@ export async function getProblemWithProblemId( Authorization: `Bearer ${token}`, }, }); + const data = await response.json(); switch (response.status) { case 401: - toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요"); + handle401(); break; case 403: - toast.error("강의에 소속이 되어있지 않습니다. 다시 확인해 주세요"); + throw new ForbiddenError( + "강의에 소속이 되어있지 않습니다. 다시 확인해 주세요" + ); break; case 404: - toast.error("해당 강의 ID가 존재하지 않습니다"); + throw new NotFoundError("해당 강의 ID가 존재하지 않습니다"); break; case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); break; } - return { ...(await response.json()), status: response.status }; + return data; } export async function getAllPractices( token: string, userId: string -): Promise { +): Promise { const response = await fetch(`${API_SERVER_URL}/user/${userId}/practices`, { method: "GET", headers: { @@ -428,19 +480,24 @@ export async function getAllPractices( Authorization: `Bearer ${token}`, }, }); + const data = await response.json(); switch (response.status) { case 401: - toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요"); + handle401(); break; case 403: - toast.error("강의에 소속이 되어있지 않습니다. 다시 확인해 주세요"); + throw new ForbiddenError( + "강의에 소속이 되어있지 않습니다. 다시 확인해 주세요" + ); break; case 404: - toast.error("해당 강의 ID가 존재하지 않습니다"); + throw new NotFoundError("해당 강의 ID가 존재하지 않습니다"); break; case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); break; } - return { ...(await response.json()), status: response.status }; + return data; } diff --git a/app/API/media.ts b/app/API/media.ts index f1a7920..1891ea8 100644 --- a/app/API/media.ts +++ b/app/API/media.ts @@ -1,18 +1,21 @@ import { API_SERVER_URL } from "~/util/constant"; -import toast from "react-hot-toast"; import { UploadFileResponse } from "~/types/APIResponse"; +import { + BadRequestError, + InternalServerError, + RequestTooLongError, +} from "~/util/errors"; +import { handle401 } from "~/util"; export async function uploadFile( file: File, token: string ): Promise { if (!file) { - toast.error("File이 유효하지 않습니다. 다시 확인해주세요"); - return { message: "declined by FE", status: 400 }; + throw new BadRequestError("File이 유효하지 않습니다. 다시 확인해주세요"); } if (file.size > 1024 * 1024 * 30) { - toast.error("파일이 30MB이상입니다. 너무 파일이 큽니다"); - return { message: "declined by FE", status: 413 }; + throw new RequestTooLongError("파일이 30MB이상입니다. 너무 파일이 큽니다"); } const formData = new FormData(); formData.append("documents", file); @@ -23,19 +26,23 @@ export async function uploadFile( }, body: formData, }); + + const data = await response.json(); + switch (response.status) { case 400: - toast.error("uploadFile은 하나의 파일만 업로드 가능합니다"); + throw new BadRequestError("uploadFile은 하나의 파일만 업로드 가능합니다"); break; case 401: - toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요"); + handle401(); break; case 413: - toast.error("파일이 너무 큽니다."); - break; + throw new RequestTooLongError("파일이 너무 큽니다."); case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); break; } - return { ...(await response.json()), status: response.status }; + return data; } diff --git a/app/API/practice.ts b/app/API/practice.ts index 81a865b..3fa3ac3 100644 --- a/app/API/practice.ts +++ b/app/API/practice.ts @@ -1,6 +1,12 @@ import { API_SERVER_URL } from "~/util/constant"; -import toast from "react-hot-toast"; import { PracticeDetailResponse } from "~/types/APIResponse"; +import { handle401 } from "~/util"; +import { + BadRequestError, + ForbiddenError, + InternalServerError, + NotFoundError, +} from "~/util/errors"; export async function getPracticeWithPracticeId( practiceId: number | string, @@ -15,18 +21,24 @@ export async function getPracticeWithPracticeId( }); switch (response.status) { case 401: - toast.error("유효하지 않은 JWT 토큰. 다시 로그인 하세요"); + handle401(); break; case 403: - toast.error("강의에 소속된 유저가 이닙니다. 다시 확인해 주세요"); + throw new ForbiddenError( + "강의에 소속된 유저가 이닙니다. 다시 확인해 주세요" + ); break; case 404: - toast.error("해당 실습 ID가 존재하지 않습니다"); + throw new NotFoundError("해당 실습 ID가 존재하지 않습니다"); break; + case 500: + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); default: break; } - return { ...(await response.json()), status: response.status }; + return await response.json(); } export async function createNewPractice( @@ -37,8 +49,7 @@ export async function createNewPractice( token: string ) { if (!title) { - toast.error("제목은 필수 입력 필드입니다"); - return { status: 400, message: "Declined by FE" }; + throw new BadRequestError("제목은 필수 입력 필드입니다"); } const response = await fetch(`${API_SERVER_URL}/practice`, { method: "POST", @@ -51,19 +62,21 @@ export async function createNewPractice( switch (response.status) { case 400: - toast.error("입력값 검증 실패"); + throw new BadRequestError("입력값 검증 실패"); break; case 401: - toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요"); + handle401(); break; case 403: - toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); + throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요"); break; case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); break; } - return { ...(await response.json()), status: response.status }; + return await response.json(); } export async function updatePractice( @@ -74,8 +87,7 @@ export async function updatePractice( token: string ) { if (!title) { - toast.error("제목은 필수 입력 필드입니다"); - return { status: 400, message: "Declined by FE" }; + throw new BadRequestError("제목은 필수 입력 필드입니다"); } const response = await fetch(`${API_SERVER_URL}/practice/${practice_id}`, { method: "PUT", @@ -88,22 +100,24 @@ export async function updatePractice( switch (response.status) { case 400: - toast.error("입력값 검증 실패"); + throw new BadRequestError("입력값 검증 실패"); break; case 401: - toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요"); + handle401(); break; case 403: - toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); + throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요"); break; case 404: - toast.error("해당 실습 ID가 존재하지 않습니다"); + throw new NotFoundError("해당 실습 ID가 존재하지 않습니다"); break; case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); break; } - return { status: response.status }; + return await response.json(); } export async function deletePractice( @@ -119,17 +133,19 @@ export async function deletePractice( }); switch (response.status) { case 401: - toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요"); + handle401(); break; case 403: - toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); + throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요"); break; case 404: - toast.error("강의 ID가 없거나 실습 ID 가 없습니다"); + throw new NotFoundError("강의 ID가 없거나 실습 ID 가 없습니다"); break; case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); break; } - return { status: response.status }; + return await response.json(); } diff --git a/app/API/problem.ts b/app/API/problem.ts index 3758640..e14aff3 100644 --- a/app/API/problem.ts +++ b/app/API/problem.ts @@ -1,8 +1,14 @@ import { API_SERVER_URL } from "~/util/constant"; import { parsedCodeElement } from "~/util/codeHole"; -import toast from "react-hot-toast"; import { uploadFile } from "./media"; -import { SuccessUploadFileResponse } from "~/types/APIResponse"; +import { handle401 } from "~/util"; +import { + BadRequestError, + ForbiddenError, + InternalServerError, + NotFoundError, +} from "~/util/errors"; +import { EmptyResponse, ProblemDetailResponse } from "~/types/APIResponse"; export async function postSolveProblem( file: File, @@ -11,24 +17,18 @@ export async function postSolveProblem( time_limit: number, title: string, token: string -) { +): Promise { if (0 > memory_limit || memory_limit > 4096) { - toast.error("메모리 제한은 0 ~ 4096 사이 값을 넣어야 합니다"); - return { status: 400, message: "Declined by FE" }; + throw new BadRequestError("메모리 제한은 0 ~ 4096 사이 값을 넣어야 합니다"); } if (!title) { - toast.error("제목은 필수 입력 필드입니다"); - return { status: 400, message: "Declined by FE" }; + throw new BadRequestError("제목은 필수 입력 필드입니다"); } if (0 > time_limit || time_limit > 10000) { - toast.error("시간 제한은 0~10,000 사이의 값을 넣어야 합니다"); - return { status: 400, message: "Declined by FE" }; + throw new BadRequestError("시간 제한은 0~10,000 사이의 값을 넣어야 합니다"); } const fileUploadResponse = await uploadFile(file, token); - if (fileUploadResponse.status !== 200) { - return { status: fileUploadResponse.status }; - } - const file_path = (fileUploadResponse as SuccessUploadFileResponse).data.path; + const file_path = fileUploadResponse.data.path; const response = await fetch(`${API_SERVER_URL}/problem`, { method: "POST", headers: { @@ -46,15 +46,15 @@ export async function postSolveProblem( }); switch (response.status) { case 400: - toast.error("입력값 검증 실패"); + throw new BadRequestError("입력값 검증 실패"); break; case 401: - toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요"); + handle401(); break; case 403: - toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); + throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요"); } - return { status: response.status, ...(await response.json()) }; + return await response.json(); } export async function postBlankProblem( @@ -66,24 +66,18 @@ export async function postBlankProblem( time_limit: number, title: string, token: string -) { +): Promise { if (0 > memory_limit || memory_limit > 4096) { - toast.error("메모리 제한은 0 ~ 4096 사이 값을 넣어야 합니다"); - return { status: 400, message: "Declined by FE" }; + throw new BadRequestError("메모리 제한은 0 ~ 4096 사이 값을 넣어야 합니다"); } if (!title) { - toast.error("제목은 필수 입력 필드입니다"); - return { status: 400, message: "Declined by FE" }; + throw new BadRequestError("제목은 필수 입력 필드입니다"); } if (0 > time_limit || time_limit > 10000) { - toast.error("시간 제한은 0~10,000 사이의 값을 넣어야 합니다"); - return { status: 400, message: "Declined by FE" }; + throw new BadRequestError("시간 제한은 0~10,000 사이의 값을 넣어야 합니다"); } const fileUploadResponse = await uploadFile(file, token); - if (fileUploadResponse.status !== 200) { - return { status: fileUploadResponse.status }; - } - const file_path = (fileUploadResponse as SuccessUploadFileResponse).data.path; + const file_path = fileUploadResponse.data.path; const response = await fetch(`${API_SERVER_URL}/problem`, { method: "POST", headers: { @@ -102,15 +96,15 @@ export async function postBlankProblem( }); switch (response.status) { case 400: - toast.error("입력값 검증 실패"); + throw new BadRequestError("입력값 검증 실패"); break; case 401: - toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요"); + handle401(); break; case 403: - toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); + throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요"); } - return { status: response.status, ...(await response.json()) }; + return await response.json(); } export async function updateProblem( @@ -122,23 +116,19 @@ export async function updateProblem( token: string, file_path: string, parsed_code_elements?: parsedCodeElement[][] -) { +): Promise { if (0 > memory_limit || memory_limit > 4096) { - toast.error("메모리 제한은 0 ~ 4096 사이 값을 넣어야 합니다"); - return { status: 400, message: "Declined by FE" }; + throw new BadRequestError("메모리 제한은 0 ~ 4096 사이 값을 넣어야 합니다"); } if (!title) { - toast.error("제목은 필수 입력 필드입니다"); - return { status: 400, message: "Declined by FE" }; + throw new BadRequestError("제목은 필수 입력 필드입니다"); } if (0 > time_limit || time_limit > 10000) { - toast.error("시간 제한은 0~10,000 사이의 값을 넣어야 합니다"); - return { status: 400, message: "Declined by FE" }; + throw new BadRequestError("시간 제한은 0~10,000 사이의 값을 넣어야 합니다"); } if (problemType === "blank") { if (!parsed_code_elements) { - toast.error("빈칸 문제에는 빈칸정보가 필요합니다"); - return { status: 400 }; + throw new BadRequestError("빈칸 문제에는 빈칸정보가 필요합니다"); } } const response = await fetch(`${API_SERVER_URL}/problem/${problemId}`, { @@ -158,25 +148,30 @@ export async function updateProblem( }); switch (response.status) { case 400: - toast.error("입력값 검증 실패"); + throw new BadRequestError("입력값 검증 실패"); break; case 401: - toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요"); + handle401(); break; case 403: - toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); + throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요"); break; case 404: - toast.error("해당 문제 ID가 존재하지 않습니다"); + throw new NotFoundError("해당 문제 ID가 존재하지 않습니다"); break; case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); break; } - return { status: response.status }; + return await response.json(); } -export async function deleteProblem(problemId: number, token: string) { +export async function deleteProblem( + problemId: number, + token: string +): Promise { const response = await fetch(`${API_SERVER_URL}/problem/${problemId}`, { method: "DELETE", headers: { @@ -186,17 +181,19 @@ export async function deleteProblem(problemId: number, token: string) { }); switch (response.status) { case 401: - toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요"); + handle401(); break; case 403: - toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); + throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요"); break; case 404: - toast.error("해당 문제 ID가 존재하지 않습니다"); + throw new NotFoundError("해당 문제 ID가 존재하지 않습니다"); break; case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); break; } - return { status: response.status }; + return await response.json(); } diff --git a/app/API/submission.ts b/app/API/submission.ts index 810eb97..f234433 100644 --- a/app/API/submission.ts +++ b/app/API/submission.ts @@ -1,12 +1,24 @@ import { API_SERVER_URL } from "~/util/constant"; -import toast from "react-hot-toast"; import { removePackageStatementFromFile, handle401 } from "~/util"; +import { + BoardResponse, + EmptyResponse, + SubmissionResponse, + SubmissionsResponse, +} from "~/types/APIResponse"; +import { + BadRequestError, + ForbiddenError, + InternalServerError, + NotFoundError, + RequestTooLongError, +} from "~/util/errors"; export async function submit( token: string, problem_id: string, formdata: FormData -) { +): Promise { if (formdata.get("language") === "java") { const fileList = formdata.getAll("codes") as File[]; const code = formdata.get("code") as string; @@ -34,28 +46,32 @@ export async function submit( ); switch (response.status) { case 400: - toast.error("JWT토큰이 없거나 입력값 검증 실패"); + throw new BadRequestError("JWT토큰이 없거나 입력값 검증 실패"); break; case 401: handle401(); break; case 403: - toast.error("소속되어 있지 않은 강의의 문제 접근"); + throw new ForbiddenError("소속되어 있지 않은 강의의 문제 접근"); break; case 404: - toast.error("problem_id가 존재하지 않습니다"); + throw new NotFoundError("problem_id가 존재하지 않습니다"); break; case 413: - toast.error("Request too long"); + throw new RequestTooLongError("Request too long"); break; + case 500: + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); } - return { ...(await response.json()), status: response.status }; + return await response.json(); } export async function getSubmissionWithSubmissionId( submissionId: number, token: string -) { +): Promise { const response = await fetch(`${API_SERVER_URL}/submission/${submissionId}`, { method: "GET", headers: { @@ -65,13 +81,13 @@ export async function getSubmissionWithSubmissionId( }); switch (response.status) { case 400: - toast.error("JWT토큰이 없거나 입력값 검증 실패"); + throw new BadRequestError("JWT토큰이 없거나 입력값 검증 실패"); break; case 401: handle401(); break; } - return { ...(await response.json()), status: response.status }; + return await response.json(); } export async function getSubmissionStatus( @@ -82,7 +98,7 @@ export async function getSubmissionStatus( practice_id?: string | number; problem_id?: number; } -) { +): Promise { const searchParams = new URLSearchParams(); if (queryParams.user_id !== undefined) { searchParams.append("user_id", queryParams.user_id); @@ -108,19 +124,23 @@ export async function getSubmissionStatus( ); switch (response.status) { case 400: - toast.error("JWT토큰이 없거나 입력값 검증 실패"); + throw new BadRequestError("JWT토큰이 없거나 입력값 검증 실패"); break; case 401: handle401(); break; + case 500: + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); } - return { ...(await response.json()), status: response.status }; + return await response.json(); } export async function getLectureScoreBoard( token: string, lecture_id: string | number -) { +): Promise { const response = await fetch( `${API_SERVER_URL}/lecture/${lecture_id}/score`, { @@ -133,22 +153,26 @@ export async function getLectureScoreBoard( ); switch (response.status) { case 400: - toast.error("JWT토큰이 없거나 입력값 검증 실패"); + throw new BadRequestError("JWT토큰이 없거나 입력값 검증 실패"); break; case 401: handle401(); break; case 403: - toast.error("소속되지 않은 강의의 스코어보드 접근"); + throw new ForbiddenError("소속되지 않은 강의의 스코어보드 접근"); break; + case 500: + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); } - return { ...(await response.json()), status: response.status }; + return await response.json(); } export async function getPracticeScoreBoard( token: string, practice_id: number | string -) { +): Promise { const response = await fetch( `${API_SERVER_URL}/practice/${practice_id}/score`, { @@ -161,19 +185,23 @@ export async function getPracticeScoreBoard( ); switch (response.status) { case 400: - toast.error("JWT토큰이 없거나 입력값 검증 실패"); + throw new BadRequestError("JWT토큰이 없거나 입력값 검증 실패"); break; case 401: handle401(); break; case 403: - toast.error("소속되지 않은 강의의 스코어보드 접근"); + throw new ForbiddenError("소속되지 않은 강의의 스코어보드 접근"); break; case 404: - toast.error("아직 미구현 되어있다고 하네요"); + throw new NotFoundError("존재하지 않는 강의"); break; + case 500: + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); } - return { ...(await response.json()), status: response.status }; + return await response.json(); } export async function reJudge( @@ -183,35 +211,33 @@ export async function reJudge( practice_id?: number; problem_id?: number; } -): Promise<{ status: number; message: string }> { - return new Promise(async (resolve, reject) => { - const response = await fetch(`${API_SERVER_URL}/re_judge`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - body: JSON.stringify(queryParams), - }); - - switch (response.status) { - case 400: - toast.error("JWT토큰이 없거나 입력값 검증 실패"); - reject(400); - break; - case 401: - handle401(); - reject(401); - break; - case 403: - toast.error("권한이 부족합니다"); - reject(403); - break; - case 404: - toast.error("존재하지 않는걸 재채점 한다고 하네요"); - reject(404); - break; - } - resolve({ ...(await response.json()), status: response.status }); +): Promise { + const response = await fetch(`${API_SERVER_URL}/re_judge`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify(queryParams), }); + + switch (response.status) { + case 400: + throw new BadRequestError("JWT토큰이 없거나 입력값 검증 실패"); + break; + case 401: + handle401(); + break; + case 403: + throw new ForbiddenError("권한이 부족합니다"); + break; + case 404: + throw new NotFoundError("존재하지 않는 항목"); + break; + case 500: + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); + } + return await response.json(); } diff --git a/app/API/testCase.ts b/app/API/testCase.ts index 917deb7..c962579 100644 --- a/app/API/testCase.ts +++ b/app/API/testCase.ts @@ -1,21 +1,24 @@ import { API_SERVER_URL } from "~/util/constant"; -import toast from "react-hot-toast"; -import { TestcaseResponse } from "~/types/APIResponse"; +import { EmptyResponse, TestcaseResponse } from "~/types/APIResponse"; import { handle401 } from "~/util"; +import { + BadRequestError, + ForbiddenError, + InternalServerError, + NotFoundError, +} from "~/util/errors"; export async function postNewTestcase( problemId: number, formData: FormData, token: string -) { +): Promise { const score = formData.get("score") as string; if (score === "") { - toast.error("점수는 필수 입력입니다"); - return { message: "Declined by FE", status: 400 }; + throw new BadRequestError("점수는 필수 입력입니다"); } if (parseInt(score) < 0) { - toast.error("점수는 음수가 될 수 없습니다"); - return { message: "Declined by FE", status: 400 }; + throw new BadRequestError("점수는 음수가 될 수 없습니다"); } const response = await fetch( `${API_SERVER_URL}/problem/${problemId}/testcase`, @@ -30,22 +33,24 @@ export async function postNewTestcase( switch (response.status) { case 400: - toast.error("입력값 검증이 실패하였습니다"); + throw new BadRequestError("입력값 검증이 실패하였습니다"); break; case 401: handle401(); break; case 403: - toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); + throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요"); break; case 404: - toast.error("해당 문제 ID가 존재하지 않습니다"); + throw new NotFoundError("해당 문제 ID가 존재하지 않습니다"); break; case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); break; } - return { ...(await response.json()), status: response.status }; + return await response.json(); } export async function getTestcaseById( @@ -64,28 +69,29 @@ export async function getTestcaseById( handle401(); break; case 403: - toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); + throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요"); break; case 404: - toast.error("해당 TC ID가 존재하지 않습니다"); + throw new NotFoundError("해당 TC ID가 존재하지 않습니다"); break; case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); break; } - return { ...(await response.json()), status: response.status }; + return await response.json(); } export async function updateTestcase( testcaseId: number, formData: FormData, token: string -) { +): Promise { const score = formData.get("score"); if (score === "") { - toast.error("점수는 필수 입력입니다"); - return { message: "Declined by FE", status: 400 }; + throw new Error("점수는 필수 입력입니다"); } const response = await fetch(`${API_SERVER_URL}/testcase/${testcaseId}`, { method: "PUT", @@ -97,22 +103,32 @@ export async function updateTestcase( switch (response.status) { case 400: - toast.error("JWT 토큰이 없거나 입력값 검증이 실패하였습니다"); + throw new BadRequestError( + "JWT 토큰이 없거나 입력값 검증이 실패하였습니다" + ); break; case 401: handle401(); break; case 403: - toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); + throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요"); break; case 404: - toast.error("해당 TC ID가 존재하지 않습니다"); + throw new NotFoundError("해당 TC ID가 존재하지 않습니다"); + break; + case 500: + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); break; } - return { ...(await response.json()), status: response.status }; + return await response.json(); } -export async function deleteTestcase(testcaseId: number, token: string) { +export async function deleteTestcase( + testcaseId: number, + token: string +): Promise { const response = await fetch(`${API_SERVER_URL}/testcase/${testcaseId}`, { method: "DELETE", headers: { @@ -126,23 +142,25 @@ export async function deleteTestcase(testcaseId: number, token: string) { handle401(); break; case 403: - toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); + throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요"); break; case 404: - toast.error("해당 TC ID가 존재하지 않습니다"); + throw new NotFoundError("해당 TC ID가 존재하지 않습니다"); break; case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); break; } - return { status: response.status }; + return await response.json(); } export async function deleteFileInputFromTestCase( testcaseId: number, fileName: string, token: string -) { +): Promise { const response = await fetch( `${API_SERVER_URL}/testcase/${testcaseId}/file`, { @@ -162,19 +180,22 @@ export async function deleteFileInputFromTestCase( handle401(); break; case 403: - throw new Error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); + throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요"); case 404: - throw new Error("존재하지 않는 TC ID 입니다"); + throw new NotFoundError("존재하지 않는 TC ID 입니다"); case 500: - throw new Error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); } + return await response.json(); } export async function deleteFileOutputFromTestCase( testcaseId: number, fileName: string, token: string -) { +): Promise { const response = await fetch( `${API_SERVER_URL}/testcase/${testcaseId}/file`, { @@ -194,10 +215,13 @@ export async function deleteFileOutputFromTestCase( handle401(); break; case 403: - throw new Error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); + throw new ForbiddenError("강의 소유 권한이 없습니다. 다시 확인해 주세요"); case 404: - throw new Error("존재하지 않는 TC ID 입니다"); + throw new NotFoundError("존재하지 않는 TC ID 입니다"); case 500: - throw new Error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); } + return await response.json(); } diff --git a/app/API/user.ts b/app/API/user.ts index eb8cae0..c7afa32 100644 --- a/app/API/user.ts +++ b/app/API/user.ts @@ -1,8 +1,16 @@ import { API_SERVER_URL } from "~/util/constant"; -import toast from "react-hot-toast"; +import { + BadRequestError, + ConflictError, + ForbiddenError, + InternalServerError, + NotFoundError, + UnauthorizedError, +} from "~/util/errors"; +import { handle401 } from "~/util"; +import { EmptyResponse, UserSearchResponse } from "~/types/APIResponse"; interface loginResponse { - status: number; message: string; data: { token: string; @@ -10,7 +18,6 @@ interface loginResponse { } interface getUserInfoResponse { - status: number; message: string; data: { id: string; @@ -21,20 +28,9 @@ interface getUserInfoResponse { } interface changePasswordResponse { - status: number; message: string; } -function handleStatus(status: number) { - switch (status) { - case 500: - toast.error("500 : 관리자에게 문의해 주세요"); - return; - default: - break; - } -} - export async function login( id: string, password: string @@ -46,8 +42,21 @@ export async function login( }, body: JSON.stringify({ id, password }), }); - handleStatus(response.status); - return { ...(await response.json()), status: response.status }; + switch (response.status) { + case 400: + throw new BadRequestError("body가 유효한 JSON이 아님"); + break; + case 401: + throw new UnauthorizedError("PW 불일치"); + break; + case 404: + throw new NotFoundError("ID가 존재하지 않습니다"); + case 500: + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); + } + return await response.json(); } export async function getUserInfo( @@ -61,8 +70,22 @@ export async function getUserInfo( Authorization: `Bearer ${token}`, }, }); - handleStatus(response.status); - return { ...(await response.json()), status: response.status }; + switch (response.status) { + case 401: + handle401(); + break; + case 403: + throw new ForbiddenError( + "권한이 부족해 사용자 정보를 열람할 수 없습니다" + ); + case 404: + throw new NotFoundError("해당 ID의 사용자가 없습니다"); + case 500: + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); + } + return await response.json(); } export async function changePassword( @@ -79,8 +102,26 @@ export async function changePassword( }, body: JSON.stringify({ old_password, new_password }), }); - handleStatus(response.status); - return { ...(await response.json()), status: response.status }; + switch (response.status) { + case 401: + handle401(); + break; + case 403: + throw new ForbiddenError( + "권한이 부족합니다. 올바른 접근인지 확인해 주십시오" + ); + case 404: + throw new NotFoundError("해당 ID의 사용자가 없습니다"); + case 409: + throw new ConflictError( + "기존 PW와 일치하지 않습니다. 다시 확인해 주세요" + ); + case 500: + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); + } + return await response.json(); } export async function resetPassword( @@ -95,11 +136,32 @@ export async function resetPassword( }, body: JSON.stringify({ new_password: "password" }), }); - handleStatus(response.status); - return { ...(await response.json()), status: response.status }; + switch (response.status) { + case 401: + handle401(); + break; + case 403: + throw new ForbiddenError( + "권한이 부족합니다. 올바른 접근인지 확인해 주십시오" + ); + case 404: + throw new NotFoundError("해당 ID의 사용자가 없습니다"); + case 409: + throw new ConflictError( + "기존 PW와 일치하지 않습니다. 다시 확인해 주세요" + ); + case 500: + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); + } + return await response.json(); } -export async function deleteUser(user_id: string, token: string) { +export async function deleteUser( + user_id: string, + token: string +): Promise { const response = await fetch(`${API_SERVER_URL}/user/${user_id}`, { method: "DELETE", headers: { @@ -107,8 +169,22 @@ export async function deleteUser(user_id: string, token: string) { Authorization: `Bearer ${token}`, }, }); - handleStatus(response.status); - return { ...(await response.json()), status: response.status }; + switch (response.status) { + case 401: + handle401(); + break; + case 403: + throw new ForbiddenError( + "권한이 부족합니다. 올바른 접근인지 확인해 주십시오" + ); + case 404: + throw new NotFoundError("해당 ID의 사용자가 없습니다"); + case 500: + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); + } + return await response.json(); } export async function addUser( @@ -117,7 +193,7 @@ export async function addUser( name: string, role: string, token: string -) { +): Promise { const response = await fetch(`${API_SERVER_URL}/user`, { method: "POST", headers: { @@ -126,11 +202,32 @@ export async function addUser( }, body: JSON.stringify({ id, is_admin, name, role }), }); - handleStatus(response.status); - return { ...(await response.json()), status: response.status }; + switch (response.status) { + case 401: + handle401(); + break; + case 403: + throw new ForbiddenError( + "권한이 부족합니다. 올바른 접근인지 확인해 주십시오" + ); + case 404: + throw new NotFoundError("해당 ID의 사용자가 없습니다"); + case 409: + throw new ConflictError( + "학번이 중복되는 사용자가 존재합니다. 다시 확인해 주세요" + ); + case 500: + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); + } + return await response.json(); } -export async function searchUser(searchString: string, token: string) { +export async function searchUser( + searchString: string, + token: string +): Promise { const response = await fetch( `${API_SERVER_URL}/user?search=${searchString}`, { @@ -141,6 +238,18 @@ export async function searchUser(searchString: string, token: string) { }, } ); - handleStatus(response.status); - return { ...(await response.json()), status: response.status }; + switch (response.status) { + case 401: + handle401(); + break; + case 403: + throw new ForbiddenError( + "권한이 부족합니다. 올바른 접근인지 확인해 주십시오" + ); + case 500: + throw new InternalServerError( + "서버 에러가 발생했습니다. 관리자에게 문의해 주세요" + ); + } + return await response.json(); } diff --git a/app/components/Header/UserInfo.tsx b/app/components/Header/UserInfo.tsx index c234048..3960276 100644 --- a/app/components/Header/UserInfo.tsx +++ b/app/components/Header/UserInfo.tsx @@ -48,19 +48,14 @@ const UserInfo = ({ userId, token, userName, userClass, isAdmin }: Props) => { return; } - const { status, message } = await changePassword( - userId, - token, - old_password, - new_password + await toast.promise( + changePassword(userId, token, old_password, new_password), + { + loading: "변경중...", + success: "성공적으로 변경되었습니다!", + error: (err) => `Error: ${err.message} - ${err.responseMessage}`, + } ); - - if (status === 200) { - toast.success("성공적으로 변경되었습니다!"); - setIsPWChangeModalOpen(false); - } else { - toast(message, { icon: "⚠️" }); - } } async function logout() { diff --git a/app/contexts/AuthContext.tsx b/app/contexts/AuthContext.tsx index 748d57c..7d78bb9 100644 --- a/app/contexts/AuthContext.tsx +++ b/app/contexts/AuthContext.tsx @@ -111,16 +111,14 @@ export const AuthProvider = ({ children }: AuthProviderProps) => { useEffect(() => { async function fetchRole() { const response = await getUserInfo(state.userId, state.token); - if (response.status === 200) { - dispatch({ - type: "__internal_fetch_complete", - payload: { - role: response.data.role as "student" | "professor", - isAdmin: response.data.is_admin, - userName: response.data.name, - }, - }); - } + dispatch({ + type: "__internal_fetch_complete", + payload: { + role: response.data.role as "student" | "professor", + isAdmin: response.data.is_admin, + userName: response.data.name, + }, + }); } if (state.token) fetchRole(); }, [state.token]); diff --git a/app/routes/_noAuthOnly+/_index.tsx b/app/routes/_noAuthOnly+/_index.tsx index fa26d37..2fd06cb 100644 --- a/app/routes/_noAuthOnly+/_index.tsx +++ b/app/routes/_noAuthOnly+/_index.tsx @@ -21,16 +21,18 @@ const Login = () => { toast.error("ID와 비밀번호를 입력하십시오!"); return; } - const response = await login(id, password); - if (response.status === 201) { + try { + const response = await login(id, password); authDispatch({ type: "UPDATE_DATA", payload: { token: response.data.token, userId: id }, }); toast.success("성공적으로 로그인 하였습니다!"); navigate("/lectures"); - } else toast(response.message, { icon: "⚠️" }); + } catch (e: any) { + toast(e.message, { icon: "⚠️" }); + } } return ( diff --git a/app/routes/_procted+/grade+/$lectureId+/$practiceId+/SubmissionRecordModal.tsx b/app/routes/_procted+/grade+/$lectureId+/$practiceId+/SubmissionRecordModal.tsx index 45296c2..9c37b36 100644 --- a/app/routes/_procted+/grade+/$lectureId+/$practiceId+/SubmissionRecordModal.tsx +++ b/app/routes/_procted+/grade+/$lectureId+/$practiceId+/SubmissionRecordModal.tsx @@ -32,20 +32,23 @@ const SubmissionRecordModal = ({ useState(false); useEffect(() => { async function getHistory() { - const response = await getSubmissionStatus(auth.token, { - user_id: studentId, - lecture_id: params.lectureId!, - practice_id: params.practiceId!, - problem_id: problemId, - }); - if (response.status === 200) { - setHistory((response as any).data); - setIsLoading(false); - } else { - toast.error( - "올바르게 데이터를 가져오지 못했습니다. 다시 시도해 주세요" - ); - } + await toast.promise( + getSubmissionStatus(auth.token, { + user_id: studentId, + lecture_id: params.lectureId!, + practice_id: params.practiceId!, + problem_id: problemId, + }), + { + loading: "불러오는 중...", + success: (response) => { + setHistory(response.data); + setIsLoading(false); + return "불러오기 완료!"; + }, + error: (err) => `Error: ${err.message} - ${err.responseMessage}`, + } + ); } getHistory(); }, []); @@ -64,7 +67,7 @@ const SubmissionRecordModal = ({ , ]); }); + setData( + response.data.users.map((user: any, userIdx: number) => { + const map = new Map(); + map.set("userName", user.name); + map.set("totalScore", user.total_score); + user.scores.map((score: any, idx: number) => { + map.set( + `problemNo${idx}`, + + ); + }); + return map; + }) + ); + setIsLoading(false); + } catch (e: any) { + toast.error(`Error: ${e.message} - ${e.responseMessage}`); } - setData( - response.data.users.map((user: any, userIdx: number) => { - const map = new Map(); - map.set("userName", user.name); - map.set("totalScore", user.total_score); - user.scores.map((score: any, idx: number) => { - map.set( - `problemNo${idx}`, - - ); - }); - return map; - }) - ); - setIsLoading(false); } getPracticeScore(); }, [params.practiceId, isLoading]); diff --git a/app/routes/_procted+/grade+/$lectureId+/_layout.tsx b/app/routes/_procted+/grade+/$lectureId+/_layout.tsx index c7ea1ab..b614707 100644 --- a/app/routes/_procted+/grade+/$lectureId+/_layout.tsx +++ b/app/routes/_procted+/grade+/$lectureId+/_layout.tsx @@ -5,6 +5,7 @@ import { getLectureWithLectureId } from "~/API/lecture"; import { useAuth } from "~/contexts/AuthContext"; import { ButtonElement } from "~/components/Aside/ButtonElement"; import { Link } from "react-router-dom"; +import toast from "react-hot-toast"; const ScoreBoardLayout = () => { const params = useParams(); @@ -13,13 +14,15 @@ const ScoreBoardLayout = () => { const [practiceList, setPracticeList] = useState([]); useEffect(() => { async function getPractices() { - const response = await getLectureWithLectureId( - params.lectureId!, - auth.token - ); - if (response.status === 200) { + try { + const response = await getLectureWithLectureId( + params.lectureId!, + auth.token + ); setPracticeList((response as any).data.practices); setIsLoading(false); + } catch (e: any) { + toast.error(`Error: ${e.message} - ${e.responseMessage}`); } } getPractices(); diff --git a/app/routes/_procted+/grade+/$lectureId+/index.tsx b/app/routes/_procted+/grade+/$lectureId+/index.tsx index 98fbd16..c0b81b5 100644 --- a/app/routes/_procted+/grade+/$lectureId+/index.tsx +++ b/app/routes/_procted+/grade+/$lectureId+/index.tsx @@ -23,10 +23,6 @@ import { getPreviousSemesterLectures, getFutureSemesterLectures, } from "~/API/lecture"; -import { - isSuccessResponse, - SuccessLecturesResponse, -} from "~/types/APIResponse"; import toast from "react-hot-toast"; const TableHeader = () => { @@ -53,22 +49,19 @@ const TableHeader = () => { auth.userId, auth.token ); - if (isSuccessResponse(response)) - setLectureList((response as SuccessLecturesResponse).data); + setLectureList(response.data); } else if (semester === "past") { const response = await getPreviousSemesterLectures( auth.userId, auth.token ); - if (isSuccessResponse(response)) - setLectureList((response as SuccessLecturesResponse).data); + setLectureList(response.data); } else if (semester === "future") { const response = await getFutureSemesterLectures( auth.userId, auth.token ); - if (isSuccessResponse(response)) - setLectureList((response as SuccessLecturesResponse).data); + setLectureList(response.data); } const pastResponse = await getPreviousSemesterLectures( auth.userId, @@ -82,22 +75,12 @@ const TableHeader = () => { auth.userId, auth.token ); - if (isSuccessResponse(pastResponse)) { - setPastLectureList((pastResponse as SuccessLecturesResponse).data); - } - if (isSuccessResponse(currentResponse)) { - setCurrentLectureList( - (currentResponse as SuccessLecturesResponse).data - ); - } - if (isSuccessResponse(futureResponse)) { - setFutureLectureList( - (futureResponse as SuccessLecturesResponse).data - ); - } + setPastLectureList(pastResponse.data); + setCurrentLectureList(currentResponse.data); + setFutureLectureList(futureResponse.data); setLectureListLoading(false); - } catch (error) { - console.error(error); + } catch (e: any) { + toast.error(`Error: ${e.message} - ${e.responseMessage}`); } }; getLectureList(); @@ -231,12 +214,13 @@ const LectureScoreBoard = () => { useEffect(() => { async function getData() { setDataHeaders(["사용자 이름", "학번", "총점"]); - const response = await getLectureScoreBoard( - auth.token, - params.lectureId! - ); - if (response.status === 200) { - response.data.metadata.map((data: any) => { + try { + const response = await getLectureScoreBoard( + auth.token, + params.lectureId! + ); + + response.data.metadata.map((data) => { setDataHeaders((prev) => [ ...prev, , ]); }); setData( - response.data.users.map((user: any) => { + response.data.users.map((user) => { const map = new Map(); map.set("userName", user.name); map.set("userId", user.id); map.set("totalScore", user.total_score); - user.scores.map((score: any, idx: number) => { + user.scores.map((score, idx: number) => { map.set( `problemNo${idx}`, `${ @@ -281,6 +270,8 @@ const LectureScoreBoard = () => { }) ); setIsLoading(false); + } catch (e: any) { + toast.error(`Error: ${e.message} - ${e.responseMessage}`); } } getData(); diff --git a/app/routes/_procted+/grade+/index.tsx b/app/routes/_procted+/grade+/index.tsx index 78b86d0..a06f7de 100644 --- a/app/routes/_procted+/grade+/index.tsx +++ b/app/routes/_procted+/grade+/index.tsx @@ -1,5 +1,6 @@ import { useNavigate } from "@remix-run/react"; import { useEffect } from "react"; +import toast from "react-hot-toast"; import { getCurrentSemesterLectures, getFutureSemesterLectures, @@ -7,11 +8,7 @@ import { } from "~/API/lecture"; import { useAuth } from "~/contexts/AuthContext"; import { useLectureDataDispatch } from "~/contexts/LectureDataContext"; -import { - LectureEntity, - SuccessLecturesResponse, - isSuccessResponse, -} from "~/types/APIResponse"; +import { LectureEntity } from "~/types/APIResponse"; const GradeRedirect = () => { const navigate = useNavigate(); @@ -20,65 +17,53 @@ const GradeRedirect = () => { useEffect(() => { async function getLectures() { - const response = await getCurrentSemesterLectures( - auth.userId, - auth.token - ); - if (isSuccessResponse(response)) { - dispatch({ - type: "UPDATE_DATA", - payload: { - semester: "present", - lectureName: (response as SuccessLecturesResponse).data[0].title, - }, - }); - navigate(`/grade/${(response as SuccessLecturesResponse).data[0].id}`); - } else { - const previousResponse = await getPreviousSemesterLectures( + try { + const response = await getCurrentSemesterLectures( auth.userId, auth.token ); - if (previousResponse.status === 200) { - if ((previousResponse as SuccessLecturesResponse).data.length !== 0) { + if (response.data.length > 0) { + dispatch({ + type: "UPDATE_DATA", + payload: { + semester: "present", + lectureName: response.data[0].title, + }, + }); + navigate(`/grade/${response.data[0].id}`); + } else { + const previousResponse = await getPreviousSemesterLectures( + auth.userId, + auth.token + ); + if (previousResponse.data.length !== 0) { dispatch({ type: "UPDATE_DATA", payload: { semester: "past", - lectureName: ( - (previousResponse as SuccessLecturesResponse) - .data as LectureEntity[] - )[0].title, + lectureName: previousResponse.data[0].title, }, }); - navigate( - `/grade/${ - (previousResponse as SuccessLecturesResponse).data[0].id - }?semester=past` + navigate(`/grade/${previousResponse.data[0].id}?semester=past`); + } else { + const futureResponse = await getFutureSemesterLectures( + auth.userId, + auth.token ); - } - } else { - const futureResponse = await getFutureSemesterLectures( - auth.userId, - auth.token - ); - if (futureResponse.status === 200) { - if ((futureResponse as SuccessLecturesResponse).data.length !== 0) { + if (futureResponse.data.length !== 0) { dispatch({ type: "UPDATE_DATA", payload: { semester: "future", - lectureName: (futureResponse as SuccessLecturesResponse) - .data[0].title, + lectureName: futureResponse.data[0].title, }, }); - navigate( - `/grade/${ - (futureResponse as SuccessLecturesResponse).data[0].id - }?semester=future` - ); + navigate(`/grade/${futureResponse.data[0].id}?semester=future`); } } } + } catch (error: any) { + toast.error(`Error: ${error.message} - ${error.responseMessage}`); } } getLectures(); diff --git a/app/routes/_procted+/lectures+/$lectureId+/$practiceId+/$labId/SubmitModal.tsx b/app/routes/_procted+/lectures+/$lectureId+/$practiceId+/$labId/SubmitModal.tsx index c4e9689..50afad7 100644 --- a/app/routes/_procted+/lectures+/$lectureId+/$practiceId+/$labId/SubmitModal.tsx +++ b/app/routes/_procted+/lectures+/$lectureId+/$practiceId+/$labId/SubmitModal.tsx @@ -13,6 +13,8 @@ import { useNavigate, useParams } from "@remix-run/react"; import CodeBlank from "~/components/CodeBlank"; import { getProblemWithProblemId } from "~/API/lecture"; import { generateFullCode } from "~/util/codeHole"; +import toast from "react-hot-toast"; +import { SimpleProblemDetail } from "~/types/APIResponse"; interface Props { isOpen: boolean; @@ -28,14 +30,16 @@ const SubmitModal = ({ isOpen, onClose }: Props) => { const [language, setLanguage] = useState("c"); const [entryPoint, setEntryPoint] = useState(""); const [isLoading, setIsLoading] = useState(true); - const [problemDetail, setProblemDetail] = useState(); + const [problemDetail, setProblemDetail] = useState(); useEffect(() => { async function getData() { - const response = await getProblemWithProblemId(labId!, auth.token); - if (response.status / 100 === 2) { - setProblemDetail((response as any).data); + try { + const response = await getProblemWithProblemId(labId!, auth.token); + setProblemDetail(response.data); setIsLoading(false); + } catch (error: any) { + toast.error(`Error: ${error.message} - ${error.responseMessage}`); } } diff --git a/app/routes/_procted+/lectures+/$lectureId+/$practiceId+/$labId/index.tsx b/app/routes/_procted+/lectures+/$lectureId+/$practiceId+/$labId/index.tsx index a2d9f30..da3e621 100644 --- a/app/routes/_procted+/lectures+/$lectureId+/$practiceId+/$labId/index.tsx +++ b/app/routes/_procted+/lectures+/$lectureId+/$practiceId+/$labId/index.tsx @@ -3,11 +3,7 @@ import { useEffect, useState } from "react"; import toast from "react-hot-toast"; import { getProblemWithProblemId } from "~/API/lecture"; import { useAuth } from "~/contexts/AuthContext"; -import { - SimpleProblemDetail, - SuccessProblemDetailResponse, - isSuccessResponse, -} from "~/types/APIResponse"; +import { SimpleProblemDetail } from "~/types/APIResponse"; import { STATIC_SERVER_URL } from "~/util/constant"; import styles from "./index.module.css"; import SubmitModal from "./SubmitModal"; @@ -26,20 +22,20 @@ const LabDetail = () => { useEffect(() => { async function getData() { - const response = await getProblemWithProblemId( - parseInt(labId!), - auth.token - ); - const response2 = await getPracticeWithPracticeId( - practiceId!, - auth.token - ); - if (isSuccessResponse(response) && response2.status === 200) { - setProblemDetail((response as SuccessProblemDetailResponse).data); - setPracticeDetail((response2 as any).data); + try { + const response = await getProblemWithProblemId( + parseInt(labId!), + auth.token + ); + const response2 = await getPracticeWithPracticeId( + practiceId!, + auth.token + ); + setProblemDetail(response.data); + setPracticeDetail(response2.data); setLoading(false); - } else { - toast.error("잘못된 접근입니다"); + } catch (error: any) { + toast.error(`Error: ${error.message} - ${error.responseMessage}`); navigate("/"); } } diff --git a/app/routes/_procted+/lectures+/$lectureId+/_layout/DownloadMyCodesModal.tsx b/app/routes/_procted+/lectures+/$lectureId+/_layout/DownloadMyCodesModal.tsx index 95aa13d..d12411b 100644 --- a/app/routes/_procted+/lectures+/$lectureId+/_layout/DownloadMyCodesModal.tsx +++ b/app/routes/_procted+/lectures+/$lectureId+/_layout/DownloadMyCodesModal.tsx @@ -34,55 +34,47 @@ const DownloadMyCodesModal = ({ isOpen, onClose }: Props) => { useEffect(() => { async function getData() { - const lectureResponse = await getLectureWithLectureId( - params.lectureId!, - auth.token - ); + try { + const lectureResponse = await getLectureWithLectureId( + params.lectureId!, + auth.token + ); - if (lectureResponse.status !== 200) - throw new Error("Failed to get lecture"); + const practices = lectureResponse.data.practices; + const practicesResponseList = await Promise.all( + practices.map(async (practice: any) => + getPracticeWithPracticeId(practice.id, auth.token) + ) + ); - const practices = (lectureResponse as any).data.practices; - const practicesResponseList = await Promise.all( - practices.map(async (practice: any) => - getPracticeWithPracticeId(practice.id, auth.token) - ) - ); - - practicesResponseList.forEach((res) => { - if (res.status !== 200) throw new Error("Failed to get practice"); - }); - - const practiceNodes = await Promise.all( - practicesResponseList.map(async (practiceResponse) => { - const problemResponseList = await Promise.all( - practiceResponse.data.problems.map(async (problem: any) => - getProblemWithProblemId(problem.id, auth.token) - ) - ); - const problemDataList = problemResponseList.map((res) => { - if (res.status !== 200) throw new Error("Failed to get problem"); + const practiceNodes = await Promise.all( + practicesResponseList.map(async (practiceResponse) => { + const problemResponseList = await Promise.all( + practiceResponse.data.problems.map(async (problem: any) => + getProblemWithProblemId(problem.id, auth.token) + ) + ); + const problemDataList = problemResponseList.map((res) => { + return { + title: res.data.title as string, + id: res.data.id + "", + children: null, + }; + }); return { - title: res.data.title as string, - id: res.data.id as string, - children: null, + title: practiceResponse.data.title, + id: practiceResponse.data.id + "", + children: problemDataList, }; - }); - return { - title: practiceResponse.data.title, - id: practiceResponse.data.id, - children: problemDataList, - }; - }) - ); - setNodesData(practiceNodes); - setIsLoading(false); + }) + ); + setNodesData(practiceNodes); + setIsLoading(false); + } catch (error: any) { + toast.error(`Error: ${error.message} - ${error.responseMessage}`); + } } - toast.promise(getData(), { - loading: "이 강의에 대한 정보들을 불러오는중...", - success: "불러오기 완료!", - error: (err) => err.toString(), - }); + getData(); }, [params.lectureId]); return isLoading ? null : ( diff --git a/app/routes/_procted+/lectures+/$lectureId+/_layout/ImportPracticeModal.tsx b/app/routes/_procted+/lectures+/$lectureId+/_layout/ImportPracticeModal.tsx index 3cf89db..ee30aa3 100644 --- a/app/routes/_procted+/lectures+/$lectureId+/_layout/ImportPracticeModal.tsx +++ b/app/routes/_procted+/lectures+/$lectureId+/_layout/ImportPracticeModal.tsx @@ -10,10 +10,6 @@ import inputStyles from "~/components/Input/input.module.css"; import formStyles from "~/components/common/form.module.css"; import TextInput from "~/components/Input/TextInput"; import DateInput from "~/components/Input/DateInput"; -import { - SuccessAllPracticesResponse, - isSuccessResponse, -} from "~/types/APIResponse"; interface Props { isOpen: boolean; @@ -29,30 +25,26 @@ const ImportPracticeModal = ({ isOpen, onClose }: Props) => { async function getData() { try { const response = await getAllPractices(token, userId); - if (isSuccessResponse(response)) { - const res: TreeViewNode[] = []; - (response as SuccessAllPracticesResponse).data.forEach( - (data, idx) => { - res[idx] = {} as TreeViewNode; - res[idx].title = semesterNumberToString(data.semester); - res[idx].id = data.semester + ""; - res[idx].children = data.lectures.map((lecture) => { + const res: TreeViewNode[] = []; + response.data.forEach((data, idx) => { + res[idx] = {} as TreeViewNode; + res[idx].title = semesterNumberToString(data.semester); + res[idx].id = data.semester + ""; + res[idx].children = data.lectures.map((lecture) => { + return { + title: lecture.title, + id: lecture.id + "", + children: lecture.practices.map((practice) => { return { - title: lecture.title, - id: lecture.id + "", - children: lecture.practices.map((practice) => { - return { - title: practice.title, - id: practice.id + "", - children: null, - }; - }) as TreeViewNode[], + title: practice.title, + id: practice.id + "", + children: null, }; - }); - } - ); - setData(res); - } + }) as TreeViewNode[], + }; + }); + }); + setData(res); } catch (error) { console.error(error); } diff --git a/app/routes/_procted+/lectures+/$lectureId+/_layout/PracticeEditModal.tsx b/app/routes/_procted+/lectures+/$lectureId+/_layout/PracticeEditModal.tsx index bf88765..0b79508 100644 --- a/app/routes/_procted+/lectures+/$lectureId+/_layout/PracticeEditModal.tsx +++ b/app/routes/_procted+/lectures+/$lectureId+/_layout/PracticeEditModal.tsx @@ -8,11 +8,7 @@ import { updatePractice } from "~/API/practice"; import { useAuth } from "~/contexts/AuthContext"; import { useEffect, useState } from "react"; import { getPracticeWithPracticeId } from "~/API/practice"; -import { - SimplePracticeDetail, - SuccessPracticeDetailResponse, - isSuccessResponse, -} from "~/types/APIResponse"; +import { SimplePracticeDetail } from "~/types/APIResponse"; import { toLocalDateTimeString } from "~/util"; interface Props { @@ -28,10 +24,14 @@ const PracticeEditModal = ({ isOpen, onClose, practiceId }: Props) => { useEffect(() => { async function getPracticeFromServer() { - const response = await getPracticeWithPracticeId(practiceId, auth.token); - if (isSuccessResponse(response)) - setPracticeData((response as SuccessPracticeDetailResponse).data); - else { + try { + const response = await getPracticeWithPracticeId( + practiceId, + auth.token + ); + setPracticeData(response.data); + } catch (error: any) { + toast.error(`Error: ${error.message} - ${error.responseMessage}`); onClose(); } setIsLoading(false); diff --git a/app/routes/_procted+/lectures+/$lectureId+/_layout/ProblemAddModal.tsx b/app/routes/_procted+/lectures+/$lectureId+/_layout/ProblemAddModal.tsx index d7c1574..f010648 100644 --- a/app/routes/_procted+/lectures+/$lectureId+/_layout/ProblemAddModal.tsx +++ b/app/routes/_procted+/lectures+/$lectureId+/_layout/ProblemAddModal.tsx @@ -63,34 +63,48 @@ const ProblemAddModal = ({ toast.error("블록 주석에 오류가 있습니다!"); return; } - const blankResponse = await postBlankProblem( - file, - memory, - holes, - language, - practiceId, - time, - name, - auth.token + await toast.promise( + postBlankProblem( + file, + memory, + holes, + language, + practiceId, + time, + name, + auth.token + ), + { + loading: "문제를 추가하는중...", + success: () => { + onClose(); + return "문제를 성공적으로 추가했습니다!"; + }, + error: (err) => + `Error: ${err.message} - ${err.responseMessage}`, + } ); - if (blankResponse.status === 201) { - toast.success("문제를 성공적으로 추가했습니다!"); - onClose(); - } break; case "solving": - const solvingResponse = await postSolveProblem( - file, - memory, - practiceId, - time, - name, - auth.token + await toast.promise( + postSolveProblem( + file, + memory, + practiceId, + time, + name, + auth.token + ), + { + loading: "문제를 추가하는중...", + success: () => { + onClose(); + return "문제를 성공적으로 추가했습니다!"; + }, + error: (err) => + `Error: ${err.message} - ${err.responseMessage}`, + } ); - if (solvingResponse.status === 201) { - toast.success("문제를 성공적으로 추가했습니다!"); - onClose(); - } break; } }} diff --git a/app/routes/_procted+/lectures+/$lectureId+/_layout/ProblemEditModal.tsx b/app/routes/_procted+/lectures+/$lectureId+/_layout/ProblemEditModal.tsx index b26a839..9ff9aca 100644 --- a/app/routes/_procted+/lectures+/$lectureId+/_layout/ProblemEditModal.tsx +++ b/app/routes/_procted+/lectures+/$lectureId+/_layout/ProblemEditModal.tsx @@ -4,12 +4,7 @@ import formStyles from "~/components/common/form.module.css"; import { useEffect, useState } from "react"; import { getProblemWithProblemId } from "~/API/lecture"; import { useAuth } from "~/contexts/AuthContext"; -import { - SimpleProblemDetail, - SuccessProblemDetailResponse, - SuccessUploadFileResponse, - isSuccessResponse, -} from "~/types/APIResponse"; +import { SimpleProblemDetail } from "~/types/APIResponse"; import toast from "react-hot-toast"; import { updateProblem } from "~/API/problem"; import { @@ -45,12 +40,12 @@ const ProblemEditModal = ({ isOpen, onClose, editingProblemId }: Props) => { const auth = useAuth(); useEffect(() => { async function getData() { - const response = await getProblemWithProblemId( - editingProblemId, - auth.token - ); - if (isSuccessResponse(response)) { - const problemInfo = (response as SuccessProblemDetailResponse).data; + try { + const response = await getProblemWithProblemId( + editingProblemId, + auth.token + ); + const problemInfo = response.data; setPrevProblemInfo(problemInfo); setProblemType(problemInfo.type); if (problemInfo.parsed_code_elements) { @@ -60,7 +55,7 @@ const ProblemEditModal = ({ isOpen, onClose, editingProblemId }: Props) => { ); } setLoading(false); - } else { + } catch (e) { toast.error("잘못된 접근입니다"); onClose(); } @@ -89,11 +84,13 @@ const ProblemEditModal = ({ isOpen, onClose, editingProblemId }: Props) => { let newFilePath = ""; if (file.size !== 0) { - const fileUploadResponse = await uploadFile(file, auth.token); - if (isSuccessResponse(fileUploadResponse)) { - newFilePath = (fileUploadResponse as SuccessUploadFileResponse) - .data.path; - } else onClose(); + try { + const fileUploadResponse = await uploadFile(file, auth.token); + newFilePath = fileUploadResponse.data.path; + } catch (e) { + toast.error("파일 저장에 오류가 있습니다!"); + onClose(); + } } switch (problemType) { @@ -105,35 +102,47 @@ const ProblemEditModal = ({ isOpen, onClose, editingProblemId }: Props) => { toast.error("블록 주석에 오류가 있습니다!"); return; } - const blankResponse = await updateProblem( - problemType, - editingProblemId, - memory, - time, - name, - auth.token, - newFilePath.length ? newFilePath : prevProblemInfo!.file_path, - holes + await toast.promise( + updateProblem( + problemType, + editingProblemId, + memory, + time, + name, + auth.token, + newFilePath.length + ? newFilePath + : prevProblemInfo!.file_path, + holes + ), + { + loading: "문제를 수정하는중...", + success: "문제를 성공적으로 수정했습니다!", + error: (e) => `Error: ${e.message} - ${e.responseMessage}`, + } ); - if (blankResponse.status === 204) { - toast.success("문제를 성공적으로 수정했습니다!"); - onClose(); - } + onClose(); break; case "solving": - const solvingResponse = await updateProblem( - problemType, - editingProblemId, - memory, - time, - name, - auth.token, - newFilePath.length ? newFilePath : prevProblemInfo!.file_path + await toast.promise( + updateProblem( + problemType, + editingProblemId, + memory, + time, + name, + auth.token, + newFilePath.length + ? newFilePath + : prevProblemInfo!.file_path + ), + { + loading: "문제를 수정하는중...", + success: "문제를 성공적으로 수정했습니다!", + error: (e) => `Error: ${e.message} - ${e.responseMessage}`, + } ); - if (solvingResponse.status === 204) { - toast.success("문제를 성공적으로 수정했습니다!"); - onClose(); - } + onClose(); break; } }} diff --git a/app/routes/_procted+/lectures+/$lectureId+/_layout/TestCaseAddModal.tsx b/app/routes/_procted+/lectures+/$lectureId+/_layout/TestCaseAddModal.tsx index 9d623ef..d18670e 100644 --- a/app/routes/_procted+/lectures+/$lectureId+/_layout/TestCaseAddModal.tsx +++ b/app/routes/_procted+/lectures+/$lectureId+/_layout/TestCaseAddModal.tsx @@ -47,16 +47,16 @@ const TestCaseAddModal = ({ isOpen, onClose, problemId }: Props) => { argvList.forEach((argv) => formData.append("argv", argv)); - const resposnse = await postNewTestcase( - problemId, - formData, - auth.token + await toast.promise( + postNewTestcase(problemId, formData, auth.token), + { + loading: "TC를 추가시도...", + success: "성공적으로 TC를 추가하였습니다", + error: (error) => + `Error: ${error.message} - ${error.responseMessage}`, + } ); - - if (resposnse.status === 201) { - toast.success("성공적으로 TC를 추가하였습니다"); - onClose(); - } + onClose(); }} > { useEffect(() => { async function getTestCaseFromServer() { - const response = await getTestcaseById(testCaseId, auth.token); - if (isSuccessResponse(response)) { - setTestCaseData((response as SuccessTestcaseResponse).data); - setArgvList((response as SuccessTestcaseResponse).data.argv || []); + try { + const response = await getTestcaseById(testCaseId, auth.token); + setTestCaseData(response.data); + setArgvList(response.data.argv || []); setIsLoading(false); - } else { + } catch (error: any) { + toast.error(`Error: ${error.message} - ${error.responseMessage}`); onClose(); } } @@ -83,16 +80,18 @@ const TestCaseEditModal = ({ isOpen, onClose, testCaseId }: Props) => { formData.append("file_outputs", file) ); - const response = await updateTestcase( - testCaseId, - formData, - auth.token + await toast.promise( + updateTestcase(testCaseId, formData, auth.token), + { + loading: "TC 수정중...", + success: () => { + onClose(); + return "성공적으로 TC를 수정하였습니다"; + }, + error: (error) => + `Error: ${error.message} - ${error.responseMessage}`, + } ); - - if (response.status === 200) { - toast.success("성공적으로 TC를 수정하였습니다"); - onClose(); - } }} > { { loading: "TC 파일 삭제중...", success: "TC 파일 삭제완료!", - error: (err) => err.toString(), + error: (error) => + `Error: ${error.message} - ${error.responseMessage}`, } ); setIsLoading(true); @@ -264,7 +264,8 @@ const TestCaseEditModal = ({ isOpen, onClose, testCaseId }: Props) => { { loading: "TC 파일 삭제중...", success: "TC 파일 삭제완료!", - error: (err) => err.toString(), + error: (error) => + `Error: ${error.message} - ${error.responseMessage}`, } ); setIsLoading(true); diff --git a/app/routes/_procted+/lectures+/$lectureId+/_layout/index.tsx b/app/routes/_procted+/lectures+/$lectureId+/_layout/index.tsx index 332fb2d..24c6859 100644 --- a/app/routes/_procted+/lectures+/$lectureId+/_layout/index.tsx +++ b/app/routes/_procted+/lectures+/$lectureId+/_layout/index.tsx @@ -3,13 +3,8 @@ import { MetaFunction, Outlet, useNavigate, useParams } from "@remix-run/react"; import styles from "~/css/routes/lectureDetail.module.css"; import { LectureEntity, - SimpleLectureDetail, SimplePracticeDetail, SimpleProblemDetail, - SuccessLecturesResponse, - SuccessPracticeDetailResponse, - SuccessProblemDetailResponse, - isSuccessResponse, } from "~/types/APIResponse"; import { useBlockingLectureData, @@ -47,7 +42,7 @@ import DownloadMyCodesModal from "./DownloadMyCodesModal"; const LectureDetail = () => { const [isLoading, setIsLoading] = useState(true); const [lectures, setLectures] = useState([]); - const [currentLecture, setCurrentLecture] = useState(); + const [currentLecture, setCurrentLecture] = useState(); const [isNewPracticeModalOpen, setIsNewPracticeModalOpen] = useState(false); const [isImportPracticeModalOpen, setIsImportPracticeModalOpen] = useState(false); @@ -64,33 +59,33 @@ const LectureDetail = () => { useEffect(() => { async function getData() { - if (semester === "present") { - const responses = await Promise.all([ - getCurrentSemesterLectures(auth.userId, auth.token), - getLectureWithLectureId(params.lectureId!, auth.token), - ]); - if (isSuccessResponse(responses[0])) { - setLectures((responses[0] as SuccessLecturesResponse).data); + try { + if (semester === "present") { + const responses = await Promise.all([ + getCurrentSemesterLectures(auth.userId, auth.token), + getLectureWithLectureId(params.lectureId!, auth.token), + ]); + setLectures(responses[0].data); + setCurrentLecture(responses[1].data); + } else if (semester === "past") { + const responses = await Promise.all([ + getPreviousSemesterLectures(auth.userId, auth.token), + getLectureWithLectureId(params.lectureId!, auth.token), + ]); + setLectures(responses[0].data); + setCurrentLecture(responses[1].data); + } else if (semester === "future") { + const responses = await Promise.all([ + getFutureSemesterLectures(auth.userId, auth.token), + getLectureWithLectureId(params.lectureId!, auth.token), + ]); + setLectures(responses[0].data); + setCurrentLecture(responses[1].data); } - setCurrentLecture(responses[1].data); - } else if (semester === "past") { - const responses = await Promise.all([ - getPreviousSemesterLectures(auth.userId, auth.token), - getLectureWithLectureId(params.lectureId!, auth.token), - ]); - if (isSuccessResponse(responses[0])) - setLectures((responses[0] as SuccessLecturesResponse).data); - setCurrentLecture(responses[1].data); - } else if (semester === "future") { - const responses = await Promise.all([ - getFutureSemesterLectures(auth.userId, auth.token), - getLectureWithLectureId(params.lectureId!, auth.token), - ]); - if (isSuccessResponse(responses[0])) - setLectures((responses[0] as SuccessLecturesResponse).data); - setCurrentLecture(responses[1].data); + setIsLoading(false); + } catch (error: any) { + toast.error(`Error: ${error.message} - ${error.responseMessage}`); } - setIsLoading(false); } if (!isContextLoading) { getData(); @@ -194,14 +189,12 @@ const PracticeDetail = ({ id, setSuperIsLoading }: DetailProps) => { const [isFoldableOpen, setIsFoldableOpen] = useState(false); useEffect(() => { async function getData() { - const response = await getPracticeWithPracticeId(id, auth.token); - if (isSuccessResponse(response)) { - setPracticeDetail((response as SuccessPracticeDetailResponse).data); + try { + const response = await getPracticeWithPracticeId(id, auth.token); + setPracticeDetail(response.data); setLoading(false); - } else if ((response as any).status === 404) { - setLoading(false); - } else { - toast.error("잘못된 접근입니다"); + } catch (error: any) { + toast.error(`Error: ${error.message} - ${error.responseMessage}`); navigate("/"); } } @@ -295,13 +288,12 @@ const ProblemDetail = ({ superId, id, setSuperIsLoading }: DetailProps) => { useEffect(() => { async function getData() { - const response = await getProblemWithProblemId(id, auth.token); - if (isSuccessResponse(response)) { - setProblemDetail((response as SuccessProblemDetailResponse).data); - setLoading(false); - } - if (response.status === 404) { + try { + const response = await getProblemWithProblemId(id, auth.token); + setProblemDetail(response.data); setLoading(false); + } catch (error: any) { + toast.error(`Error: ${error.message} - ${error.responseMessage}`); } } getData(); @@ -328,11 +320,15 @@ const ProblemDetail = ({ superId, id, setSuperIsLoading }: DetailProps) => { if ( confirm(`정말로 ${problemDetail!.title} 문제을 삭제 하시겠습니까?`) ) { - const response = await deleteProblem(id, auth.token); - if (response.status === 204) { - toast.success("성공적으로 삭제되었습니다"); - setSuperIsLoading!(true); - } + await toast.promise(deleteProblem(id, auth.token), { + loading: "문제 삭제중...", + success: () => { + setSuperIsLoading!(true); + return "성공적으로 삭제되었습니다"; + }, + error: (error) => + `Error: ${error.message} - ${error.responseMessage}`, + }); } }} isFoldable={auth.role === "professor"} @@ -356,14 +352,18 @@ const ProblemDetail = ({ superId, id, setSuperIsLoading }: DetailProps) => { `정말로 ${testcase.title} 테스트 케이스를 삭제하시겠습니까?` ) ) { - const response = await deleteTestcase( - testcase.id, - auth.token + await toast.promise( + deleteTestcase(testcase.id, auth.token), + { + loading: "TC 삭제중...", + success: () => { + setSuperIsLoading!(true); + return "성공적으로 삭제되었습니다"; + }, + error: (error) => + `Error: ${error.message} - ${error.responseMessage}`, + } ); - if (response.status === 204) { - toast.success("성공적으로 삭제되었습니다"); - setLoading(true); - } } }, ]} diff --git a/app/routes/_procted+/lectures+/_index/LectureAddModal.tsx b/app/routes/_procted+/lectures+/_index/LectureAddModal.tsx index 5a12318..f31bd42 100644 --- a/app/routes/_procted+/lectures+/_index/LectureAddModal.tsx +++ b/app/routes/_procted+/lectures+/_index/LectureAddModal.tsx @@ -44,36 +44,20 @@ const LectureAddModal = ({ isOpen, onClose, currentYear }: Props) => { const yearStr = formData.get("year") as string; const semesterStr = formData.get("semester") as string; const language = formData.get("language") as string; - - const response = await postNewLecture( - code, - language, - semesterStringToNumber(yearStr, semesterStr), - name, - token + await toast.promise( + postNewLecture( + code, + language, + semesterStringToNumber(yearStr, semesterStr), + name, + token + ), + { + loading: "Loading", + success: "성공적으로 추가되었습니다", + error: (e) => `Error: ${e.message} - ${e.responseMessage}`, + } ); - switch (response.status) { - case 201: - toast.success("성공적으로 등록되었습니다"); - break; - case 400: - toast.error("입력이 잘못되었습니다!"); - break; - case 401: - toast.error("권한이 없습니다"); - break; - case 409: - toast.error("이미 존재하는 강의입니다"); - break; - case 500: - toast.error( - "서버에서 알 수 없는 에러가 발생했습니다. 관리자에게 문의하세요" - ); - break; - default: - toast.error("알 수 없는 에러입니다. 관리자에게 문의하세요"); - break; - } onClose(); }} diff --git a/app/routes/_procted+/lectures+/_index/LectureEditModal.tsx b/app/routes/_procted+/lectures+/_index/LectureEditModal.tsx index b25888e..7d58831 100644 --- a/app/routes/_procted+/lectures+/_index/LectureEditModal.tsx +++ b/app/routes/_procted+/lectures+/_index/LectureEditModal.tsx @@ -48,39 +48,22 @@ const LectureEditModal = ({ const code = formData.get("code") as string; const language = formData.get("language") as string; - const response = await UpdateLecture( - lectureId, - code, - language, - semesterStringToNumber(lectureYear, lectureSemester), - name, - auth.token + await toast.promise( + UpdateLecture( + lectureId, + code, + language, + semesterStringToNumber(lectureYear, lectureSemester), + name, + auth.token + ), + { + loading: "Loading", + success: "강의 업데이트 성공", + error: (err) => `Error: ${err.message} - ${err.responseMessage}`, + } ); - - switch (response.status) { - case 200: - toast.success("강의 업데이트 성공"); - onClose(); - break; - case 401: - toast.error("유효하지 않은 JWT 토큰. 다시 로그인 해주세요"); - onClose(); - break; - case 403: - toast.error("강의 소유 권한이 없습니다. 다시 확인해 주세요"); - break; - case 404: - toast.error("해당 강의 ID가 존재하지 않습니다"); - break; - case 409: - toast.error( - "수정 하려는 정보 조합이 이미 존재합니다. 다시 확인해 주세요" - ); - break; - case 500: - toast.error("서버 에러가 발생했습니다. 관리자에게 문의해 주세요"); - break; - } + onClose(); }} >
diff --git a/app/routes/_procted+/lectures+/_index/index.tsx b/app/routes/_procted+/lectures+/_index/index.tsx index a291797..750d3a9 100644 --- a/app/routes/_procted+/lectures+/_index/index.tsx +++ b/app/routes/_procted+/lectures+/_index/index.tsx @@ -15,12 +15,8 @@ import LectureAddModal from "./LectureAddModal"; import LectureEditModal from "./LectureEditModal"; import { formatLectureInfo, semesterToString } from "~/util"; import { useLectureDataDispatch } from "~/contexts/LectureDataContext"; -import { - LectureEntity, - LecturesResponse, - SuccessLecturesResponse, - isSuccessResponse, -} from "~/types/APIResponse"; +import { LectureEntity, LecturesResponse } from "~/types/APIResponse"; +import toast from "react-hot-toast"; const Lectures = () => { const [currentSemeseterLectures, setCurrentSemeseterLectures] = useState< @@ -44,24 +40,19 @@ const Lectures = () => { const isProfessor = role === "professor"; useEffect(() => { const getLectures = async () => { - const lectures = await Promise.all([ - getCurrentSemesterLectures(userId, token), - getPreviousSemesterLectures(userId, token), - getFutureSemesterLectures(userId, token), - ]); - if (isSuccessResponse(lectures[0])) - setCurrentSemeseterLectures( - (lectures[0] as SuccessLecturesResponse).data - ); - if (isSuccessResponse(lectures[1])) - setPreviousSemesterLectures( - (lectures[1] as SuccessLecturesResponse).data - ); - if (isSuccessResponse(lectures[2])) - setFutureSemesterLectures( - (lectures[2] as SuccessLecturesResponse).data - ); - setIsLoading(false); + try { + const lectures = await Promise.all([ + getCurrentSemesterLectures(userId, token), + getPreviousSemesterLectures(userId, token), + getFutureSemesterLectures(userId, token), + ]); + setCurrentSemeseterLectures(lectures[0].data); + setPreviousSemesterLectures(lectures[1].data); + setFutureSemesterLectures(lectures[2].data); + setIsLoading(false); + } catch (error: any) { + toast.error(`Error: ${error.message} - ${error.responseMessage}`); + } }; getLectures(); }, [isLoading]); @@ -122,13 +113,19 @@ const Lectures = () => { className={styles.icon} onClick={async (e) => { if (confirm("정말로 강의를 삭제하시겠습니까")) { - const response = await deleteLecture( - lecture.id, - token + await toast.promise( + deleteLecture(lecture.id, token), + { + loading: + "강의를 삭제중입니다...", + success: () => { + setIsLoading(true); + return "강의를 성공적으로 삭제하였습니다"; + }, + error: (err) => + `Error: ${err.message} - ${err.responseMessage}`, + } ); - if (response.status === 204) { - setIsLoading(true); - } } }} /> @@ -187,13 +184,18 @@ const Lectures = () => { className={styles.icon} onClick={async (e) => { if (confirm("정말로 강의를 삭제하시겠습니까")) { - const response = await deleteLecture( - lecture.id, - token + await toast.promise( + deleteLecture(lecture.id, token), + { + loading: "강의를 삭제중입니다...", + success: () => { + setIsLoading(true); + return "강의를 성공적으로 삭제하였습니다"; + }, + error: (err) => + `Error: ${err.message} - ${err.responseMessage}`, + } ); - if (response.status === 204) { - setIsLoading(true); - } } }} /> @@ -269,13 +271,18 @@ const Lectures = () => { "정말로 강의를 삭제하시겠습니까? 강의에 소속된 실습 등도 모두 삭제됩니다!" ) ) { - const response = await deleteLecture( - lecture.id, - token + await toast.promise( + deleteLecture(lecture.id, token), + { + loading: "강의를 삭제중입니다...", + success: () => { + setIsLoading(true); + return "강의를 성공적으로 삭제하였습니다"; + }, + error: (err) => + `Error: ${err.message} - ${err.responseMessage}`, + } ); - if (response.status === 204) { - setIsLoading(true); - } } }} /> diff --git a/app/routes/_procted+/students+/$lectureId+/$labId+/history+/SubmissionDetailModal.tsx b/app/routes/_procted+/students+/$lectureId+/$labId+/history+/SubmissionDetailModal.tsx index f9fa825..9af7e81 100644 --- a/app/routes/_procted+/students+/$lectureId+/$labId+/history+/SubmissionDetailModal.tsx +++ b/app/routes/_procted+/students+/$lectureId+/$labId+/history+/SubmissionDetailModal.tsx @@ -7,6 +7,7 @@ import CodeBlock from "~/components/CodeBlock"; import TextInput from "~/components/Input/TextInput"; import TextArea from "~/components/Input/TextArea"; import { bytesToSize } from "~/util"; +import toast from "react-hot-toast"; interface Props { isOpen: boolean; @@ -21,15 +22,16 @@ const SubmissionDetailModal = ({ isOpen, onClose, submissionId }: Props) => { useEffect(() => { async function getSubmissionFromServer() { - const response = await getSubmissionWithSubmissionId( - submissionId, - auth.token - ); - if (response.status === 200) { + try { + const response = await getSubmissionWithSubmissionId( + submissionId, + auth.token + ); setSubmissionResponse(response); setIsLoading(false); - } else { + } catch (error: any) { onClose(); + toast.error(`Error: ${error.message} - ${error.responseMessage}`); } } diff --git a/app/routes/_procted+/students+/$lectureId+/$labId+/history+/index.tsx b/app/routes/_procted+/students+/$lectureId+/$labId+/history+/index.tsx index f046b28..38b584b 100644 --- a/app/routes/_procted+/students+/$lectureId+/$labId+/history+/index.tsx +++ b/app/routes/_procted+/students+/$lectureId+/$labId+/history+/index.tsx @@ -18,6 +18,7 @@ import TableBase from "~/components/Table/TableBase"; import { getSubmissionStatus } from "~/API/submission"; import SubmissionDetailModal from "./SubmissionDetailModal"; import { getPracticeWithPracticeId } from "~/API/practice"; +import toast from "react-hot-toast"; const TableHeader = () => { const navigate = useNavigate(); @@ -52,18 +53,16 @@ const TableHeader = () => { params.lectureId!, auth.token ); - if (response.status === 200) { - setPracticeList((response.data as any).practices); - setPracticeName( - (response.data as any).practices.find( - (practice: { id: number; title: string }) => - practice.id === Number(params.labId) - )?.title - ); - setPracticeListLoading(false); - } - } catch (error) { - console.error(error); + setPracticeList(response.data.practices); + setPracticeName( + response.data.practices.find( + (practice: { id: number; title: string }) => + practice.id === Number(params.labId) + )?.title || "" + ); + setPracticeListLoading(false); + } catch (error: any) { + toast.error(`Error: ${error.message} - ${error.responseMessage}`); } }; getPracticeList(); @@ -71,19 +70,21 @@ const TableHeader = () => { useEffect(() => { const getProblemList = async () => { - const response = await getPracticeWithPracticeId( - params.labId!, - auth.token - ); - if (response.status === 200) { - setProblemList(((response as any).data as any).problems); + try { + const response = await getPracticeWithPracticeId( + params.labId!, + auth.token + ); + setProblemList(response.data.problems); setCurrentProblem( - ((response as any).data as any).problems.find( + response.data.problems.find( (problem: { id: number; title: string }) => problem.id === parseInt(problemId!) - ) || ((response as any).data as any).problems[0] + ) || response.data.problems[0] ); setProblemListLoading(false); + } catch (error: any) { + toast.error(`Error: ${error.message} - ${error.responseMessage}`); } }; getProblemList(); @@ -184,7 +185,7 @@ const Table = () => { const lectureId = params.lectureId!; const [searchParams] = useSearchParams(); const problemId = searchParams.get("problemId"); - const [data, setData] = useState([]); + const [data, setData] = useState[]>([]); const [isSubmissionModalOpen, setIsSubmissionModalOpen] = useState(false); const [detailId, setDetailId] = useState(0); @@ -199,63 +200,62 @@ const Table = () => { if (JSON.stringify(response.data) !== JSON.stringify(prevData)) { setIsLoading(true); } - if (response.status === 200) { - prevData = response.data; - setData( - response.data.map((data: any) => { - const map = new Map(); - map.set("id", data.id); - map.set( - "result", - (function () { - switch (data.status) { - case "accepted": - return 맞았습니다; - case "time_limit": - return 시간 초과; - case "memory_limit": - return 메모리 초과; - case "wrong_answer": - return 틀렸습니다; - case "runtime_error": - return 런타임 에러; - case "compile_error": - return 컴파일 에러; - case "pending": - return 기다리는 중; - case "running": - return ( - {`실행중 ${data.progress}%`} - ); - case "internal_error": - return 서버 내부 에러; - default: - return {data.status}; - } - })() - ); - map.set("time", new Date(data.created_at).toLocaleString()); - map.set( - "buttons", -
- -
- ); - return map; - }) - ); - setIsLoading(false); - } + + prevData = response.data; + setData( + response.data.map((data: any) => { + const map = new Map(); + map.set("id", data.id); + map.set( + "result", + (function () { + switch (data.status) { + case "accepted": + return 맞았습니다; + case "time_limit": + return 시간 초과; + case "memory_limit": + return 메모리 초과; + case "wrong_answer": + return 틀렸습니다; + case "runtime_error": + return 런타임 에러; + case "compile_error": + return 컴파일 에러; + case "pending": + return 기다리는 중; + case "running": + return ( + {`실행중 ${data.progress}%`} + ); + case "internal_error": + return 서버 내부 에러; + default: + return {data.status}; + } + })() + ); + map.set("time", new Date(data.created_at).toLocaleString()); + map.set( + "buttons", +
+ +
+ ); + return map; + }) + ); + setIsLoading(false); } if (intervalId !== undefined) { clearInterval(intervalId); diff --git a/app/routes/_procted+/students+/$lectureId+/Table.tsx b/app/routes/_procted+/students+/$lectureId+/Table.tsx index 457977a..3da48ff 100644 --- a/app/routes/_procted+/students+/$lectureId+/Table.tsx +++ b/app/routes/_procted+/students+/$lectureId+/Table.tsx @@ -21,14 +21,9 @@ import { import { useAuth } from "~/contexts/AuthContext"; import plusW from "~/assets/plus-w.svg"; import UserAddModal from "./UserAddModal"; -import { - SuccessLecturesResponse, - SuccessUserSearchResponse, - UserEntity, - isSuccessResponse, -} from "~/types/APIResponse"; +import { UserEntity } from "~/types/APIResponse"; import { resetPassword } from "~/API/user"; -import { handle401, mapRoleToString } from "~/util"; +import { mapRoleToString } from "~/util"; import toast from "react-hot-toast"; const TableHeader = () => { @@ -56,23 +51,20 @@ const TableHeader = () => { auth.userId, auth.token ); - if (isSuccessResponse(response)) - setLectureList((response as SuccessLecturesResponse).data); + setLectureList(response.data); } else if (semester === "past") { const response = await getPreviousSemesterLectures( auth.userId, auth.token ); - if (isSuccessResponse(response)) - setLectureList((response as SuccessLecturesResponse).data); + + setLectureList(response.data); } else if (semester === "future") { const response = await getFutureSemesterLectures( auth.userId, auth.token ); - if (isSuccessResponse(response)) { - setLectureList((response as SuccessLecturesResponse).data); - } + setLectureList(response.data); } const pastResponse = await getPreviousSemesterLectures( auth.userId, @@ -86,22 +78,12 @@ const TableHeader = () => { auth.userId, auth.token ); - if (isSuccessResponse(pastResponse)) { - setPastLectureList((pastResponse as SuccessLecturesResponse).data); - } - if (isSuccessResponse(currentResponse)) { - setCurrentLectureList( - (currentResponse as SuccessLecturesResponse).data - ); - } - if (isSuccessResponse(futureResponse)) { - setFutureLectureList( - (futureResponse as SuccessLecturesResponse).data - ); - } + setPastLectureList(pastResponse.data); + setCurrentLectureList(currentResponse.data); + setFutureLectureList(futureResponse.data); setLectureListLoading(false); - } catch (error) { - console.error(error); + } catch (error: any) { + toast.error(`Error: ${error.message} - ${error.responseMessage}`); } }; getLectureList(); @@ -257,11 +239,13 @@ const Table = () => { useEffect(() => { async function getData() { - const response = await getUsersInLecture(lectureId, auth.token); - if (isSuccessResponse(response)) { - setUsers((response as SuccessUserSearchResponse).data); + try { + const response = await getUsersInLecture(lectureId, auth.token); + setUsers(response.data); + setIsLoading(false); + } catch (err: any) { + toast.error(`Error: ${err.message} - ${err.responseMessage}`); } - setIsLoading(false); } getData(); }, [isLoading, params.lectureId]); @@ -282,31 +266,12 @@ const Table = () => { className={tableStyles["reset-password"]} onClick={async () => { if (confirm("정말로 초기화 하시겠습니까?")) { - const response = await resetPassword(elem.id, auth.token); - switch (response.status) { - case 200: - toast.success("성공적으로 암호를 초기화 했습니다"); - break; - case 400: - toast.error("형식이 올바르지 않습니다"); - break; - case 401: - handle401(); - break; - case 404: - toast.error( - "초기화 하려는 사용자의 ID가 존재하지 않습니다" - ); - break; - case 409: - case 500: - toast.error( - "서버 오류가 발생했습니다. 관리자에게 문의해 주세요" - ); - break; - default: - break; - } + await toast.promise(resetPassword(elem.id, auth.token), { + loading: "초기화 요청중...", + success: "성공적으로 암호를 초기화 했습니다", + error: (err) => + `Error: ${err.message} - ${err.responseMessage}`, + }); } }} > @@ -316,30 +281,15 @@ const Table = () => { className={tableStyles["out-user"]} onClick={async () => { if (confirm("정말로 해당 유저를 내보내시겠습니까?")) { - const response = await removeUserInLecture( - lectureId, - elem.id, - auth.token + await toast.promise( + removeUserInLecture(lectureId, elem.id, auth.token), + { + loading: "내보내기 요청중...", + success: "성공적으로 유저를 내보냈습니다", + error: (err) => + `Error: ${err.message} - ${err.responseMessage}`, + } ); - switch (response.status) { - case 204: - toast.success("성공적으로 유저를 내보냈습니다"); - break; - case 401: - toast.error( - "유효하지 않은 JWT 토큰. 다시 로그인 해주세요" - ); - break; - case 403: - toast.error( - "강의 소유 권한이 없습니다. 다시 확인해 주세요" - ); - break; - case 404: - toast.error( - "해당 강의 ID 또는 유저 ID가 존재하지 않습니다" - ); - } setIsLoading(true); } }} diff --git a/app/routes/_procted+/students+/$lectureId+/UserAddModal.tsx b/app/routes/_procted+/students+/$lectureId+/UserAddModal.tsx index 645d6a3..d8426a7 100644 --- a/app/routes/_procted+/students+/$lectureId+/UserAddModal.tsx +++ b/app/routes/_procted+/students+/$lectureId+/UserAddModal.tsx @@ -34,32 +34,32 @@ const UserAddModal = ({ isOpen, onClose }: Props) => { className={styles["modal-body"]} onSubmit={async (e) => { e.preventDefault(); - const formData = new FormData(e.currentTarget); - if (tabIndex === 0) { - const formFile = file ? file : (formData.get("file") as File); - const response = await addUsersInLecture( - lectureId, - await parseXlsx(formFile), - auth.token - ); - if (response.status === 201) { + try { + const formData = new FormData(e.currentTarget); + if (tabIndex === 0) { + const formFile = file ? file : (formData.get("file") as File); + await addUsersInLecture( + lectureId, + await parseXlsx(formFile), + auth.token + ); toast.success("성공적으로 추가하였습니다"); onClose(); - } - } else { - const name = formData.get("name") as string; - const id = formData.get("id") as string; - const role = formData.get("role") as string; + } else { + const name = formData.get("name") as string; + const id = formData.get("id") as string; + const role = formData.get("role") as string; - const response = await addUserInLecture( - lectureId, - { userId: id, isTutor: role === "tutor", userName: name }, - auth.token - ); - if (response.status === 201) { + await addUserInLecture( + lectureId, + { userId: id, isTutor: role === "tutor", userName: name }, + auth.token + ); toast.success("성공적으로 추가하였습니다"); onClose(); } + } catch (error: any) { + toast.error(`Error: ${error.message} - ${error.responseMessage}`); } }} > diff --git a/app/routes/_procted+/students+/index.tsx b/app/routes/_procted+/students+/index.tsx index bb5970e..216a359 100644 --- a/app/routes/_procted+/students+/index.tsx +++ b/app/routes/_procted+/students+/index.tsx @@ -4,11 +4,7 @@ import { useAuth } from "~/contexts/AuthContext"; import { getCurrentSemesterLectures } from "~/API/lecture"; import toast from "react-hot-toast"; import { useLectureDataDispatch } from "~/contexts/LectureDataContext"; -import { - LectureEntity, - SuccessLecturesResponse, - isSuccessResponse, -} from "~/types/APIResponse"; +import { LectureEntity } from "~/types/APIResponse"; const index = () => { const { token, userId } = useAuth(); @@ -21,29 +17,21 @@ const index = () => { const getFirstLecture = async () => { try { const response = await getCurrentSemesterLectures(userId, token); - if (!isSuccessResponse(response)) { - toast.error("강의를 불러오는데 실패하였습니다"); - return; - } - if ((response as SuccessLecturesResponse).data.length === 0) { + if (response.data.length === 0) { toast("현재 학기에 강의를 생성하신 후 이용해 주세요"); navigate("/lectures"); } - setFirstLectId( - ((response as SuccessLecturesResponse).data as LectureEntity[])[0] - .id + "" - ); + setFirstLectId((response.data as LectureEntity[])[0].id + ""); dispatch({ type: "UPDATE_DATA", payload: { semester: "present", - lectureName: ( - (response as SuccessLecturesResponse).data as LectureEntity[] - )[0].title, + lectureName: (response.data as LectureEntity[])[0].title, }, }); setLoading(false); } catch (error) { + toast.error("강의를 불러오는데 실패하였습니다"); console.error(error); } }; diff --git a/app/routes/admin+/_layout.tsx b/app/routes/admin+/_layout.tsx index 0894e7c..96956be 100644 --- a/app/routes/admin+/_layout.tsx +++ b/app/routes/admin+/_layout.tsx @@ -22,13 +22,17 @@ const ProctedRoute = () => { setIsNotAdmin(true); return; } - getUserInfo(userId, token).then(({ status, data }) => { - console.log(token, userId); - if (status !== 200 || data.is_admin === false) { + getUserInfo(userId, token) + .then(({ data }) => { + console.log(token, userId); + if (data.is_admin === false) { + setIsNotAdmin(true); + return; + } + }) + .catch(() => { setIsNotAdmin(true); - return; - } - }); + }); }, []); useEffect(() => { diff --git a/app/routes/admin+/semester-manage.tsx b/app/routes/admin+/semester-manage.tsx index a83c36f..43975ec 100644 --- a/app/routes/admin+/semester-manage.tsx +++ b/app/routes/admin+/semester-manage.tsx @@ -15,11 +15,11 @@ const Manage = () => { useEffect(() => { async function getSemesterFromServer() { - const { status, data } = await getSemester(auth.token); - if (status === 200) { + try { + const data = await getSemester(auth.token); setCurrentSemester(data.semester); - } else { - toast(data.message, { icon: "⚠️" }); + } catch (error: any) { + toast(error.message, { icon: "⚠️" }); } } @@ -34,15 +34,14 @@ const Manage = () => { const formData = new FormData(e.currentTarget); const year = formData.get("year") as string; - const { status, message } = await setSemester( - semesterStringToNumber(year, semesterString), - auth.token - ); - - if (status === 200) { + try { + await setSemester( + semesterStringToNumber(year, semesterString), + auth.token + ); toast.success("성공적으로 변경되었습니다!"); - } else { - toast(message, { icon: "⚠️" }); + } catch (error: any) { + toast(error.message, { icon: "⚠️" }); } }} > diff --git a/app/routes/admin+/users/AdminTableRowDataContext.tsx b/app/routes/admin+/users/AdminTableRowDataContext.tsx index 89ad712..c0ce576 100644 --- a/app/routes/admin+/users/AdminTableRowDataContext.tsx +++ b/app/routes/admin+/users/AdminTableRowDataContext.tsx @@ -1,5 +1,5 @@ import React, { createContext, useContext, ReactNode, useReducer } from "react"; -import { SuccessUserSearchResponse, UserEntity } from "~/types/APIResponse"; +import { UserEntity } from "~/types/APIResponse"; type AdminTableRowDataType = { data: UserEntity[]; diff --git a/app/routes/admin+/users/Table.tsx b/app/routes/admin+/users/Table.tsx index 16cf130..9be29c1 100644 --- a/app/routes/admin+/users/Table.tsx +++ b/app/routes/admin+/users/Table.tsx @@ -12,7 +12,7 @@ import { } from "./AdminTableRowDataContext"; import { deleteUser, resetPassword, searchUser } from "~/API/user"; import { useAuth } from "~/contexts/AuthContext"; -import { handle401, mapRoleToString } from "~/util"; +import { mapRoleToString } from "~/util"; import toast from "react-hot-toast"; const TableHeader = () => { @@ -30,24 +30,14 @@ const TableHeader = () => { e.preventDefault(); const formData = new FormData(e.currentTarget); const searchStr = formData.get("search") as string; - const response = await searchUser(searchStr, auth.token); - switch (response.status) { - case 200: - toast.success("성공적으로 검색하였습니다"); + await toast.promise(searchUser(searchStr, auth.token), { + loading: "제목 검색시도...", + success: (response) => { dispatch({ type: "UPDATE_DATA", payload: response.data }); - break; - case 400: - toast.error("인증 토큰이 누락되었습니다"); - break; - case 401: - toast.error("다시 로그인 하십시오"); - break; - case 500: - toast.error("서버 오류가 발생했습니다. 관리자에게 문의해 주세요"); - break; - default: - break; - } + return "성공적으로 검색하였습니다"; + }, + error: (e) => `Error: ${e.message} - ${e.responseMessage}`, + }); }} />
{ className={tableStyles["reset-password"]} onClick={async () => { if (confirm("정말로 초기화 하시겠습니까?")) { - const response = await resetPassword(elem.id, auth.token); - switch (response.status) { - case 200: - toast.success("성공적으로 암호를 초기화 했습니다"); - break; - case 400: - toast.error("형식이 올바르지 않습니다"); - break; - case 401: - handle401(); - break; - case 404: - toast.error( - "초기화 하려는 사용자의 ID가 존재하지 않습니다" - ); - break; - case 409: - case 500: - toast.error( - "서버 오류가 발생했습니다. 관리자에게 문의해 주세요" - ); - break; - default: - break; - } + await toast.promise(resetPassword(elem.id, auth.token), { + loading: "초기화 요청중...", + success: "성공적으로 암호를 초기화 했습니다", + error: (err) => + `Error: ${err.message} - ${err.responseMessage}`, + }); } }} > @@ -121,24 +92,12 @@ const Table = () => { className={tableStyles["out-user"]} onClick={async () => { if (confirm("정말로 해당 유저를 삭제하시겠습니까?")) { - const response = await deleteUser(elem.id, auth.token); - switch (response.status) { - case 204: - toast.success("성공적으로 삭제되었습니다"); - break; - case 401: - toast.error("관리자 권한이 필요합니다"); - break; - case 404: - toast.error( - "삭제하려는 사용자의 해당 ID가 존재하지 않습니다" - ); - break; - case 500: - toast.error("500 : 관리자에게 문의해 주세요"); - default: - break; - } + await toast.promise(deleteUser(elem.id, auth.token), { + loading: "삭제 요청중...", + success: "성공적으로 삭제되었습니다", + error: (err) => + `Error: ${err.message} - ${err.responseMessage}`, + }); } }} > diff --git a/app/routes/admin+/users/UserAddModal.tsx b/app/routes/admin+/users/UserAddModal.tsx index 5749255..9439ebf 100644 --- a/app/routes/admin+/users/UserAddModal.tsx +++ b/app/routes/admin+/users/UserAddModal.tsx @@ -28,28 +28,12 @@ const UserAddModal = ({ isOpen, onClose }: Props) => { const name = formData.get("name") as string; const id = formData.get("id") as string; const role = formData.get("role") as string; - const response = await addUser(id, false, name, role, auth.token); - switch (response.status) { - case 201: - toast.success("성공적으로 추가하였습니다"); - break; - case 400: - toast.error("입력값이 잘못되었습니다"); - break; - case 401: - toast.error("해당 작업은 관리자 계정으로만 가능합니다"); - break; - case 409: - toast.error("추가하려는 사용자가 이미 존재합니다. (학번 중복)"); - break; - case 500: - toast.error( - "서버에서 알 수 없는 에러가 발생했습니다. 관리자에게 문의하세요" - ); - break; - default: - break; - } + + await toast.promise(addUser(id, false, name, role, auth.token), { + loading: "추가 요청중...", + success: "성공적으로 추가하였습니다", + error: (e) => `Error: ${e.message} - ${e.responseMessage}`, + }); onClose(); }} > diff --git a/app/types/APIResponse.ts b/app/types/APIResponse.ts index eab243d..e4bb475 100644 --- a/app/types/APIResponse.ts +++ b/app/types/APIResponse.ts @@ -1,5 +1,5 @@ import { codeHoles } from "~/util/codeHole"; -import { Lecture } from "."; +import { Lecture, ServerSideFile, judgeStatus, lanugage } from "."; export interface UserEntity { id: string; @@ -15,68 +15,73 @@ export interface LectureEntity { semester: number; title: string; professor_name: string; + practices: SimplePracticeDetail[]; } -export interface SuccessUserSearchResponse { - status: 200; +export interface UserSearchResponse { message: string; data: UserEntity[]; } -export interface SuccessUserResponse { - status: 200; +export interface UserResponse { message: string; data: UserEntity; } -export interface SuccessLecturesResponse { - status: 200; +export interface LectureResponse { + message: string; + data: LectureEntity; +} + +export interface LecturesResponse { message: string; data: LectureEntity[]; } -export interface SuccessPracticeDetailResponse { - status: 200; +export interface PracticeDetailResponse { message: string; data: SimplePracticeDetail; } -export interface SuccessAllPracticesResponse { - status: 200; +export interface AllPracticesResponse { message: string; data: AllPracticeType[]; } -export interface SuccessProblemDetailResponse { - status: 200; +export interface ProblemDetailResponse { message: string; data: SimpleProblemDetail; } -export interface SuccessUploadFileResponse { - status: 200; +export interface UploadFileResponse { message: string; data: { path: string; }; } -export interface SuccessTestcaseResponse { - status: 200; +export interface TestcaseResponse { message: string; data: TestcaseType; } -export interface FailedResponse { - status: number; +export interface EmptyResponse { + message?: string; +} + +export interface SubmissionResponse { message: string; + data: Submission; } -export interface SimpleLectureDetail extends Lecture { - practices: { - id: number; - title: string; - }[]; +export interface SubmissionsResponse { + message: string; + data: Submission[]; +} + +export interface BoardResponse { + message: string; + data: Board; } export interface SimplePracticeDetail { @@ -140,8 +145,8 @@ export interface AllPracticeType { export interface TestcaseType { argv?: string[]; - file_input?: { content: string; name: string }[]; - file_output?: { content: string; name: string }[]; + file_input?: ServerSideFile[]; + file_output?: ServerSideFile[]; id: number; is_visible: boolean; score: number; @@ -150,39 +155,29 @@ export interface TestcaseType { title: string; } -export type UserResponse = SuccessUserResponse | FailedResponse; - -export type UserSearchResponse = SuccessUserSearchResponse | FailedResponse; - -export type LecturesResponse = SuccessLecturesResponse | FailedResponse; - -export type AllPracticeResponse = SuccessAllPracticesResponse | FailedResponse; - -export type UploadFileResponse = SuccessUploadFileResponse | FailedResponse; - -export type TestcaseResponse = SuccessTestcaseResponse | FailedResponse; - -export type ProblemDetailResponse = - | SuccessProblemDetailResponse - | FailedResponse; -export type PracticeDetailResponse = - | SuccessPracticeDetailResponse - | FailedResponse; +export interface Submission { + codes: ServerSideFile[]; + created_at: string; + entrypoint: string; + id: number; + language: lanugage; + message: string; + progress: number; + status: judgeStatus; + used_memory: number; + used_time: number; +} -export function isSuccessResponse( - response: - | UserResponse - | UserSearchResponse - | LecturesResponse - | PracticeDetailResponse - | AllPracticeResponse - | TestcaseResponse -): response is - | SuccessUserResponse - | SuccessUserSearchResponse - | SuccessLecturesResponse - | PracticeDetailResponse - | SuccessAllPracticesResponse - | SuccessTestcaseResponse { - return response.status < 300; +export interface Board { + metadata: { + id: number; + score: number; + title: string; + }[]; + users: { + id: string; + name: string; + scores: number[]; + total_score: number; + }[]; } diff --git a/app/types/index.d.ts b/app/types/index.d.ts index ac22991..eaee44f 100644 --- a/app/types/index.d.ts +++ b/app/types/index.d.ts @@ -29,3 +29,19 @@ export type lanugage = | "javascript" | "c" | "cpp"; + +export type judgeStatus = + | "accepted" + | "time_limit" + | "memory_limit" + | "wrong_answer" + | "runtime_error" + | "compile_error" + | "pending" + | "running" + | "internal_error"; + +export interface ServerSideFile { + name: string; + content: string; +} diff --git a/app/util/errors.ts b/app/util/errors.ts new file mode 100644 index 0000000..1f51227 --- /dev/null +++ b/app/util/errors.ts @@ -0,0 +1,57 @@ +class HttpError extends Error { + statusCode: number; + responseMessage: string; + + constructor( + message: string, + statusCode: number, + responseMessage: string = "" + ) { + super(message); + this.statusCode = statusCode; + this.responseMessage = responseMessage || message; + this.name = this.constructor.name; + } +} + +export class BadRequestError extends HttpError { + constructor(responseMessage: string = "") { + super("Bad Request", 400, responseMessage); + } +} + +export class UnauthorizedError extends HttpError { + constructor(responseMessage: string = "") { + super("Unauthorized", 401, responseMessage); + } +} + +export class ForbiddenError extends HttpError { + constructor(responseMessage: string = "") { + super("Forbidden", 403, responseMessage); + } +} + +export class NotFoundError extends HttpError { + constructor(responseMessage: string = "") { + super("Not Found", 404, responseMessage); + } +} + +export class ConflictError extends HttpError { + constructor(responseMessage: string = "") { + super("Conflict", 409, responseMessage); + } +} + +export class RequestTooLongError extends HttpError { + constructor(responseMessage: string = "") { + super("Request Too Long", 413, responseMessage); + } +} + +export class InternalServerError extends HttpError { + constructor(responseMessage: string = "") { + super("Internal Server Error", 500, responseMessage); + } +} diff --git a/app/util/getMyAllCodes.ts b/app/util/getMyAllCodes.ts index 001a922..00ddb36 100644 --- a/app/util/getMyAllCodes.ts +++ b/app/util/getMyAllCodes.ts @@ -20,11 +20,8 @@ export async function getCodesWithZipFile( lecture_id: lectureId, problem_id: parseInt(node.id), }); - if (submissionResponse.status !== 200) { - throw new Error("failed to get submission data"); - } const recentCorrectData = submissionResponse.data.find( - (elem: any) => elem.status === "accepted" + (elem) => elem.status === "accepted" ); if (!recentCorrectData) continue; @@ -37,7 +34,7 @@ export async function getCodesWithZipFile( setProgress(`${idx}/${length} ${node.fullName} 가져오기 완료!`); - correctSubmissionResponse.data.codes.map((file: any) => { + correctSubmissionResponse.data.codes.map((file) => { zip.file(`${practiceName}/${problemName}/${file.name}`, file.content); console.log(file.content); });