diff options
Diffstat (limited to 'app/api/evaluation/attachments/route.ts')
| -rw-r--r-- | app/api/evaluation/attachments/route.ts | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/app/api/evaluation/attachments/route.ts b/app/api/evaluation/attachments/route.ts new file mode 100644 index 00000000..c856832b --- /dev/null +++ b/app/api/evaluation/attachments/route.ts @@ -0,0 +1,201 @@ +// app/api/evaluation/attachments/route.ts +import { NextRequest, NextResponse } from "next/server"; +import db from "@/db/db"; +import { reviewerEvaluationAttachments, reviewerEvaluationDetails, reviewerEvaluations } from "@/db/schema"; +import { eq, and } from "drizzle-orm"; +import { getServerSession } from "next-auth/next" +import { authOptions } from "@/app/api/auth/[...nextauth]/route" +import { saveFile } from "@/lib/file-stroage"; + +// 파일 업로드 +export async function POST(request: NextRequest) { + try { + // 인증 확인 + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + return NextResponse.json( + { success: false, error: "인증이 필요합니다." }, + { status: 401 } + ); + } + + const formData = await request.formData(); + const file = formData.get("file") as File; + const questionId = formData.get("questionId") as string; + const evaluationId = formData.get("evaluationId") as string; + const description = formData.get("description") as string | null; + + if (!file || !questionId || !evaluationId) { + return NextResponse.json( + { success: false, error: "필수 파라미터가 누락되었습니다." }, + { status: 400 } + ); + } + + // 파일 크기 제한 (10MB) + const maxSize = 10 * 1024 * 1024; + if (file.size > maxSize) { + return NextResponse.json( + { success: false, error: "파일 크기는 10MB를 초과할 수 없습니다." }, + { status: 400 } + ); + } + + // 파일 타입 제한 + const allowedTypes = [ + 'application/pdf', + 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.hancom.hwp', + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'image/jpeg', + 'image/png', + 'image/gif' + ]; + + if (!allowedTypes.includes(file.type)) { + return NextResponse.json( + { success: false, error: "지원하지 않는 파일 형식입니다." }, + { status: 400 } + ); + } + + const result = await db.transaction(async (tx) => { + // 1. 해당 평가 항목에 대한 reviewerEvaluationDetailId 찾기 + const evaluationDetail = await tx + .select({ + id: reviewerEvaluationDetails.id, + reviewerEvaluationId: reviewerEvaluationDetails.reviewerEvaluationId, + }) + .from(reviewerEvaluationDetails) + .innerJoin( + reviewerEvaluations, + eq(reviewerEvaluationDetails.reviewerEvaluationId, reviewerEvaluations.id) + ) + .where( + and( + eq(reviewerEvaluations.id, parseInt(evaluationId)), + // questionId는 실제로는 criteriaId를 의미 + // 여기서는 regEvalCriteriaDetailsId를 통해 연결해야 함 + ) + ) + .limit(1); + + if (evaluationDetail.length === 0) { + throw new Error("평가 세부사항을 찾을 수 없습니다."); + } + + // 2. 파일 저장 + const fileResult = await saveFile({ + file, + directory: "evaluation-attachments", + originalName: file.name, + }); + + if (!fileResult.success) { + throw new Error(fileResult.error || "파일 저장에 실패했습니다."); + } + + // 3. DB에 첨부파일 정보 저장 + const [attachment] = await tx + .insert(reviewerEvaluationAttachments) + .values({ + reviewerEvaluationDetailId: evaluationDetail[0].id, + originalFileName: file.name, + storedFileName: fileResult.fileName!, + filePath: fileResult.filePath!, + publicPath: fileResult.publicPath!, + fileSize: file.size, + mimeType: file.type, + fileExtension: file.name.split('.').pop()?.toLowerCase() || '', + description: description || null, + uploadedBy: parseInt(session.user.id), + }) + .returning({ + id: reviewerEvaluationAttachments.id, + originalFileName: reviewerEvaluationAttachments.originalFileName, + publicPath: reviewerEvaluationAttachments.publicPath, + fileSize: reviewerEvaluationAttachments.fileSize, + description: reviewerEvaluationAttachments.description, + createdAt: reviewerEvaluationAttachments.createdAt, + }); + + return attachment; + }); + + return NextResponse.json({ + success: true, + attachment: result, + }); + + } catch (error) { + console.error("파일 업로드 실패:", error); + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : "파일 업로드 중 오류가 발생했습니다.", + }, + { status: 500 } + ); + } +} + +// 특정 질문의 첨부파일 목록 조회 +export async function GET(request: NextRequest) { + try { + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + return NextResponse.json( + { success: false, error: "인증이 필요합니다." }, + { status: 401 } + ); + } + + const { searchParams } = new URL(request.url); + const questionId = searchParams.get("questionId"); + const evaluationId = searchParams.get("evaluationId"); + + if (!questionId || !evaluationId) { + return NextResponse.json( + { success: false, error: "필수 파라미터가 누락되었습니다." }, + { status: 400 } + ); + } + + const attachments = await db + .select({ + id: reviewerEvaluationAttachments.id, + originalFileName: reviewerEvaluationAttachments.originalFileName, + publicPath: reviewerEvaluationAttachments.publicPath, + fileSize: reviewerEvaluationAttachments.fileSize, + description: reviewerEvaluationAttachments.description, + createdAt: reviewerEvaluationAttachments.createdAt, + }) + .from(reviewerEvaluationAttachments) + .innerJoin( + reviewerEvaluationDetails, + eq(reviewerEvaluationAttachments.reviewerEvaluationDetailId, reviewerEvaluationDetails.id) + ) + .innerJoin( + reviewerEvaluations, + eq(reviewerEvaluationDetails.reviewerEvaluationId, reviewerEvaluations.id) + ) + .where(eq(reviewerEvaluations.id, parseInt(evaluationId))); + + return NextResponse.json({ + success: true, + attachments, + }); + + } catch (error) { + console.error("첨부파일 조회 실패:", error); + return NextResponse.json( + { + success: false, + error: "첨부파일 조회 중 오류가 발생했습니다.", + }, + { status: 500 } + ); + } +}
\ No newline at end of file |
