diff options
Diffstat (limited to 'app/api/stage-submissions/bulk-upload')
| -rw-r--r-- | app/api/stage-submissions/bulk-upload/route.ts | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/app/api/stage-submissions/bulk-upload/route.ts b/app/api/stage-submissions/bulk-upload/route.ts new file mode 100644 index 00000000..4ecb5c5c --- /dev/null +++ b/app/api/stage-submissions/bulk-upload/route.ts @@ -0,0 +1,202 @@ +// app/api/stage-submissions/bulk-upload/route.ts +import { NextRequest, NextResponse } from "next/server" +import { getServerSession } from "next-auth/next" +import { authOptions } from "@/app/api/auth/[...nextauth]/route" +import db from "@/db/db" +import { stageSubmissions, stageSubmissionAttachments, vendors } from "@/db/schema" +import { eq, and } from "drizzle-orm" +import { + extractRevisionNumber, + normalizeRevisionCode +} from "@/lib/vendor-document-list/plant/upload/utils/file-parser" +import { saveFileStream } from "@/lib/file-storage" + +export async function POST(request: NextRequest) { + const session = await getServerSession(authOptions) + if (!session?.user?.companyId) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) + } + + const vendorId = session.user.companyId + const userId = session.user.id + + const vendor = await db.query.vendors.findFirst({ + where: eq(vendors.id, session.user.companyId), + columns: { + vendorName: true, + vendorCode: true, + } + }) + + try { + const formData = await request.formData() + const files = formData.getAll('files') as File[] + + // 메타데이터 파싱 + const metadata: any[] = [] + let index = 0 + while (formData.has(`metadata[${index}]`)) { + const meta = formData.get(`metadata[${index}]`) + if (meta) { + metadata.push(JSON.parse(meta as string)) + } + index++ + } + + if (files.length !== metadata.length) { + return NextResponse.json( + { error: "Files and metadata count mismatch" }, + { status: 400 } + ) + } + + // 총 용량 체크 (10GB) + const totalSize = files.reduce((acc, file) => acc + file.size, 0) + const maxSize = 10 * 1024 * 1024 * 1024 // 10GB + + if (totalSize > maxSize) { + return NextResponse.json( + { error: `Total file size exceeds 10GB limit` }, + { status: 400 } + ) + } + + const uploadResults = [] + + // 각 파일 처리 + for (let i = 0; i < files.length; i++) { + const file = files[i] + const meta = metadata[i] + + try { + await db.transaction(async (tx) => { + // 리비전 정보 추출 + const revisionNumber = extractRevisionNumber(meta.revision) + const revisionCode = normalizeRevisionCode(meta.revision) // ⭐ 추가 + + // 1. 해당 스테이지의 최신 submission 찾기 + let submission = await tx + .select() + .from(stageSubmissions) + .where( + and( + eq(stageSubmissions.stageId, meta.stageId), + eq(stageSubmissions.documentId, meta.documentId) + ) + ) + .orderBy(stageSubmissions.revisionNumber) + .limit(1) + + if (!submission[0] || submission[0].revisionNumber < revisionNumber) { + // 새 submission 생성 + const [newSubmission] = await tx + .insert(stageSubmissions) + .values({ + stageId: meta.stageId, + documentId: meta.documentId, + revisionNumber, + revisionCode, // ⭐ 원본 리비전 코드 저장 + revisionType: revisionNumber === 0 ? "INITIAL" : "RESUBMISSION", + submissionStatus: "SUBMITTED", + submittedBy: session.user.name || session.user.email || "Unknown", + submittedByEmail: session.user.email, + vendorId, + vendorCode: vendor?.vendorCode || null, + totalFiles: 1, + totalFileSize: file.size, + submissionTitle: `Revision ${revisionCode} Submission`, // ⭐ 코드 사용 + syncStatus: "pending", + lastModifiedBy: "EVCP", + }) + .returning() + + submission = [newSubmission] + } else if (submission[0].revisionNumber === revisionNumber) { + // 같은 리비전 업데이트 + await tx + .update(stageSubmissions) + .set({ + revisionCode, // ⭐ 코드 업데이트 + totalFiles: submission[0].totalFiles + 1, + totalFileSize: submission[0].totalFileSize + file.size, + updatedAt: new Date(), + }) + .where(eq(stageSubmissions.id, submission[0].id)) + } + + // 2. 파일 저장 (대용량 파일은 스트리밍) + const directory = `submissions/${meta.documentId}/${meta.stageId}/${revisionCode}` // ⭐ 디렉토리에 리비전 코드 포함 + + let saveResult + if (file.size > 100 * 1024 * 1024) { // 100MB 이상은 스트리밍 + saveResult = await saveFileStream({ + file, + directory, + originalName: meta.originalName, + userId: userId.toString() + }) + } else { + const { saveFile } = await import("@/lib/file-storage") + saveResult = await saveFile({ + file, + directory, + originalName: meta.originalName, + userId: userId.toString() + }) + } + + if (!saveResult.success) { + throw new Error(saveResult.error || "File save failed") + } + + // 3. attachment 레코드 생성 + await tx.insert(stageSubmissionAttachments).values({ + submissionId: submission[0].id, + fileName: saveResult.fileName!, + originalFileName: meta.originalName, + fileType: file.type, + fileExtension: meta.originalName.split('.').pop(), + fileSize: file.size, + storageType: "LOCAL", + storagePath: saveResult.filePath!, + storageUrl: saveResult.publicPath!, + mimeType: file.type, + uploadedBy: session.user.name || session.user.email || "Unknown", + status: "ACTIVE", + syncStatus: "pending", + lastModifiedBy: "EVCP", + }) + }) + + uploadResults.push({ + fileName: meta.originalName, + success: true + }) + + } catch (error) { + console.error(`Failed to upload ${meta.originalName}:`, error) + uploadResults.push({ + fileName: meta.originalName, + success: false, + error: error instanceof Error ? error.message : "Upload failed" + }) + } + } + + const successCount = uploadResults.filter(r => r.success).length + + return NextResponse.json({ + success: true, + uploaded: successCount, + failed: uploadResults.length - successCount, + results: uploadResults + }) + + } catch (error) { + console.error("Bulk upload error:", error) + return NextResponse.json( + { error: "Bulk upload failed" }, + { status: 500 } + ) + } +}
\ No newline at end of file |
