diff options
Diffstat (limited to 'app/api')
| -rw-r--r-- | app/api/projects/[projectId]/cover/route.ts | 73 | ||||
| -rw-r--r-- | app/api/projects/cover-template/save/route.ts | 125 | ||||
| -rw-r--r-- | app/api/projects/cover-template/upload/route.ts | 127 |
3 files changed, 325 insertions, 0 deletions
diff --git a/app/api/projects/[projectId]/cover/route.ts b/app/api/projects/[projectId]/cover/route.ts new file mode 100644 index 00000000..b88f06ee --- /dev/null +++ b/app/api/projects/[projectId]/cover/route.ts @@ -0,0 +1,73 @@ +// app/api/projects/[projectId]/cover/route.ts +import { NextRequest, NextResponse } from "next/server" +import db from "@/db/db" +import { projectCoverTemplates, generatedCoverPages } from "@/db/schema" +import { eq, and, desc } from "drizzle-orm" + +export async function GET( + request: NextRequest, + { params }: { params: { projectId: string } } +) { + try { + const projectId = parseInt(params.projectId) + + if (isNaN(projectId)) { + return NextResponse.json( + { success: false, message: "유효하지 않은 프로젝트 ID입니다" }, + { status: 400 } + ) + } + + // 1. 해당 프로젝트의 활성 템플릿 찾기 + const [activeTemplate] = await db + .select() + .from(projectCoverTemplates) + .where( + and( + eq(projectCoverTemplates.projectId, projectId), + eq(projectCoverTemplates.isActive, true) + ) + ) + .limit(1) + + if (!activeTemplate) { + return NextResponse.json( + { success: false, message: "활성 템플릿을 찾을 수 없습니다" }, + { status: 404 } + ) + } + + // 2. 해당 템플릿의 최신 생성된 커버 페이지 찾기 + const [latestCover] = await db + .select() + .from(generatedCoverPages) + .where(eq(generatedCoverPages.templateId, activeTemplate.id)) + .orderBy(desc(generatedCoverPages.generatedAt)) + .limit(1) + + if (!latestCover) { + return NextResponse.json( + { success: false, message: "생성된 커버 페이지를 찾을 수 없습니다" }, + { status: 404 } + ) + } + + // 3. 파일 경로와 정보 반환 + return NextResponse.json({ + success: true, + fileUrl: latestCover.filePath, + fileName: latestCover.fileName, + generatedAt: latestCover.generatedAt, + }) + + } catch (error) { + console.error("❌ 커버 페이지 조회 오류:", error) + return NextResponse.json( + { + success: false, + message: error instanceof Error ? error.message : "조회 중 오류 발생" + }, + { status: 500 } + ) + } +}
\ No newline at end of file diff --git a/app/api/projects/cover-template/save/route.ts b/app/api/projects/cover-template/save/route.ts new file mode 100644 index 00000000..e681512d --- /dev/null +++ b/app/api/projects/cover-template/save/route.ts @@ -0,0 +1,125 @@ +// app/api/projects/cover-template/save/route.ts +import { saveFile } from "@/lib/file-stroage" +import db from "@/db/db" +import { projectCoverTemplates, generatedCoverPages } from "@/db/schema" +import { eq, and, desc } from "drizzle-orm" +import { NextRequest, NextResponse } from "next/server" +import { revalidateTag } from "next/cache" +import { getServerSession } from 'next-auth'; +import { authOptions } from '@/app/api/auth/[...nextauth]/route'; + +export async function POST(request: NextRequest) { + try { + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + return NextResponse.json( + { error: '인증이 필요합니다' }, + { status: 401 } + ); + } + + const formData = await request.formData() + const file = formData.get("file") as File + const projectId = formData.get("projectId") as string + const templateName = formData.get("templateName") as string | null + + if (!file) { + return NextResponse.json( + { success: false, message: "파일이 없습니다" }, + { status: 400 } + ) + } + + if (!projectId) { + return NextResponse.json( + { success: false, message: "프로젝트 ID가 없습니다" }, + { status: 400 } + ) + } + + // 해당 프로젝트의 활성 템플릿 찾기 + const [activeTemplate] = await db + .select() + .from(projectCoverTemplates) + .where( + and( + eq(projectCoverTemplates.projectId, parseInt(projectId)), + eq(projectCoverTemplates.isActive, true) + ) + ) + .limit(1) + + if (!activeTemplate) { + return NextResponse.json( + { success: false, message: "활성 템플릿을 찾을 수 없습니다" }, + { status: 404 } + ) + } + + // 생성된 커버 페이지 저장 디렉토리 + const coverPagesDirectory = `projects/${projectId}/generated-covers` + + // 파일명 생성 (타임스탬프 포함) + const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5) + const fileName = templateName + ? `${templateName}_${timestamp}.docx` + : `cover_${timestamp}.docx` + + // 파일 저장 + const saveResult = await saveFile({ + file, + directory: coverPagesDirectory, + originalName: fileName, + }) + + if (!saveResult.success) { + return NextResponse.json( + { success: false, message: saveResult.error || "파일 저장 실패" }, + { status: 500 } + ) + } + + // TODO: 실제로는 문서에서 변수 값을 추출하거나 별도로 전달받아야 함 + // 현재는 빈 객체로 저장 (추후 확장 가능) + const variableValues = {} + + // generatedCoverPages 테이블에 저장 + const [generatedCover] = await db + .insert(generatedCoverPages) + .values({ + templateId: activeTemplate.id, + variableValues: variableValues, + fileName: saveResult.fileName, + filePath: saveResult.publicPath, + fileSize: saveResult.fileSize, + generatedBy: session.user.name, + }) + .returning() + + console.log(`✅ 커버 페이지 생성 완료: ${saveResult.fileName}`) + console.log(`✅ DB 저장 완료 - Generated Cover ID: ${generatedCover.id}`) + + // 캐시 무효화 + revalidateTag("project-cover-lists") + + return NextResponse.json({ + success: true, + generatedCoverId: generatedCover.id, + templateId: activeTemplate.id, + filePath: saveResult.publicPath, + fileName: saveResult.fileName, + fileSize: saveResult.fileSize, + message: "커버 페이지가 저장되었습니다" + }) + + } catch (error) { + console.error("❌ 커버 페이지 저장 오류:", error) + return NextResponse.json( + { + success: false, + message: error instanceof Error ? error.message : "저장 중 오류 발생" + }, + { status: 500 } + ) + } +}
\ No newline at end of file diff --git a/app/api/projects/cover-template/upload/route.ts b/app/api/projects/cover-template/upload/route.ts new file mode 100644 index 00000000..9c8df7ca --- /dev/null +++ b/app/api/projects/cover-template/upload/route.ts @@ -0,0 +1,127 @@ +// app/api/projects/cover-template/upload/route.ts +import db from "@/db/db" +import { projectCoverTemplates } from "@/db/schema" +import { saveFile } from "@/lib/file-stroage" +import { eq, and } from "drizzle-orm" +import { NextRequest, NextResponse } from "next/server" +import { revalidateTag } from "next/cache" +import { getServerSession } from 'next-auth'; +import { authOptions } from '@/app/api/auth/[...nextauth]/route'; + +export async function POST(request: NextRequest) { + try { + + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + return NextResponse.json( + { error: '인증이 필요합니다' }, + { status: 401 } + ); + } + + const formData = await request.formData() + const file = formData.get("file") as File + const projectId = formData.get("projectId") as string + + if (!file) { + return NextResponse.json( + { success: false, message: "파일이 없습니다" }, + { status: 400 } + ) + } + + if (!projectId) { + return NextResponse.json( + { success: false, message: "프로젝트 ID가 없습니다" }, + { status: 400 } + ) + } + + // 파일 확장자 확인 + if (!file.name.endsWith('.docx')) { + return NextResponse.json( + { success: false, message: "DOCX 파일만 업로드 가능합니다" }, + { status: 400 } + ) + } + + // 템플릿 디렉토리 + const templateDirectory = `projects/${projectId}/cover-templates` + + // 파일 저장 + const saveResult = await saveFile({ + file, + directory: templateDirectory, + originalName: file.name, + }) + + if (!saveResult.success) { + return NextResponse.json( + { success: false, message: saveResult.error || "파일 저장 실패" }, + { status: 500 } + ) + } + + // 기존 활성 템플릿 비활성화 + await db + .update(projectCoverTemplates) + .set({ + isActive: false, + updatedAt: new Date() + }) + .where( + and( + eq(projectCoverTemplates.projectId, parseInt(projectId)), + eq(projectCoverTemplates.isActive, true) + ) + ) + + // 기본 템플릿 변수 설정 + const defaultVariables = { + docNumber: "{{docNumber}}", + projectNumber: "{{projectNumber}}", + projectName: "{{projectName}}" + } + + // 새 템플릿을 DB에 저장 + const [newTemplate] = await db + .insert(projectCoverTemplates) + .values({ + projectId: parseInt(projectId), + templateName: file.name.replace('.docx', ''), + originalFileName: file.name, + filePath: saveResult.publicPath, + fileSize: saveResult.fileSize, + variables: defaultVariables, + isActive: true, + createdBy: session.user.name, // TODO: 실제 사용자 정보로 변경 + updatedBy: session.user.name, + }) + .returning() + + console.log(`✅ 커버 템플릿 업로드 완료: ${saveResult.fileName}`) + console.log(`✅ DB 저장 완료 - Template ID: ${newTemplate.id}`) + + // 캐시 무효화 + revalidateTag("project-cover-lists") + + return NextResponse.json({ + success: true, + templateId: newTemplate.id, + filePath: saveResult.publicPath, + fileName: saveResult.fileName, + fileSize: saveResult.fileSize, + variables: defaultVariables, + }) + + } catch (error) { + console.error("❌ 템플릿 업로드 오류:", error) + return NextResponse.json( + { + success: false, + message: error instanceof Error ? error.message : "업로드 중 오류 발생" + }, + { status: 500 } + ) + } +}
\ No newline at end of file |
