summaryrefslogtreecommitdiff
path: root/app/api/stage-submissions/bulk-upload
diff options
context:
space:
mode:
Diffstat (limited to 'app/api/stage-submissions/bulk-upload')
-rw-r--r--app/api/stage-submissions/bulk-upload/route.ts202
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