// app/api/revision-upload/route.ts import { NextRequest, NextResponse } from 'next/server' import { writeFile } from 'fs/promises' import { join } from 'path' import { v4 as uuidv4 } from 'uuid' import path from 'path' import db from '@/db/db' import { documents, issueStages, revisions, documentAttachments } from '@/db/schema/vendorDocu' import { and, eq } from 'drizzle-orm' export async function POST(request: NextRequest) { try { const formData = await request.formData() // FormData에서 데이터 추출 const stage = formData.get("stage") as string | null const revision = formData.get("revision") as string | null const docIdStr = formData.get("documentId") as string const docId = parseInt(docIdStr, 10) const uploaderName = formData.get("uploaderName") as string | null const comment = formData.get("comment") as string | null const mode = formData.get("mode") as string || "new" // 'new' | 'append' // 파일들 추출 const attachmentFiles = formData.getAll("attachments") as File[] // 유효성 검증 if (!docId || Number.isNaN(docId)) { return NextResponse.json( { error: "Invalid or missing documentId" }, { status: 400 } ) } if (!stage || !revision) { return NextResponse.json( { error: "Missing stage or revision" }, { status: 400 } ) } if (attachmentFiles.length === 0) { return NextResponse.json( { error: "No files provided" }, { status: 400 } ) } // 파일 크기 검증 (각 파일 최대 3GB) const maxFileSize = 3 * 1024 * 1024 * 1024 // 3GB for (const file of attachmentFiles) { if (file.size > maxFileSize) { return NextResponse.json( { error: `파일 ${file.name}이 너무 큽니다. 최대 3GB까지 허용됩니다.` }, { status: 400 } ) } } // 트랜잭션 실행 const result = await db.transaction(async (tx) => { // (1) issueStageId 찾기 또는 생성 let issueStageId: number const stageRecord = await tx .select() .from(issueStages) .where(and(eq(issueStages.stageName, stage), eq(issueStages.documentId, docId))) .limit(1) if (!stageRecord.length) { // Stage가 없으면 새로 생성 const [newStage] = await tx .insert(issueStages) .values({ documentId: docId, stageName: stage, updatedAt: new Date(), }) .returning() issueStageId = newStage.id } else { issueStageId = stageRecord[0].id } // (2) Revision 찾기 또는 생성 let revisionId: number const revisionRecord = await tx .select() .from(revisions) .where(and(eq(revisions.issueStageId, issueStageId), eq(revisions.revision, revision))) .limit(1) const currentDate = new Date().toISOString().split('T')[0] // YYYY-MM-DD 형식 if (!revisionRecord.length || mode === 'new') { // 새 리비전 생성 const [newRevision] = await tx .insert(revisions) .values({ issueStageId, revision, uploaderType: "vendor", // 항상 vendor로 고정 uploaderName: uploaderName || undefined, revisionStatus: "SUBMITTED", // 기본 상태 submittedDate: currentDate, // 제출일 설정 comment: comment || undefined, updatedAt: new Date(), }) .returning() revisionId = newRevision.id console.log("✅ 새 리비전 생성:", { revisionId, revision }) } else { // 기존 리비전에 파일 추가 (append 모드) revisionId = revisionRecord[0].id // 기존 리비전 정보 업데이트 (코멘트나 업로더명 변경 가능) await tx .update(revisions) .set({ uploaderName: uploaderName || revisionRecord[0].uploaderName, comment: comment || revisionRecord[0].comment, updatedAt: new Date(), }) .where(eq(revisions.id, revisionId)) console.log("✅ 기존 리비전에 파일 추가:", { revisionId, revision }) } // (3) 파일들 저장 및 DB 기록 const uploadedFiles = [] const baseDir = join(process.cwd(), "public", "documents") for (const file of attachmentFiles) { if (file.size > 0) { const originalName = file.name const ext = path.extname(originalName) const uniqueName = uuidv4() + ext const savePath = join(baseDir, uniqueName) // 파일 저장 const arrayBuffer = await file.arrayBuffer() const buffer = Buffer.from(arrayBuffer) await writeFile(savePath, buffer) // DB에 첨부파일 정보 저장 const [attachmentRecord] = await tx .insert(documentAttachments) .values({ revisionId, fileName: originalName, filePath: "/documents/" + uniqueName, fileSize: file.size, fileType: ext.replace('.', '').toLowerCase() || undefined, updatedAt: new Date(), }) .returning() uploadedFiles.push({ id: attachmentRecord.id, fileName: originalName, fileSize: file.size, filePath: attachmentRecord.filePath, }) console.log("✅ 파일 저장 완료:", originalName) } } // (4) Documents 테이블의 updatedAt 갱신 await tx .update(documents) .set({ updatedAt: new Date() }) .where(eq(documents.id, docId)) return { revisionId, stage, revision, uploaderName, comment, uploadedFiles, mode, } }) console.log("✅ 리비전 업로드 완료:", { documentId: docId, stage, revision, filesCount: result.uploadedFiles.length, mode }) return NextResponse.json({ success: true, message: `${result.uploadedFiles.length}개 파일이 성공적으로 업로드되었습니다.`, data: result, }) } catch (error) { console.error('❌ 리비전 업로드 오류:', error) return NextResponse.json( { error: 'Failed to upload revision', details: error instanceof Error ? error.message : String(error), }, { status: 500 } ) } }