import { getAdminToken, isAdminRequest } from "@tline/config"; import { getDb } from "@tline/db"; import { z } from "zod"; const ADMIN_HEADER = "X-Porthole-Admin-Token"; const paramsSchema = z.object({ id: z.string().uuid(), }); const bodySchema = z .object({ captureTsUtcOverride: z.string().datetime().nullable().optional(), captureOffsetMinutesOverride: z.number().int().nullable().optional(), }) .strict(); type DbLike = ReturnType; export function getAdminOk(headers: Headers) { const headerToken = headers.get(ADMIN_HEADER); return isAdminRequest({ adminToken: getAdminToken() }, { headerToken }); } export async function handleSetCaptureOverride(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 = paramsSchema.safeParse(input.params); if (!paramsParsed.success) { return { status: 400, body: { error: "invalid_params", issues: paramsParsed.error.issues }, }; } const bodyParsed = bodySchema.safeParse(input.body ?? {}); if (!bodyParsed.success) { return { status: 400, body: { error: "invalid_body", issues: bodyParsed.error.issues }, }; } const data = bodyParsed.data; const hasCaptureTs = "captureTsUtcOverride" in data; const hasCaptureOffset = "captureOffsetMinutesOverride" in data; if (!hasCaptureTs && !hasCaptureOffset) { return { status: 400, body: { error: "invalid_body" } }; } const db = (input.db ?? getDb()) as DbLike; const captureTs = hasCaptureTs ? data.captureTsUtcOverride ? new Date(data.captureTsUtcOverride) : null : null; const captureOffset = hasCaptureOffset ? data.captureOffsetMinutesOverride ?? null : null; const rows = await db< { asset_id: string; capture_ts_utc_override: string | null; capture_offset_minutes_override: number | null; created_at: string; }[] >` insert into asset_overrides ( asset_id, capture_ts_utc_override, capture_offset_minutes_override ) values ( ${paramsParsed.data.id}, ${captureTs}, ${captureOffset} ) on conflict (asset_id) do update set capture_ts_utc_override = case when ${hasCaptureTs} then excluded.capture_ts_utc_override else asset_overrides.capture_ts_utc_override end, capture_offset_minutes_override = case when ${hasCaptureOffset} then excluded.capture_offset_minutes_override else asset_overrides.capture_offset_minutes_override end returning asset_id, capture_ts_utc_override, capture_offset_minutes_override, created_at `; const created = rows[0]; if (!created) { return { status: 500, body: { error: "insert_failed" } }; } const payload = JSON.stringify({ capture_ts_utc_override: created.capture_ts_utc_override, capture_offset_minutes_override: created.capture_offset_minutes_override, }); await db` insert into audit_log (actor, action, entity_type, entity_id, payload) values ('admin', 'override_capture_ts', 'asset', ${created.asset_id}, ${payload}::jsonb) `; return { status: 200, body: created }; }