ZXDB: Releases browser filters, schema lists, and fixes
- UI: Add /zxdb hub cards for Entries and Releases; implement Releases browser
with URL‑synced filters (q, year, sort, DL language/machine, file/scheme/source/case, demo)
and a paginated table (Entry ID, Title, Release #, Year).
- API: Add GET /api/zxdb/releases/search (Zod‑validated, Node runtime) supporting
title, year, sort, and downloads‑based filters; return paged JSON.
- Repo: Rewrite searchReleases to Drizzle QB; correct ORDER BY on releases.release_year;
implement EXISTS on downloads using explicit "from downloads as d"; return JSON‑safe rows.
- Schema: Align Drizzle models with ZXDB for releases/downloads; add lookups
availabletypes, currencies, roletypes, and roles relation.
- API (lookups): Add GET /api/zxdb/{availabletypes,currencies,roletypes} for dropdowns.
- Stability: JSON‑clone SSR payloads before passing to Client Components to avoid
RowDataPacket serialization errors.
Signed-off-by: Junie@lucy.xalior.com
This commit is contained in:
48
src/app/api/zxdb/releases/search/route.ts
Normal file
48
src/app/api/zxdb/releases/search/route.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { searchReleases } from "@/server/repo/zxdb";
|
||||
|
||||
const querySchema = z.object({
|
||||
q: z.string().optional(),
|
||||
page: z.coerce.number().int().positive().optional(),
|
||||
pageSize: z.coerce.number().int().positive().max(100).optional(),
|
||||
year: z.coerce.number().int().optional(),
|
||||
sort: z.enum(["year_desc", "year_asc", "title", "entry_id_desc"]).optional(),
|
||||
dLanguageId: z.string().trim().length(2).optional(),
|
||||
dMachinetypeId: z.coerce.number().int().positive().optional(),
|
||||
filetypeId: z.coerce.number().int().positive().optional(),
|
||||
schemetypeId: z.string().trim().length(2).optional(),
|
||||
sourcetypeId: z.string().trim().length(1).optional(),
|
||||
casetypeId: z.string().trim().length(1).optional(),
|
||||
isDemo: z.coerce.boolean().optional(),
|
||||
});
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const parsed = querySchema.safeParse({
|
||||
q: searchParams.get("q") ?? undefined,
|
||||
page: searchParams.get("page") ?? undefined,
|
||||
pageSize: searchParams.get("pageSize") ?? undefined,
|
||||
year: searchParams.get("year") ?? undefined,
|
||||
sort: searchParams.get("sort") ?? undefined,
|
||||
dLanguageId: searchParams.get("dLanguageId") ?? undefined,
|
||||
dMachinetypeId: searchParams.get("dMachinetypeId") ?? undefined,
|
||||
filetypeId: searchParams.get("filetypeId") ?? undefined,
|
||||
schemetypeId: searchParams.get("schemetypeId") ?? undefined,
|
||||
sourcetypeId: searchParams.get("sourcetypeId") ?? undefined,
|
||||
casetypeId: searchParams.get("casetypeId") ?? undefined,
|
||||
isDemo: searchParams.get("isDemo") ?? undefined,
|
||||
});
|
||||
if (!parsed.success) {
|
||||
return new Response(JSON.stringify({ error: parsed.error.flatten() }), {
|
||||
status: 400,
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
}
|
||||
const data = await searchReleases(parsed.data);
|
||||
return new Response(JSON.stringify(data), {
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
export const runtime = "nodejs";
|
||||
Reference in New Issue
Block a user