import { NextResponse } from "next/server"; import { promises as fs } from "fs"; import path from "path"; import { PrismaClient } from "@prisma/client"; const UPLOAD_DIR = path.join(process.cwd(), "uploads"); let prisma: PrismaClient | null = null; if (process.env.DATABASE_URL && process.env.DATABASE_URL.trim() !== "") { try { prisma = new PrismaClient(); } catch (error) { // DB 가 준비되지 않은 환경에서는 mimeType 조회 없이 기본값만 사용한다. console.error("PrismaClient 초기화 실패 - 기본 MIME 타입으로만 응답합니다.", error); prisma = null; } } export async function GET(request: Request) { // Next.js 15 이후 params 가 Promise 로 전달되는 경고를 피하기 위해, // 동적 세그먼트는 request.url 의 path 에서 직접 파싱한다. let id: string | undefined; try { const url = new URL(request.url); const segments = url.pathname.split("/").filter(Boolean); // [..., "api", "image", ":id"] 형태를 기대한다. const last = segments[segments.length - 1]; if (last && last !== "image") { id = last; } } catch { // URL 파싱 실패 시에는 그대로 둔다. } if (!id) { return NextResponse.json({ message: "id 파라미터가 필요합니다." }, { status: 400 }); } // Referer 기반으로 간단한 핫링크 방지: 동일 호스트에서 온 요청만 허용한다. try { const requestUrl = new URL(request.url); const referer = request.headers.get("referer"); if (!referer) { return NextResponse.json({ message: "이미지 접근이 허용되지 않습니다." }, { status: 403 }); } const refererUrl = new URL(referer); if (refererUrl.host !== requestUrl.host) { return NextResponse.json({ message: "이미지 접근이 허용되지 않습니다." }, { status: 403 }); } } catch (error) { console.error("Referer 검사 중 오류", error); return NextResponse.json({ message: "이미지 접근이 허용되지 않습니다." }, { status: 403 }); } const filePath = path.join(UPLOAD_DIR, id); try { const bytes = await fs.readFile(filePath); // 기본 MIME 타입은 png 로 두되, 가능하면 Asset 메타데이터에서 실제 타입을 읽어온다. let mimeType = "image/png"; if (prisma) { try { const asset = await prisma.asset.findUnique({ where: { id } }); const meta: any = asset?.meta ?? null; const candidate = typeof meta?.mimeType === "string" ? meta.mimeType.trim() : ""; if (candidate) { mimeType = candidate; } } catch (error) { console.error("이미지 메타데이터 조회 실패 - 기본 MIME 타입으로 응답합니다.", error); } } return new NextResponse(bytes, { status: 200, headers: { "Content-Type": mimeType, "Cache-Control": "public, max-age=31536000, immutable", }, }); } catch (error: any) { if (error && typeof error === "object" && (error as any).code === "ENOENT") { return NextResponse.json({ message: "이미지를 찾을 수 없습니다." }, { status: 404 }); } console.error("이미지 파일 읽기 중 오류", error); return NextResponse.json({ message: "이미지 로드 중 오류가 발생했습니다." }, { status: 500 }); } }