import { NextResponse } from "next/server"; import { isValidChannelSlug } from "yt-dlp-transcript-common/controller/channels"; import { opsAuth, opsFail } from "../_lib"; import { listJobs } from "../_jobs"; export const dynamic = "force-dynamic"; // GET /api/ops/jobs[?active=1 | ?failed=1][&kind=][&slug=][&limit=N] // // The /jobs list, for a caller with no browser. Newest first, `limit` rows // (default 50, at most 500), each row `GET /api/ops/job/`'s answer without // the tail: // // active=1 the live head — every queued and running job, in queue order // (running first), each with where it waits. From the registry. // failed=1 jobs that ended `failed`. // kind, slug narrow either list to one job kind / one channel. // // Without `active` the list is the `.jobs/` directory merged with the registry, // as the page's tail is; a filtered list looks through the newest 2000 jobs // (`scanned` says how many it read), so "no failed syncs" is not an artefact of // the last fifty being refresh-reports. `active` and `failed` together are a // 400: a failed job is not active. const KEYS = ["active", "failed", "kind", "slug", "limit"]; const MAX_LIMIT = 500; export async function GET(request: Request) { const denied = opsAuth(request); if (denied) return denied; const q = new URL(request.url).searchParams; const unknown = [...q.keys()].filter((k) => !KEYS.includes(k)); if (unknown.length) { return opsFail(`unknown query key(s): ${unknown.join(", ")} — accepted: ${KEYS.join(", ")}`); } const flag = (k: string) => { const v = q.get(k); return v !== null && v !== "0" && v !== "false"; }; const active = flag("active"); const failed = flag("failed"); if (active && failed) { return opsFail('"active" and "failed" together match nothing — a failed job is not active'); } const kind = q.get("kind") ?? undefined; if (kind !== undefined && !/^[a-z0-9-]+$/.test(kind)) { return opsFail(`"${kind}" is not a job kind`); } const slug = q.get("slug") ?? undefined; if (slug !== undefined && !isValidChannelSlug(slug)) { return opsFail(`"${slug}" is not a valid channel slug`); } let limit = 50; const rawLimit = q.get("limit"); if (rawLimit !== null) { limit = Number(rawLimit); if (!Number.isInteger(limit) || limit <= 0) { return opsFail('"limit" must be a whole number above zero'); } limit = Math.min(limit, MAX_LIMIT); } const page = await listJobs({ active, failed, kind, slug, limit }); return NextResponse.json({ ok: true, ...page }); }