import type { NextRequest } from "next/server"; import { readAttachmentForUser } from "@/features/attachments/service"; import { getCurrentUser } from "@/lib/dal"; /** * GET /api/files/:id[?v=thumb][&indir=1] — oturum ve hane yetkisi kontrol edilerek ek dosyayı * servis eder. Erişim yoksa (başka hane, gizli hesap, silinmiş kayıt) 404: varlık sızdırılmaz. */ export async function GET(request: NextRequest, ctx: RouteContext<"/api/files/[id]">) { const user = await getCurrentUser(); if (!user) return new Response(null, { status: 401 }); const { id } = await ctx.params; const params = request.nextUrl.searchParams; const file = await readAttachmentForUser( user.id, id, params.get("v") === "thumb" ? "thumb" : "full", ); if (!file) return new Response(null, { status: 404 }); const disposition = params.get("indir") === "1" ? "attachment" : "inline"; const fallbackName = file.mimeType === "application/pdf" ? "belge.pdf" : "gorsel.webp"; const name = toServedName(file.name || fallbackName, file.mimeType); return new Response(new Uint8Array(file.bytes), { headers: { "Content-Type": file.mimeType, "Content-Length": String(file.bytes.length), "Content-Disposition": `${disposition}; filename="${asciiName(name)}"; filename*=UTF-8''${encodeURIComponent(name)}`, // Yalnızca kullanıcının tarayıcısında; paylaşılan önbelleklere girmez. "Cache-Control": "private, max-age=3600", "X-Content-Type-Options": "nosniff", }, }); } /** Görseller WebP'ye çevrildiği için indirilen adın uzantısı içerikle uyumlu olsun. */ function toServedName(name: string, mimeType: string): string { const ext = mimeType === "application/pdf" ? ".pdf" : ".webp"; return name.replace(/\.[a-z0-9]{1,5}$/i, "") + ext; } function asciiName(name: string): string { return ( name .normalize("NFKD") .replace(/[^\x20-\x7e]/g, "") .replace(/["\\]/g, "") || "dosya" ); }