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 { revalidateTag } from "next/cache" import db from "@/db/db" import { documents, revisions, documentAttachments, issueStages, } from "@/db/schema/vendorDocu" import { eq } from "drizzle-orm" /* change log 유틸 */ import { logAttachmentChange, } from "@/lib/vendor-document-list/sync-service" export async function POST(request: NextRequest) { try { const formData = await request.formData() /* ------- 파라미터 파싱 ------- */ const revisionId = Number(formData.get("revisionId")) const uploaderName = formData.get("uploaderName") as string | null const targetSystem = (formData.get("targetSystem") as string | null) ?? "DOLCE" const attachmentFiles = formData.getAll("attachments") as File[] /* ------- 검증 ------- */ if (!revisionId || Number.isNaN(revisionId)) return NextResponse.json({ error: "Invalid revisionId" }, { status: 400 }) if (!attachmentFiles.length) return NextResponse.json({ error: "No files provided" }, { status: 400 }) const MAX = 50 * 1024 * 1024 // 50MB for (const f of attachmentFiles) if (f.size > MAX) return NextResponse.json( { error: `${f.name} exceeds 50MB limit` }, { status: 400 } ) /* ------- 리비전 및 계약 정보 확보 ------- */ const [revisionInfo] = await db .select({ id: revisions.id, revision: revisions.revision, usage: revisions.usage, usageType: revisions.usageType, issueStageId: revisions.issueStageId, projectId: documents.projectId, }) .from(revisions) .innerJoin(issueStages, eq(revisions.issueStageId, issueStages.id)) .innerJoin(documents, eq(issueStages.documentId, documents.id)) .where(eq(revisions.id, revisionId)) .limit(1) if (!revisionInfo) { return NextResponse.json({ error: "Revision not found" }, { status: 404 }) } /* ------- 트랜잭션 ------- */ const result = await db.transaction(async (tx) => { /* 첨부파일 처리 */ const uploadedFiles: any[] = [] const baseDir = join(process.cwd(), "public", "documents") for (const file of attachmentFiles) { const ext = path.extname(file.name) const fname = uuidv4() + ext const dest = join(baseDir, fname) await writeFile(dest, Buffer.from(await file.arrayBuffer())) const [att] = await tx.insert(documentAttachments) .values({ revisionId, fileName: file.name, filePath: "/documents/" + fname, fileSize: file.size, fileType: ext.slice(1).toLowerCase() || undefined, updatedAt: new Date(), }) .returning() uploadedFiles.push({ id: att.id, fileName: file.name, fileSize: file.size, filePath: att.filePath, fileType: ext.slice(1).toLowerCase() || null, }) // change_logs: attachment CREATE await logAttachmentChange( revisionInfo.projectId, att.id, "CREATE", att, undefined, undefined, uploaderName ?? undefined, [targetSystem] ) } /* 리비전 updatedAt 업데이트 */ await tx.update(revisions) .set({ updatedAt: new Date() }) .where(eq(revisions.id, revisionId)) return { revisionId, revision: revisionInfo.revision, usage: revisionInfo.usage, usageType: revisionInfo.usageType, uploadedFiles, projectId: revisionInfo.projectId } }) // 캐시 무효화 try { // revalidateTag(`enhanced-documents-${result.projectId}`) revalidateTag(`sync-status-${result.projectId}`) console.log(`✅ Cache invalidated for contract ${result.projectId}`) } catch (cacheError) { console.warn('⚠️ Cache invalidation failed:', cacheError) } return NextResponse.json({ success: true, message: `${result.uploadedFiles.length}개 첨부파일이 추가되었습니다`, data: { revisionId: result.revisionId, revision: result.revision, usage: result.usage, usageType: result.usageType, uploadedFiles: result.uploadedFiles, filesCount: result.uploadedFiles.length }, }) } catch (e) { console.error("revision-attachment error:", e) return NextResponse.json( { error: "Failed to upload attachments", details: String(e) }, { status: 500 }, ) } }