import { getPaths } from "yt-dlp-transcript-common/lib/paths"; import { readChannelConfig } from "yt-dlp-transcript-common/controller/channels"; import { applyChannelFormPatch, channelConfigToFormData, validateChannelFormPatch, } from "../../../channels/components/channelConfigToForm"; import { setChannelExclusionsAction, updateChannelAction, } from "../../../channels/actions"; import { actionResponse, OpsInputError, ops, optBool, readSiteMemberships, reqSlug, } from "../_lib"; export const dynamic = "force-dynamic"; // POST { // slug: string, // patch?: { : string|number|boolean|null }, // sites?: [{ siteId, groupId? | newGroupName? }], // excludeFromBuild?: boolean, // excludeFromCleanup?: boolean, // } // — at least one of patch / sites / excludeFromBuild / excludeFromCleanup. // // The patch keys are the FORM's field names, not ChannelConfig's, because the // form is what validates them: `downloadFilterInclude` / `downloadFilterExclude` // rather than a `downloadFilter` object, `ytdlpExtraArgs` as a string or an // array of lines. See channelConfigToForm.ts for why the patch is laid over the // channel's current form representation instead of being written directly. // // `""` (or null) clears a field, exactly as clearing the input does. // // `sites` is the Configure form's Sites section: the WHOLE membership set, so // a site left out is left, and `[]` takes the channel off every site. It rides // the same save as the patch, as it does in the form. // // The two exclusions are the /channels rack's build and cleanup toggles, set // to a value rather than flipped, since a caller cannot see the switch. export async function POST(request: Request) { return ops( request, ["slug", "patch", "sites", "excludeFromBuild", "excludeFromCleanup"], async (body) => { const slug = reqSlug(body, "slug"); const patch = body.patch; if ( patch !== undefined && (typeof patch !== "object" || patch === null || Array.isArray(patch)) ) { throw new OpsInputError('"patch" must be a JSON object'); } const sites = readSiteMemberships(body); const excludeFromBuild = optBool(body, "excludeFromBuild"); const excludeFromCleanup = optBool(body, "excludeFromCleanup"); if ( patch === undefined && sites === undefined && excludeFromBuild === undefined && excludeFromCleanup === undefined ) { throw new OpsInputError( 'nothing to change — give "patch", "sites", "excludeFromBuild" or "excludeFromCleanup"', ); } // KEYS FIRST, CHANNEL SECOND. A misspelled field is a fact about the // request; reporting "channel not found" for it would hide the real error. if (patch !== undefined) { try { validateChannelFormPatch(patch as Record); } catch (e) { throw new OpsInputError((e as Error).message); } } const paths = getPaths(); const existing = await readChannelConfig(paths, slug); if (!existing) throw new OpsInputError(`Channel "${slug}" not found`); if (patch !== undefined || sites !== undefined) { const fd = channelConfigToFormData(existing); if (patch !== undefined) { applyChannelFormPatch(fd, patch as Record); } if (sites !== undefined) { fd.set("siteMembershipsJson", JSON.stringify(sites)); } const saved = await updateChannelAction(slug, undefined, fd); if (saved?.error) return actionResponse(saved); } return actionResponse( await setChannelExclusionsAction(slug, { excludeFromBuild, excludeFromCleanup, }), ); }, ); }