import { NextResponse } from "next/server"; import { prisma } from "@/lib/db"; import { deleteDraftImageFile, deleteDraftImages, isDraftOwnedImagePath, MAX_DRAFT_IMAGES, parseDraftImagePaths, readDraftImage, saveDraftImage, syncDraftImageFields, validateDraftImageFile, } from "@/lib/drafts/images"; import { apiRouteErrorResponse } from "@/lib/auth/api"; function resolveImagePath( draft: { id: string; imagePath?: string | null; imagePaths?: string | null }, requested?: string | null ): string | null { const paths = parseDraftImagePaths(draft); if (paths.length === 0) return null; if (requested) { const match = paths.find((p) => p === requested || p.endsWith(`/${requested}`)); if (match && isDraftOwnedImagePath(draft.id, match)) return match; return null; } return paths[0] ?? null; } export async function GET( request: Request, { params }: { params: Promise<{ id: string }> } ) { try { const { id } = await params; const draft = await prisma.draft.findUnique({ where: { id } }); if (!draft) { return NextResponse.json({ error: "找不到草稿" }, { status: 404 }); } const requested = new URL(request.url).searchParams.get("p"); const imagePath = resolveImagePath(draft, requested); if (!imagePath) { return NextResponse.json({ error: "沒有配圖" }, { status: 404 }); } const image = await readDraftImage(imagePath); if (!image) { return NextResponse.json({ error: "找不到圖片檔案" }, { status: 404 }); } return new NextResponse(new Uint8Array(image.bytes), { headers: { "Content-Type": image.mimeType, "Cache-Control": "private, no-store, max-age=0", }, }); } catch (error) { return apiRouteErrorResponse(error, "drafts/image"); } } export async function POST( request: Request, { params }: { params: Promise<{ id: string }> } ) { try { const { id } = await params; const draft = await prisma.draft.findUnique({ where: { id } }); if (!draft) { return NextResponse.json({ error: "找不到草稿" }, { status: 404 }); } const formData = await request.formData(); const incoming = [ ...formData.getAll("file"), ...formData.getAll("files"), ].filter((item): item is File => item instanceof File); if (incoming.length === 0) { return NextResponse.json({ error: "請上傳圖片檔案" }, { status: 400 }); } const existing = parseDraftImagePaths(draft); if (existing.length + incoming.length > MAX_DRAFT_IMAGES) { return NextResponse.json( { error: `每篇草稿最多 ${MAX_DRAFT_IMAGES} 張配圖` }, { status: 400 } ); } const saved: string[] = []; for (const file of incoming) { const validationError = validateDraftImageFile(file); if (validationError) { await deleteDraftImages(saved); return NextResponse.json({ error: validationError }, { status: 400 }); } const bytes = new Uint8Array(await file.arrayBuffer()); saved.push(await saveDraftImage(id, bytes, file.type)); } const nextPaths = [...existing, ...saved]; const updated = await prisma.draft.update({ where: { id }, data: syncDraftImageFields(nextPaths), }); return NextResponse.json({ draft: updated, imagePaths: parseDraftImagePaths(updated), added: saved, }); } catch (error) { const message = error instanceof Error ? error.message : "上傳失敗"; return NextResponse.json({ error: message }, { status: 500 }); } } export async function DELETE( request: Request, { params }: { params: Promise<{ id: string }> } ) { try { const { id } = await params; const draft = await prisma.draft.findUnique({ where: { id } }); if (!draft) { return NextResponse.json({ error: "找不到草稿" }, { status: 404 }); } const requested = new URL(request.url).searchParams.get("p"); const existing = parseDraftImagePaths(draft); if (!requested) { await deleteDraftImages(existing); const updated = await prisma.draft.update({ where: { id }, data: { imagePath: null, imagePaths: null }, }); return NextResponse.json({ draft: updated, imagePaths: [] }); } const target = existing.find((p) => p === requested || p.endsWith(`/${requested}`)); if (!target || !isDraftOwnedImagePath(id, target)) { return NextResponse.json({ error: "找不到要刪除的配圖" }, { status: 404 }); } await deleteDraftImageFile(target); const nextPaths = existing.filter((p) => p !== target); const updated = await prisma.draft.update({ where: { id }, data: syncDraftImageFields(nextPaths), }); return NextResponse.json({ draft: updated, imagePaths: parseDraftImagePaths(updated), }); } catch (error) { return apiRouteErrorResponse(error, "drafts/image/delete"); } }