diff --git a/apps/web/app/api/assets/[id]/url/route.ts b/apps/web/app/api/assets/[id]/url/route.ts index d58384f..bb401b5 100644 --- a/apps/web/app/api/assets/[id]/url/route.ts +++ b/apps/web/app/api/assets/[id]/url/route.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import { getDb } from "@tline/db"; import { presignGetObjectUrl } from "@tline/minio"; -import { pickVariantKey } from "./variant"; +import { pickLegacyKeyForRequest, pickVariantKey } from "./variant"; export const runtime = "nodejs"; @@ -13,6 +13,7 @@ const paramsSchema = z.object({ const legacyVariantSchema = z.enum(["original", "thumb_small", "thumb_med", "poster"]); const kindSchema = z.enum(["original", "thumb", "poster", "video_mp4"]); const sizeSchema = z.coerce.number().int().positive(); +const videoMp4DefaultSize = 720; const legacyVariantMap = { original: { kind: "original" as const }, thumb_small: { kind: "thumb" as const, size: 256 }, @@ -53,14 +54,18 @@ export async function GET( } requestedKind = kindParsed.data; if (requestedKind !== "original") { - const sizeParsed = sizeSchema.safeParse(sizeParam); - if (!sizeParsed.success) { - return Response.json( - { error: "invalid_query", issues: sizeParsed.error.issues }, - { status: 400 }, - ); + if (requestedKind === "video_mp4" && !sizeParam) { + requestedSize = videoMp4DefaultSize; + } else { + const sizeParsed = sizeSchema.safeParse(sizeParam); + if (!sizeParsed.success) { + return Response.json( + { error: "invalid_query", issues: sizeParsed.error.issues }, + { status: 400 }, + ); + } + requestedSize = sizeParsed.data; } - requestedSize = sizeParsed.data; } } else if (legacyVariantParam) { const legacyParsed = legacyVariantSchema.safeParse(legacyVariantParam); @@ -113,14 +118,17 @@ export async function GET( return Response.json({ error: "not_found" }, { status: 404 }); } - const legacyKey = - legacyVariant === "thumb_small" - ? asset.thumb_small_key - : legacyVariant === "thumb_med" - ? asset.thumb_med_key - : legacyVariant === "poster" - ? asset.poster_key - : null; + const legacyKey = legacyVariant + ? pickLegacyKeyForRequest( + { asset }, + { kind: requestedKind, size: requestedSize ?? 0 }, + ) + : requestedSize !== null + ? pickLegacyKeyForRequest( + { asset }, + { kind: requestedKind, size: requestedSize }, + ) + : null; const key = requestedKind === "original" diff --git a/apps/web/app/api/assets/[id]/url/variant.ts b/apps/web/app/api/assets/[id]/url/variant.ts index 6e01082..645357e 100644 --- a/apps/web/app/api/assets/[id]/url/variant.ts +++ b/apps/web/app/api/assets/[id]/url/variant.ts @@ -7,3 +7,25 @@ export function pickVariantKey( ); return v?.key ?? null; } + +export function pickLegacyKeyForRequest( + input: { + asset: { + thumb_small_key: string | null; + thumb_med_key: string | null; + poster_key: string | null; + }; + }, + req: { kind: string; size: number }, +) { + if (req.kind === "thumb" && req.size === 256) { + return input.asset.thumb_small_key ?? null; + } + if (req.kind === "thumb" && req.size === 768) { + return input.asset.thumb_med_key ?? null; + } + if (req.kind === "poster" && req.size === 256) { + return input.asset.poster_key ?? null; + } + return null; +} diff --git a/apps/web/src/__tests__/variant-url-404.test.ts b/apps/web/src/__tests__/variant-url-404.test.ts index 369fa08..17b66d9 100644 --- a/apps/web/src/__tests__/variant-url-404.test.ts +++ b/apps/web/src/__tests__/variant-url-404.test.ts @@ -1,9 +1,33 @@ import { test, expect } from "bun:test"; -test("/api/assets/:id/url returns 404 when requested variant missing", async () => { +test("variant lookup returns null when no matching variant", async () => { const { pickVariantKey } = await import( "../../app/api/assets/[id]/url/variant", ); const key = pickVariantKey({ variants: [] }, { kind: "thumb", size: 256 }); expect(key).toBeNull(); }); + +test("legacy fallback maps kind+size to asset keys", async () => { + const { pickLegacyKeyForRequest } = await import( + "../../app/api/assets/[id]/url/variant", + ); + const asset = { + thumb_small_key: "thumb-small", + thumb_med_key: "thumb-med", + poster_key: "poster", + }; + + expect( + pickLegacyKeyForRequest({ asset }, { kind: "thumb", size: 256 }), + ).toBe("thumb-small"); + expect( + pickLegacyKeyForRequest({ asset }, { kind: "thumb", size: 768 }), + ).toBe("thumb-med"); + expect( + pickLegacyKeyForRequest({ asset }, { kind: "poster", size: 256 }), + ).toBe("poster"); + expect( + pickLegacyKeyForRequest({ asset }, { kind: "thumb", size: 1024 }), + ).toBeNull(); +});