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; try { prisma = new PrismaClient(); } catch (error) { // DB 가 준비되지 않은 환경에서는 mimeType 조회 없이 기본값만 사용한다. console.error("PrismaClient 초기화 실패 - 비디오 MIME 타입은 기본값으로만 응답합니다.", error); prisma = null; } interface Params { params: Promise<{ id: string; }>; } export async function GET(request: Request, ctx: Params) { // Next.js 가 params 를 항상 보장하지 않는 상황을 대비해, URL 경로에서 id 를 보완적으로 파싱한다. let id: string | undefined; try { const resolved = await ctx.params; id = resolved?.id; } catch { id = undefined; } if (!id) { try { const url = new URL(request.url); const segments = url.pathname.split("/").filter(Boolean); // [..., "api", "video", ":id"] 형태를 기대한다. const last = segments[segments.length - 1]; if (last && last !== "video") { 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 타입은 mp4 로 두되, 가능하면 Asset 메타데이터에서 실제 타입을 읽어온다. let mimeType = "video/mp4"; 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 }); } }