import { NextResponse } from "next/server"; import { LANES, PIPELINE_LANES, type AutoQueueKind, } from "yt-dlp-transcript-common/lib/autoQueueTypes"; import { getSettings } from "yt-dlp-transcript-common/lib/settings"; import { drainAutoQueueAction, pauseLaneAction, resumeLaneAction, saveAutoQueueAction, startAutoQueueAction, stopAutoQueueAction, } from "../../../operations/actions"; import { savePublishSettingsAction } from "../../../operations/settingsActions"; import { drainPublishLaneAction, startPublishLaneAction, stopPublishLaneAction, } from "../../../sites/lib/publishActions"; import { OpsInputError, okResponse, ops, oneOf, optBool } from "../_lib"; export const dynamic = "force-dynamic"; // POST { lane: transcription|download|digest|backfill|publish, // held?: boolean, enabled?: boolean, action?: "start"|"stop"|"drain" } // // One lane, up to three independent changes, applied in the order the operator // would: enable the policy, set the gate, then bring the runner up or down. // Each is the existing action and nothing more. // // `held` IS NOT `enabled`. A hold is a dispatch gate (the runner idle-waits and // keeps its place); disabling is the policy's master switch. The pair is what // /operations draws as two separate controls and this route keeps them separate. // // `action` mirrors /api/auto-queue/control's body — same three verbs, same // meanings — so a caller that already drives that route needs nothing new. // // THE PUBLISH LANE (release 19, A4) is a pipeline lane, not a LANES entry: its // switch is `settings.publish.enabled` (the /operations/publish form's, saved // through that form's own action with every other field as stored), its gate // `publish.held`, and Start, Drain and Stop are that page's three buttons. // A Start the lane refuses (switched off, or quiet hours) is a 400 with the // page's sentence. const ALL_LANES = [...LANES, ...PIPELINE_LANES] as const; // The /operations/publish form, filled from the stored block with only the // switch changed — so the form's own validation runs on what is stored. function publishForm(enabled: boolean): FormData { const p = getSettings().publish; const fd = new FormData(); if (enabled) fd.set("publishEnabled", "on"); fd.set("publishCheckEveryMinutes", String(p.checkEveryMinutes)); fd.set("publishRefreshEveryMinutes", String(p.refreshEveryMinutes)); fd.set("publishQuietStart", p.quietHours ? String(p.quietHours.start) : ""); fd.set("publishQuietEnd", p.quietHours ? String(p.quietHours.end) : ""); fd.set("publishRunner", p.runner); fd.set("publishPreviewBranch", p.previewBranch); fd.set("publishHub", p.hub); fd.set("publishHomepage", p.homepage); return fd; } export async function POST(request: Request) { return ops(request, ["lane", "held", "enabled", "action"], async (body) => { const lane = oneOf(body, "lane", ALL_LANES); const enabled = optBool(body, "enabled"); const held = optBool(body, "held"); const action = body.action; if ( action !== undefined && action !== "start" && action !== "stop" && action !== "drain" ) { throw new OpsInputError('"action" must be "start", "stop" or "drain"'); } if (enabled === undefined && held === undefined && action === undefined) { throw new OpsInputError( 'nothing to do — send at least one of "enabled", "held" or "action"', ); } if (enabled !== undefined) { if (lane === "publish") { const saved = await savePublishSettingsAction(undefined, publishForm(enabled)); if (!saved.ok) return okResponse(saved); } else { // EVERY OTHER POLICY FIELD IS CARRIED FROM THE STORED POLICY, including // `root`: saveAutoQueueAction refuses a tree edit while the priority // model compiles the roots, and handing it back the stored tree is what // makes this a pure enable/disable rather than a tree write. const policy = getSettings().autoQueue[lane]; const saved = await saveAutoQueueAction(lane, { enabled, maxWorkers: policy.maxWorkers, replaceAutoSubs: policy.replaceAutoSubs === true, order: policy.order ?? "listed", root: policy.root, }); if (!saved.ok) return okResponse(saved); } } if (held !== undefined) { const gated = held ? await pauseLaneAction(lane) : await resumeLaneAction(lane); if (!gated.ok) return okResponse(gated); } if (action !== undefined) { const run = lane === "publish" ? { start: startPublishLaneAction, stop: stopPublishLaneAction, drain: drainPublishLaneAction }[action] : { start: () => startAutoQueueAction(lane as AutoQueueKind), stop: () => stopAutoQueueAction(lane as AutoQueueKind), drain: () => drainAutoQueueAction(lane as AutoQueueKind), }[action]; const done = await run(); if (!done.ok) return okResponse(done); } return NextResponse.json({ ok: true, lane }); }); }