import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"; import { NextRequest, NextResponse } from "next/server"; const ALLOWED_PREFIXES = ["avatars/", "profiles/", "memories/", "thumbnails/", "families/"]; export async function GET(req: NextRequest) { const key = req.nextUrl.searchParams.get("key"); if (!key) return NextResponse.json({ error: "key required" }, { status: 400 }); // Only proxy our own R2 objects if (!ALLOWED_PREFIXES.some(p => key.startsWith(p))) { return NextResponse.json({ error: "Invalid key" }, { status: 403 }); } const accountId = process.env.R2_ACCOUNT_ID; const accessKeyId = process.env.R2_ACCESS_KEY_ID; const secretKey = process.env.R2_SECRET_ACCESS_KEY; const bucket = process.env.R2_BUCKET_NAME; if (!accountId || !accessKeyId || !secretKey || !bucket) { return NextResponse.json({ error: "Storage not configured" }, { status: 500 }); } const client = new S3Client({ region: "auto", endpoint: `https://${accountId}.r2.cloudflarestorage.com`, credentials: { accessKeyId, secretAccessKey: secretKey }, }); try { const obj = await client.send(new GetObjectCommand({ Bucket: bucket, Key: key })); if (!obj.Body) return new NextResponse(null, { status: 404 }); const bytes = await (obj.Body as any).transformToByteArray(); return new NextResponse(bytes, { status: 200, headers: { "Content-Type": obj.ContentType || "image/jpeg", "Cache-Control": "public, max-age=604800, immutable", ...(obj.ContentLength ? { "Content-Length": String(obj.ContentLength) } : {}), }, }); } catch (e: any) { if (e?.name === "NoSuchKey" || e?.$metadata?.httpStatusCode === 404) { return new NextResponse(null, { status: 404 }); } console.error("R2 img proxy error:", e); return new NextResponse(null, { status: 502 }); } }