From 8479f50daac54c72e304ca2b967bfe35ef193b01 Mon Sep 17 00:00:00 2001 From: William Valentin Date: Sun, 1 Feb 2026 16:47:50 -0800 Subject: [PATCH] feat: add asset variants endpoint --- .../web/app/api/assets/[id]/variants/route.ts | 43 +++++++++++++++++++ .../web/app/api/assets/[id]/variants/shape.ts | 19 ++++++++ apps/web/src/__tests__/variants-route.test.ts | 13 ++++++ 3 files changed, 75 insertions(+) create mode 100644 apps/web/app/api/assets/[id]/variants/route.ts create mode 100644 apps/web/app/api/assets/[id]/variants/shape.ts create mode 100644 apps/web/src/__tests__/variants-route.test.ts diff --git a/apps/web/app/api/assets/[id]/variants/route.ts b/apps/web/app/api/assets/[id]/variants/route.ts new file mode 100644 index 0000000..80f9207 --- /dev/null +++ b/apps/web/app/api/assets/[id]/variants/route.ts @@ -0,0 +1,43 @@ +import { z } from "zod"; + +import { getDb } from "@tline/db"; + +import { shapeVariants } from "./shape"; + +export const runtime = "nodejs"; + +const paramsSchema = z.object({ + id: z.string().uuid(), +}); + +export async function GET( + _request: Request, + context: { params: Promise<{ id: string }> }, +): Promise { + 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 db = getDb(); + const rows = await db< + { + kind: string; + size: number; + key: string; + mime_type: string; + width: number | null; + height: number | null; + }[] + >` + select kind, size, key, mime_type, width, height + from asset_variants + where asset_id = ${paramsParsed.data.id} + `; + + return Response.json(shapeVariants(rows)); +} diff --git a/apps/web/app/api/assets/[id]/variants/shape.ts b/apps/web/app/api/assets/[id]/variants/shape.ts new file mode 100644 index 0000000..a44613f --- /dev/null +++ b/apps/web/app/api/assets/[id]/variants/shape.ts @@ -0,0 +1,19 @@ +type VariantRow = { + kind: string; + size: number; + key: string; +}; + +type VariantShape = { + kind: string; + size: number; + key: string; +}; + +export function shapeVariants(rows: VariantRow[]): VariantShape[] { + return rows.map((row) => ({ + kind: row.kind, + size: row.size, + key: row.key, + })); +} diff --git a/apps/web/src/__tests__/variants-route.test.ts b/apps/web/src/__tests__/variants-route.test.ts new file mode 100644 index 0000000..8d9d727 --- /dev/null +++ b/apps/web/src/__tests__/variants-route.test.ts @@ -0,0 +1,13 @@ +import { test, expect } from "bun:test"; + +test("variants route returns only kind/size/key fields", async () => { + const { shapeVariants } = await import( + "../../app/api/assets/[id]/variants/shape", + ); + const rows = [ + { kind: "video_mp4", size: 720, key: "derived/video/a.mp4", mime_type: "video/mp4" }, + ]; + expect(shapeVariants(rows)).toEqual([ + { kind: "video_mp4", size: 720, key: "derived/video/a.mp4" }, + ]); +});