56 lines
1.7 KiB
TypeScript
56 lines
1.7 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { assertLocalRequest } from "@/lib/api/localOnly";
|
|
import { rawDb } from "@/lib/db/client";
|
|
|
|
export const runtime = "nodejs";
|
|
export const dynamic = "force-dynamic";
|
|
|
|
interface CandidateRow {
|
|
id: number;
|
|
code: string;
|
|
title: string | null;
|
|
thumb_path: string;
|
|
}
|
|
|
|
/**
|
|
* Codes with a playable video but no discoverable subtitle. The user
|
|
* picks from this list when running batch WhisperJAV generation.
|
|
*
|
|
* has_subtitle is the cheap signal — populated by the video index
|
|
* scan (sidecar files / generated subs / library roots).
|
|
*/
|
|
export async function GET(req: NextRequest) {
|
|
const blocked = assertLocalRequest(req);
|
|
if (blocked) return blocked;
|
|
|
|
const limit = Math.min(500, Math.max(1, Number(req.nextUrl.searchParams.get("limit") ?? "200")));
|
|
const offset = Math.max(0, Number(req.nextUrl.searchParams.get("offset") ?? "0"));
|
|
const includeAlreadyHasSubs = req.nextUrl.searchParams.get("all") === "1";
|
|
|
|
const where = includeAlreadyHasSubs
|
|
? `i.has_video = 1 AND i.code IS NOT NULL AND i.deleted_at IS NULL AND i.parent_image_id IS NULL`
|
|
: `i.has_video = 1 AND i.has_subtitle = 0 AND i.code IS NOT NULL AND i.deleted_at IS NULL AND i.parent_image_id IS NULL`;
|
|
|
|
const rows = rawDb.prepare(`
|
|
SELECT i.id, i.code, i.title, i.thumb_path
|
|
FROM images i
|
|
WHERE ${where}
|
|
ORDER BY UPPER(i.code) ASC
|
|
LIMIT ? OFFSET ?
|
|
`).all(limit, offset) as CandidateRow[];
|
|
|
|
const totalRow = rawDb.prepare(`
|
|
SELECT COUNT(*) AS n FROM images i WHERE ${where}
|
|
`).get() as { n: number };
|
|
|
|
return NextResponse.json({
|
|
candidates: rows.map((r) => ({
|
|
id: r.id,
|
|
code: r.code,
|
|
title: r.title,
|
|
thumbPath: r.thumb_path,
|
|
})),
|
|
total: totalRow.n,
|
|
});
|
|
}
|