import { getAdminToken, isAdminRequest } from "@tline/config"; import { getDb } from "@tline/db"; import { z } from "zod"; const ADMIN_HEADER = "X-Porthole-Admin-Token"; const createAlbumBodySchema = z .object({ name: z.string().min(1), }) .strict(); const albumParamsSchema = z.object({ id: z.string().uuid(), }); const albumAssetBodySchema = z .object({ assetId: z.string().uuid(), ord: z.coerce.number().int().optional(), }) .strict(); type DbLike = ReturnType; export function getAdminOk(headers: Headers) { const headerToken = headers.get(ADMIN_HEADER); return isAdminRequest({ adminToken: getAdminToken() }, { headerToken }); } export async function handleListAlbums(input: { adminOk: boolean; db?: DbLike; }): Promise<{ status: number; body: unknown }> { if (!input.adminOk) { return { status: 401, body: { error: "admin_required" } }; } const db = (input.db ?? getDb()) as DbLike; const rows = await db< { id: string; name: string; created_at: string; }[] >` select id, name, created_at from albums order by created_at desc `; return { status: 200, body: rows }; } export async function handleCreateAlbum(input: { adminOk: boolean; body: unknown; db?: DbLike; }): Promise<{ status: number; body: unknown }> { if (!input.adminOk) { return { status: 401, body: { error: "admin_required" } }; } const bodyParsed = createAlbumBodySchema.safeParse(input.body ?? {}); if (!bodyParsed.success) { return { status: 400, body: { error: "invalid_body", issues: bodyParsed.error.issues }, }; } const body = bodyParsed.data; const db = (input.db ?? getDb()) as DbLike; const rows = await db< { id: string; name: string; created_at: string; }[] >` insert into albums (name) values (${body.name}) returning id, name, created_at `; const created = rows[0]; if (!created) { return { status: 500, body: { error: "insert_failed" } }; } const payload = JSON.stringify({ name: created.name }); await db` insert into audit_log (actor, action, entity_type, entity_id, payload) values ('admin', 'create', 'album', ${created.id}, ${payload}::jsonb) `; return { status: 200, body: created }; } export async function handleAddAlbumAsset(input: { adminOk: boolean; params: { id: string }; body: unknown; db?: DbLike; }): Promise<{ status: number; body: unknown }> { if (!input.adminOk) { return { status: 401, body: { error: "admin_required" } }; } const paramsParsed = albumParamsSchema.safeParse(input.params); if (!paramsParsed.success) { return { status: 400, body: { error: "invalid_params", issues: paramsParsed.error.issues }, }; } const body = albumAssetBodySchema.parse(input.body ?? {}); const db = (input.db ?? getDb()) as DbLike; const rows = await db< { album_id: string; asset_id: string; ord: number | null; }[] >` insert into album_assets (album_id, asset_id, ord) values (${paramsParsed.data.id}, ${body.assetId}, ${body.ord ?? null}) on conflict (album_id, asset_id) do update set ord = excluded.ord returning album_id, asset_id, ord `; const created = rows[0]; if (!created) { return { status: 500, body: { error: "insert_failed" } }; } const payload = JSON.stringify({ asset_id: created.asset_id, ord: created.ord, }); await db` insert into audit_log (actor, action, entity_type, entity_id, payload) values ('admin', 'add_asset', 'album', ${created.album_id}, ${payload}::jsonb) `; return { status: 200, body: created }; } export async function handleRemoveAlbumAsset(input: { adminOk: boolean; params: { id: string }; body: unknown; db?: DbLike; }): Promise<{ status: number; body: unknown }> { if (!input.adminOk) { return { status: 401, body: { error: "admin_required" } }; } const paramsParsed = albumParamsSchema.safeParse(input.params); if (!paramsParsed.success) { return { status: 400, body: { error: "invalid_params", issues: paramsParsed.error.issues }, }; } const body = albumAssetBodySchema.parse(input.body ?? {}); const db = (input.db ?? getDb()) as DbLike; await db` delete from album_assets where album_id = ${paramsParsed.data.id} and asset_id = ${body.assetId} `; const payload = JSON.stringify({ asset_id: body.assetId }); await db` insert into audit_log (actor, action, entity_type, entity_id, payload) values ('admin', 'remove_asset', 'album', ${paramsParsed.data.id}, ${payload}::jsonb) `; return { status: 200, body: { ok: true } }; }