Files
porthole/apps/web/app/api/assets/[id]/url/route.ts
OpenCode Test e1a64aa092 Initial commit
2025-12-24 10:50:10 -08:00

95 lines
2.4 KiB
TypeScript

import { z } from "zod";
import { getDb } from "@tline/db";
import { presignGetObjectUrl } from "@tline/minio";
export const runtime = "nodejs";
const paramsSchema = z.object({
id: z.string().uuid()
});
const variantSchema = z.enum(["original", "thumb_small", "thumb_med", "poster"]);
export async function GET(
request: Request,
context: { params: Promise<{ id: string }> }
): Promise<Response> {
const rawParams = await context.params;
const paramsParsed = paramsSchema.safeParse(rawParams);
if (!paramsParsed.success) {
return Response.json(
{ error: "invalid_params", issues: paramsParsed.error.issues },
{ status: 400 },
);
}
const params = paramsParsed.data;
const url = new URL(request.url);
const variantParsed = variantSchema.safeParse(url.searchParams.get("variant") ?? "original");
if (!variantParsed.success) {
return Response.json(
{ error: "invalid_query", issues: variantParsed.error.issues },
{ status: 400 },
);
}
const variant = variantParsed.data;
const db = getDb();
const rows = await db<
{
bucket: string;
active_key: string;
thumb_small_key: string | null;
thumb_med_key: string | null;
poster_key: string | null;
mime_type: string;
}[]
>`
select bucket, active_key, thumb_small_key, thumb_med_key, poster_key, mime_type
from assets
where id = ${params.id}
limit 1
`;
const asset = rows[0];
if (!asset) {
return Response.json({ error: "not_found" }, { status: 404 });
}
const key =
variant === "original"
? asset.active_key
: variant === "thumb_small"
? asset.thumb_small_key
: variant === "thumb_med"
? asset.thumb_med_key
: asset.poster_key;
if (!key) {
return Response.json(
{ error: "variant_not_available", variant },
{ status: 404 }
);
}
// Hint the browser; especially helpful for Range playback.
const responseContentType = variant === "original" ? asset.mime_type : "image/jpeg";
const responseContentDisposition =
variant === "original" && asset.mime_type.startsWith("video/") ? "inline" : undefined;
const signed = await presignGetObjectUrl({
bucket: asset.bucket,
key,
responseContentType,
responseContentDisposition,
});
return Response.json(signed, {
headers: {
"Cache-Control": "no-store"
}
});
}