Initial commit
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { assertLocalRequest } from "@/lib/api/localOnly";
|
||||
import { listImages, countImages } from "@/lib/db/queries";
|
||||
import { resolveSort } from "@/lib/sortServer";
|
||||
import { parseFilterCriteria, statusToFlags } from "@/lib/filters";
|
||||
import { getAppSetting } from "@/lib/db/appSettings";
|
||||
import type { LibraryView } from "@/components/grid/ViewToggle";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* Paginated covers feed for client-side infinite-scroll appends.
|
||||
* Mirrors the SSR filter shape in app/page.tsx — every filter the
|
||||
* grid supports (letter, search, sort, marks, multi-select tabs)
|
||||
* resolves through the same listImages/countImages path.
|
||||
*/
|
||||
export async function GET(req: NextRequest) {
|
||||
const blocked = assertLocalRequest(req);
|
||||
if (blocked) return blocked;
|
||||
|
||||
const sp = req.nextUrl.searchParams;
|
||||
// Ape Object.fromEntries for plain access matching page params.
|
||||
const params: Record<string, string | string[] | undefined> = {};
|
||||
for (const [k, v] of sp.entries()) {
|
||||
const cur = params[k];
|
||||
if (cur == null) params[k] = v;
|
||||
else if (Array.isArray(cur)) cur.push(v);
|
||||
else params[k] = [cur, v];
|
||||
}
|
||||
|
||||
const criteria = parseFilterCriteria(params);
|
||||
const sort = await resolveSort(typeof params.sort === "string" ? params.sort : undefined);
|
||||
const rawLetter = (typeof params.letter === "string" ? params.letter : "").toUpperCase();
|
||||
const letter = rawLetter === "#" ? "#" : (/^[A-Z]$/.test(rawLetter) ? rawLetter : null);
|
||||
const search = (typeof params.q === "string" ? params.q.trim() : "") || undefined;
|
||||
// view is purely a presentational hint; included for symmetry but
|
||||
// doesn't affect query.
|
||||
void (params.view === "portrait" ? "portrait" : "landscape" as LibraryView);
|
||||
|
||||
const rawPage = typeof params.page === "string" ? Number(params.page) : NaN;
|
||||
const page = Number.isFinite(rawPage) && rawPage >= 1 ? Math.floor(rawPage) : 1;
|
||||
|
||||
const filterOpts = {
|
||||
sort,
|
||||
letter: letter ?? undefined,
|
||||
search,
|
||||
...statusToFlags(criteria.status),
|
||||
marks: criteria.marks,
|
||||
actressIds: criteria.ids.actresses,
|
||||
actressMode: criteria.mode.actresses,
|
||||
studioIds: criteria.ids.studios,
|
||||
seriesIds: criteria.ids.series,
|
||||
genreIds: criteria.ids.genres,
|
||||
genreMode: criteria.mode.genres,
|
||||
collectionIds: criteria.ids.collections,
|
||||
collectionMode: criteria.mode.collections,
|
||||
tagIds: criteria.ids.tags,
|
||||
tagMode: criteria.mode.tags,
|
||||
categoryIds: criteria.ids.categories,
|
||||
categoryMode: criteria.mode.categories,
|
||||
};
|
||||
|
||||
const PAGE_SIZE = Math.max(25, Math.min(500, getAppSetting("coverPageSize") ?? 100));
|
||||
const totalCount = countImages(filterOpts);
|
||||
const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));
|
||||
const effectivePage = Math.min(page, totalPages);
|
||||
const offset = (effectivePage - 1) * PAGE_SIZE;
|
||||
const items = listImages({ ...filterOpts, limit: PAGE_SIZE, offset });
|
||||
|
||||
return NextResponse.json(
|
||||
{ items, page: effectivePage, totalPages, totalCount, hasMore: effectivePage < totalPages },
|
||||
{ headers: { "Cache-Control": "no-store" } },
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user