tia/src/app/api/img/route.ts
Mannu 3cfcbdc0ca fix: garment upload MIME/proxy, log edit time pre-fill, date-ist hardening, wardrobe camera+gallery
- /api/img: add garments/ to ALLOWED_PREFIXES so garment images proxy correctly
- garments upload: resolve Android empty MIME type from file extension; return
  /api/img proxy URLs instead of raw pub-*.r2.dev (blocked by Cloudflare Bot Mgmt)
- garments route + [id] route: toDto() now builds /api/img?key= proxy URLs
- date-ist.ts: add toUTCDate() helper -- strings without Z/offset treated as UTC,
  preventing browser local-time misinterpretation; used in fmtTime, fmtDate, dateIST
- LogModal: add editTime to SmartDefault; pre-fill time picker (custom preset) when
  editing an existing log instead of defaulting to now
- activity page: pass editTime: log.loggedAt in handleEdit so LogModal pre-fills
- wardrobe/add: explicit Camera and Gallery buttons via separate hidden inputs
  (one with capture=environment for direct camera, one without for media picker)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 00:42:04 +05:30

50 lines
1.9 KiB
TypeScript

import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { NextRequest, NextResponse } from "next/server";
const ALLOWED_PREFIXES = ["avatars/", "profiles/", "memories/", "thumbnails/", "families/", "garments/"];
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 });
}
}