import { NextResponse } from "next/server"; import { keepVideosAction } from "../../../channels/[slug]/videos/[id]/videoActions"; import { OpsInputError, ops, optBool, optString, opsFail, reqSlug, reqString } from "../_lib"; export const dynamic = "force-dynamic"; // POST { slug, match, fields?: ("title"|"description")[], note?, dryRun? } // -> { ok: true, pattern, fields, considered, matched: [{ id, title, // downloaded, alreadyKept, marked }], marked, alreadyKept, // notDownloaded, unscanned, noMetadata, dryRun } // // AN ADAPTER: one call to keepVideosAction, the bulk form of the video page's // "Do not clean" toggle. It writes the same per-video do-not-clean.json marker // every cleaner already honours; it adds no protection of its own. // // `match` IS A DOWNLOAD-FILTER PATTERN, matched the way the filter matches it — // a case-insensitive regex SOURCE over title + "\n" + description, compiled by // the same function. A pattern that keeps the right videos here would download // the right ones as a channel's `downloadFilterInclude`. `fields` narrows the // subject to one half. // // A MATCH WITH NO data// IS REPORTED, NOT CREATED. The marker lives in the // video's dir, and a dir is what every enumerator reads as "fetched". Those ids // come back in `notDownloaded`: send them to `download-missing`, then run this // again. A fresh channel wants `metadata-scan` FIRST — an id with no dir and no // scan entry has no text to match, and is only counted (`unscanned`). // // `dryRun: true` answers the same question and writes nothing. export async function POST(request: Request) { return ops(request, ["slug", "match", "fields", "note", "dryRun"], async (body) => { const slug = reqSlug(body, "slug"); const pattern = reqString(body, "match"); const raw = body.fields; let fields: ("title" | "description")[] | undefined; if (raw !== undefined) { if ( !Array.isArray(raw) || raw.length === 0 || raw.some((f) => f !== "title" && f !== "description") ) { throw new OpsInputError( '"fields" must be a non-empty array of "title" and/or "description"', ); } fields = raw as ("title" | "description")[]; } const result = await keepVideosAction({ slug, pattern, fields, note: optString(body, "note"), dryRun: optBool(body, "dryRun"), }); if (!result.ok) return opsFail(result.error); return NextResponse.json({ ok: true, ...result.result }); }); }