Fix broken images by proxying through the app instead of redirecting to internal MinIO

The presigned URLs pointed to a private IP unreachable from the browser.
The image route now streams bytes directly and the card API returns
proxy paths matching the dashboard's existing approach.

Made-with: Cursor
This commit is contained in:
Randall Stillwell 2026-04-07 12:38:08 -05:00
parent 72375d2fae
commit 58f38d92ed
2 changed files with 28 additions and 8 deletions

View file

@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/db";
import { getPresignedUrl, deleteObject } from "@/lib/minio";
import { deleteObject } from "@/lib/minio";
import { fireIntegrationEvent } from "@/lib/integrations";
import { logActivity, diffCardFields } from "@/lib/activity-log";
@ -18,10 +18,12 @@ export async function GET(
return NextResponse.json({ error: "Card not found" }, { status: 404 });
}
const [frontImageUrl, backImageUrl] = await Promise.all([
card.frontImagePath ? getPresignedUrl(card.frontImagePath) : null,
card.backImagePath ? getPresignedUrl(card.backImagePath) : null,
]);
const frontImageUrl = card.frontImagePath
? `/api/images/${card.frontImagePath}`
: null;
const backImageUrl = card.backImagePath
? `/api/images/${card.backImagePath}`
: null;
return NextResponse.json({
...card,

View file

@ -1,5 +1,14 @@
import { NextRequest, NextResponse } from "next/server";
import { getPresignedUrl } from "@/lib/minio";
import { getBuffer } from "@/lib/minio";
const EXT_TO_CONTENT_TYPE: Record<string, string> = {
jpg: "image/jpeg",
jpeg: "image/jpeg",
png: "image/png",
webp: "image/webp",
gif: "image/gif",
pdf: "application/pdf",
};
export async function GET(
_request: NextRequest,
@ -13,8 +22,17 @@ export async function GET(
return NextResponse.json({ error: "Missing path" }, { status: 400 });
}
const url = await getPresignedUrl(key);
return NextResponse.redirect(url);
const ext = key.split(".").pop()?.toLowerCase() ?? "";
const contentType = EXT_TO_CONTENT_TYPE[ext] || "application/octet-stream";
const buffer = await getBuffer(key);
return new NextResponse(buffer, {
headers: {
"Content-Type": contentType,
"Cache-Control": "private, max-age=3600",
},
});
} catch (error) {
console.error("[images GET]", error);
return NextResponse.json(