diff --git a/src/app/api/zxdb/releases/search/route.ts b/src/app/api/zxdb/releases/search/route.ts index 3e31cd1..3345876 100644 --- a/src/app/api/zxdb/releases/search/route.ts +++ b/src/app/api/zxdb/releases/search/route.ts @@ -9,7 +9,7 @@ const querySchema = z.object({ 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(), + dMachinetypeId: z.string().optional(), filetypeId: z.coerce.number().int().positive().optional(), schemetypeId: z.string().trim().length(2).optional(), sourcetypeId: z.string().trim().length(1).optional(), @@ -17,6 +17,15 @@ const querySchema = z.object({ isDemo: z.coerce.boolean().optional(), }); +function parseIdList(value: string | undefined) { + if (!value) return undefined; + const ids = value + .split(",") + .map((id) => Number(id.trim())) + .filter((id) => Number.isFinite(id) && id > 0); + return ids.length ? ids : undefined; +} + export async function GET(req: NextRequest) { const { searchParams } = new URL(req.url); const parsed = querySchema.safeParse({ @@ -39,7 +48,8 @@ export async function GET(req: NextRequest) { headers: { "content-type": "application/json" }, }); } - const data = await searchReleases(parsed.data); + const dMachinetypeId = parseIdList(parsed.data.dMachinetypeId); + const data = await searchReleases({ ...parsed.data, dMachinetypeId }); return new Response(JSON.stringify(data), { headers: { "content-type": "application/json" }, }); diff --git a/src/app/api/zxdb/search/route.ts b/src/app/api/zxdb/search/route.ts index 8d982e2..9707531 100644 --- a/src/app/api/zxdb/search/route.ts +++ b/src/app/api/zxdb/search/route.ts @@ -12,12 +12,21 @@ const querySchema = z.object({ .trim() .length(2, "languageId must be a 2-char code") .optional(), - machinetypeId: z.coerce.number().int().positive().optional(), + machinetypeId: z.string().optional(), sort: z.enum(["title", "id_desc"]).optional(), scope: z.enum(["title", "title_aliases", "title_aliases_origins"]).optional(), facets: z.coerce.boolean().optional(), }); +function parseIdList(value: string | undefined) { + if (!value) return undefined; + const ids = value + .split(",") + .map((id) => Number(id.trim())) + .filter((id) => Number.isFinite(id) && id > 0); + return ids.length ? ids : undefined; +} + export async function GET(req: NextRequest) { const { searchParams } = new URL(req.url); const parsed = querySchema.safeParse({ @@ -37,9 +46,11 @@ export async function GET(req: NextRequest) { { status: 400, headers: { "content-type": "application/json" } } ); } - const data = await searchEntries(parsed.data); + const machinetypeId = parseIdList(parsed.data.machinetypeId); + const searchParamsParsed = { ...parsed.data, machinetypeId }; + const data = await searchEntries(searchParamsParsed); const body = parsed.data.facets - ? { ...data, facets: await getEntryFacets(parsed.data) } + ? { ...data, facets: await getEntryFacets(searchParamsParsed) } : data; return new Response(JSON.stringify(body), { headers: { "content-type": "application/json" }, diff --git a/src/app/zxdb/entries/EntriesExplorer.tsx b/src/app/zxdb/entries/EntriesExplorer.tsx index deed8d9..8a83582 100644 --- a/src/app/zxdb/entries/EntriesExplorer.tsx +++ b/src/app/zxdb/entries/EntriesExplorer.tsx @@ -52,11 +52,21 @@ export default function EntriesExplorer({ page: number; genreId: string | number | ""; languageId: string | ""; - machinetypeId: string | number | ""; + machinetypeId: string; sort: "title" | "id_desc"; scope?: SearchScope; }; }) { + const preferredMachineIds = [27, 26, 8, 9]; + const parseMachineIds = (value?: string) => { + if (!value) return preferredMachineIds.slice(); + const ids = value + .split(",") + .map((id) => Number(id.trim())) + .filter((id) => Number.isFinite(id) && id > 0); + return ids.length ? ids : preferredMachineIds.slice(); + }; + const router = useRouter(); const pathname = usePathname(); @@ -72,17 +82,20 @@ export default function EntriesExplorer({ initialUrlState?.genreId === "" ? "" : initialUrlState?.genreId ? Number(initialUrlState.genreId) : "" ); const [languageId, setLanguageId] = useState(initialUrlState?.languageId ?? ""); - const [machinetypeId, setMachinetypeId] = useState( - initialUrlState?.machinetypeId === "" ? "" : initialUrlState?.machinetypeId ? Number(initialUrlState.machinetypeId) : "" - ); + const [machinetypeIds, setMachinetypeIds] = useState(parseMachineIds(initialUrlState?.machinetypeId)); const [sort, setSort] = useState<"title" | "id_desc">(initialUrlState?.sort ?? "id_desc"); const [scope, setScope] = useState(initialUrlState?.scope ?? "title"); const [facets, setFacets] = useState(initialFacets ?? null); - const preferredMachineIds = [27, 26, 8, 9]; const preferredMachineNames = useMemo(() => { if (!machines.length) return preferredMachineIds.map((id) => `#${id}`); return preferredMachineIds.map((id) => machines.find((m) => m.id === id)?.name ?? `#${id}`); }, [machines]); + const orderedMachines = useMemo(() => { + const seen = new Set(preferredMachineIds); + const preferred = preferredMachineIds.map((id) => machines.find((m) => m.id === id)).filter(Boolean) as { id: number; name: string }[]; + const rest = machines.filter((m) => !seen.has(m.id)); + return [...preferred, ...rest]; + }, [machines]); const pageSize = 20; const totalPages = useMemo(() => (data ? Math.max(1, Math.ceil(data.total / data.pageSize)) : 1), [data]); @@ -97,14 +110,14 @@ export default function EntriesExplorer({ const name = languages.find((l) => l.id === languageId)?.name ?? languageId; chips.push(`lang: ${name}`); } - if (machinetypeId !== "") { - const name = machines.find((m) => m.id === Number(machinetypeId))?.name ?? `#${machinetypeId}`; - chips.push(`machine: ${name}`); + if (machinetypeIds.length > 0) { + const names = machinetypeIds.map((id) => machines.find((m) => m.id === id)?.name ?? `#${id}`); + chips.push(`machine: ${names.join(", ")}`); } if (scope === "title_aliases") chips.push("scope: titles + aliases"); if (scope === "title_aliases_origins") chips.push("scope: titles + aliases + origins"); return chips; - }, [appliedQ, genreId, languageId, machinetypeId, scope, genres, languages, machines]); + }, [appliedQ, genreId, languageId, machinetypeIds, scope, genres, languages, machines]); function updateUrl(nextPage = page) { const params = new URLSearchParams(); @@ -112,7 +125,7 @@ export default function EntriesExplorer({ params.set("page", String(nextPage)); if (genreId !== "") params.set("genreId", String(genreId)); if (languageId !== "") params.set("languageId", String(languageId)); - if (machinetypeId !== "") params.set("machinetypeId", String(machinetypeId)); + if (machinetypeIds.length > 0) params.set("machinetypeId", machinetypeIds.join(",")); if (sort) params.set("sort", sort); if (scope !== "title") params.set("scope", scope); const qs = params.toString(); @@ -128,7 +141,7 @@ export default function EntriesExplorer({ params.set("pageSize", String(pageSize)); if (genreId !== "") params.set("genreId", String(genreId)); if (languageId !== "") params.set("languageId", String(languageId)); - if (machinetypeId !== "") params.set("machinetypeId", String(machinetypeId)); + if (machinetypeIds.length > 0) params.set("machinetypeId", machinetypeIds.join(",")); if (sort) params.set("sort", sort); if (scope !== "title") params.set("scope", scope); if (withFacets) params.set("facets", "true"); @@ -165,8 +178,7 @@ export default function EntriesExplorer({ (initialUrlState?.q ?? "") === appliedQ && (initialUrlState?.genreId === "" ? "" : Number(initialUrlState?.genreId ?? "")) === (genreId === "" ? "" : Number(genreId)) && (initialUrlState?.languageId ?? "") === (languageId ?? "") && - (initialUrlState?.machinetypeId === "" ? "" : Number(initialUrlState?.machinetypeId ?? "")) === - (machinetypeId === "" ? "" : Number(machinetypeId)) && + parseMachineIds(initialUrlState?.machinetypeId).join(",") === machinetypeIds.join(",") && sort === (initialUrlState?.sort ?? "id_desc") && (initialUrlState?.scope ?? "title") === scope ) { @@ -176,7 +188,7 @@ export default function EntriesExplorer({ updateUrl(page); fetchData(appliedQ, page, true); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [page, genreId, languageId, machinetypeId, sort, scope, appliedQ]); + }, [page, genreId, languageId, machinetypeIds, sort, scope, appliedQ]); // Load filter lists on mount only if not provided by server useEffect(() => { @@ -207,7 +219,7 @@ export default function EntriesExplorer({ setAppliedQ(""); setGenreId(""); setLanguageId(""); - setMachinetypeId(""); + setMachinetypeIds(preferredMachineIds.slice()); setSort("id_desc"); setScope("title"); setPage(1); @@ -219,11 +231,11 @@ export default function EntriesExplorer({ params.set("page", String(Math.max(1, (data?.page ?? 1) - 1))); if (genreId !== "") params.set("genreId", String(genreId)); if (languageId !== "") params.set("languageId", String(languageId)); - if (machinetypeId !== "") params.set("machinetypeId", String(machinetypeId)); + if (machinetypeIds.length > 0) params.set("machinetypeId", machinetypeIds.join(",")); if (sort) params.set("sort", sort); if (scope !== "title") params.set("scope", scope); return `/zxdb/entries?${params.toString()}`; - }, [appliedQ, data?.page, genreId, languageId, machinetypeId, sort, scope]); + }, [appliedQ, data?.page, genreId, languageId, machinetypeIds, sort, scope]); const nextHref = useMemo(() => { const params = new URLSearchParams(); @@ -231,11 +243,11 @@ export default function EntriesExplorer({ params.set("page", String(Math.max(1, (data?.page ?? 1) + 1))); if (genreId !== "") params.set("genreId", String(genreId)); if (languageId !== "") params.set("languageId", String(languageId)); - if (machinetypeId !== "") params.set("machinetypeId", String(machinetypeId)); + if (machinetypeIds.length > 0) params.set("machinetypeId", machinetypeIds.join(",")); if (sort) params.set("sort", sort); if (scope !== "title") params.set("scope", scope); return `/zxdb/entries?${params.toString()}`; - }, [appliedQ, data?.page, genreId, languageId, machinetypeId, sort, scope]); + }, [appliedQ, data?.page, genreId, languageId, machinetypeIds, sort, scope]); return (
@@ -303,15 +315,34 @@ export default function EntriesExplorer({
- - {machinetypeId === "" && ( -
Preferred: {preferredMachineNames.join(", ")}
- )} +
+ {orderedMachines.map((m) => { + const active = machinetypeIds.includes(m.id); + return ( + + ); + })} +
+
Preferred: {preferredMachineNames.join(", ")}
diff --git a/src/app/zxdb/entries/page.tsx b/src/app/zxdb/entries/page.tsx index c722754..effccd5 100644 --- a/src/app/zxdb/entries/page.tsx +++ b/src/app/zxdb/entries/page.tsx @@ -7,12 +7,24 @@ export const metadata = { export const dynamic = "force-dynamic"; +function parseIdList(value: string | string[] | undefined) { + if (!value) return undefined; + const raw = Array.isArray(value) ? value.join(",") : value; + const ids = raw + .split(",") + .map((id) => Number(id.trim())) + .filter((id) => Number.isFinite(id) && id > 0); + return ids.length ? ids : undefined; +} + export default async function Page({ searchParams }: { searchParams: Promise<{ [key: string]: string | string[] | undefined }> }) { const sp = await searchParams; const page = Math.max(1, Number(Array.isArray(sp.page) ? sp.page[0] : sp.page) || 1); const genreId = (Array.isArray(sp.genreId) ? sp.genreId[0] : sp.genreId) ?? ""; const languageId = (Array.isArray(sp.languageId) ? sp.languageId[0] : sp.languageId) ?? ""; - const machinetypeId = (Array.isArray(sp.machinetypeId) ? sp.machinetypeId[0] : sp.machinetypeId) ?? ""; + const preferredMachineIds = [27, 26, 8, 9]; + const machinetypeIds = parseIdList(sp.machinetypeId) ?? preferredMachineIds; + const machinetypeId = machinetypeIds.join(","); const sort = ((Array.isArray(sp.sort) ? sp.sort[0] : sp.sort) ?? "id_desc") as "title" | "id_desc"; const q = (Array.isArray(sp.q) ? sp.q[0] : sp.q) ?? ""; const scope = ((Array.isArray(sp.scope) ? sp.scope[0] : sp.scope) ?? "title") as @@ -29,7 +41,7 @@ export default async function Page({ searchParams }: { searchParams: Promise<{ [ scope, genreId: genreId ? Number(genreId) : undefined, languageId: languageId || undefined, - machinetypeId: machinetypeId ? Number(machinetypeId) : undefined, + machinetypeId: machinetypeIds, }), listGenres(), listLanguages(), @@ -40,7 +52,7 @@ export default async function Page({ searchParams }: { searchParams: Promise<{ [ scope, genreId: genreId ? Number(genreId) : undefined, languageId: languageId || undefined, - machinetypeId: machinetypeId ? Number(machinetypeId) : undefined, + machinetypeId: machinetypeIds, }), ]); diff --git a/src/app/zxdb/releases/ReleasesExplorer.tsx b/src/app/zxdb/releases/ReleasesExplorer.tsx index 0340a34..2fdf71f 100644 --- a/src/app/zxdb/releases/ReleasesExplorer.tsx +++ b/src/app/zxdb/releases/ReleasesExplorer.tsx @@ -51,6 +51,16 @@ export default function ReleasesExplorer({ casetypes: { id: string; name: string }[]; }; }) { + const preferredMachineIds = [27, 26, 8, 9]; + const parseMachineIds = (value?: string) => { + if (!value) return preferredMachineIds.slice(); + const ids = value + .split(",") + .map((id) => Number(id.trim())) + .filter((id) => Number.isFinite(id) && id > 0); + return ids.length ? ids : preferredMachineIds.slice(); + }; + const router = useRouter(); const pathname = usePathname(); @@ -64,7 +74,7 @@ export default function ReleasesExplorer({ // Download-based filters and their option lists const [dLanguageId, setDLanguageId] = useState(initialUrlState?.dLanguageId ?? ""); - const [dMachinetypeId, setDMachinetypeId] = useState(initialUrlState?.dMachinetypeId ?? ""); + const [dMachinetypeIds, setDMachinetypeIds] = useState(parseMachineIds(initialUrlState?.dMachinetypeId)); const [filetypeId, setFiletypeId] = useState(initialUrlState?.filetypeId ?? ""); const [schemetypeId, setSchemetypeId] = useState(initialUrlState?.schemetypeId ?? ""); const [sourcetypeId, setSourcetypeId] = useState(initialUrlState?.sourcetypeId ?? ""); @@ -78,11 +88,16 @@ export default function ReleasesExplorer({ const [sources, setSources] = useState<{ id: string; name: string }[]>(initialLists?.sourcetypes ?? []); const [cases, setCases] = useState<{ id: string; name: string }[]>(initialLists?.casetypes ?? []); const initialLoad = useRef(true); - const preferredMachineIds = [27, 26, 8, 9]; const preferredMachineNames = useMemo(() => { if (!machines.length) return preferredMachineIds.map((id) => `#${id}`); return preferredMachineIds.map((id) => machines.find((m) => m.id === id)?.name ?? `#${id}`); }, [machines]); + const orderedMachines = useMemo(() => { + const seen = new Set(preferredMachineIds); + const preferred = preferredMachineIds.map((id) => machines.find((m) => m.id === id)).filter(Boolean) as { id: number; name: string }[]; + const rest = machines.filter((m) => !seen.has(m.id)); + return [...preferred, ...rest]; + }, [machines]); const pageSize = 20; const totalPages = useMemo(() => (data ? Math.max(1, Math.ceil(data.total / data.pageSize)) : 1), [data]); @@ -94,7 +109,7 @@ export default function ReleasesExplorer({ if (year) params.set("year", year); if (sort) params.set("sort", sort); if (dLanguageId) params.set("dLanguageId", dLanguageId); - if (dMachinetypeId) params.set("dMachinetypeId", dMachinetypeId); + if (dMachinetypeIds.length > 0) params.set("dMachinetypeId", dMachinetypeIds.join(",")); if (filetypeId) params.set("filetypeId", filetypeId); if (schemetypeId) params.set("schemetypeId", schemetypeId); if (sourcetypeId) params.set("sourcetypeId", sourcetypeId); @@ -114,7 +129,7 @@ export default function ReleasesExplorer({ if (year) params.set("year", String(Number(year))); if (sort) params.set("sort", sort); if (dLanguageId) params.set("dLanguageId", dLanguageId); - if (dMachinetypeId) params.set("dMachinetypeId", dMachinetypeId); + if (dMachinetypeIds.length > 0) params.set("dMachinetypeId", dMachinetypeIds.join(",")); if (filetypeId) params.set("filetypeId", filetypeId); if (schemetypeId) params.set("schemetypeId", schemetypeId); if (sourcetypeId) params.set("sourcetypeId", sourcetypeId); @@ -148,7 +163,7 @@ export default function ReleasesExplorer({ (initialUrlState?.year ?? "") === (year ?? "") && sort === (initialUrlState?.sort ?? "year_desc") && (initialUrlState?.dLanguageId ?? "") === dLanguageId && - (initialUrlState?.dMachinetypeId ?? "") === dMachinetypeId && + parseMachineIds(initialUrlState?.dMachinetypeId).join(",") === dMachinetypeIds.join(",") && (initialUrlState?.filetypeId ?? "") === filetypeId && (initialUrlState?.schemetypeId ?? "") === schemetypeId && (initialUrlState?.sourcetypeId ?? "") === sourcetypeId && @@ -168,7 +183,7 @@ export default function ReleasesExplorer({ } updateUrl(page); fetchData(appliedQ, page); - }, [page, year, sort, dLanguageId, dMachinetypeId, filetypeId, schemetypeId, sourcetypeId, casetypeId, isDemo, appliedQ]); + }, [page, year, sort, dLanguageId, dMachinetypeIds, filetypeId, schemetypeId, sourcetypeId, casetypeId, isDemo, appliedQ]); function onSubmit(e: React.FormEvent) { e.preventDefault(); @@ -209,14 +224,14 @@ export default function ReleasesExplorer({ if (year) params.set("year", year); if (sort) params.set("sort", sort); if (dLanguageId) params.set("dLanguageId", dLanguageId); - if (dMachinetypeId) params.set("dMachinetypeId", dMachinetypeId); + if (dMachinetypeIds.length > 0) params.set("dMachinetypeId", dMachinetypeIds.join(",")); if (filetypeId) params.set("filetypeId", filetypeId); if (schemetypeId) params.set("schemetypeId", schemetypeId); if (sourcetypeId) params.set("sourcetypeId", sourcetypeId); if (casetypeId) params.set("casetypeId", casetypeId); if (isDemo) params.set("isDemo", "1"); return `/zxdb/releases?${params.toString()}`; - }, [appliedQ, data?.page, year, sort, dLanguageId, dMachinetypeId, filetypeId, schemetypeId, sourcetypeId, casetypeId, isDemo]); + }, [appliedQ, data?.page, year, sort, dLanguageId, dMachinetypeIds, filetypeId, schemetypeId, sourcetypeId, casetypeId, isDemo]); const nextHref = useMemo(() => { const params = new URLSearchParams(); @@ -225,14 +240,14 @@ export default function ReleasesExplorer({ if (year) params.set("year", year); if (sort) params.set("sort", sort); if (dLanguageId) params.set("dLanguageId", dLanguageId); - if (dMachinetypeId) params.set("dMachinetypeId", dMachinetypeId); + if (dMachinetypeIds.length > 0) params.set("dMachinetypeId", dMachinetypeIds.join(",")); if (filetypeId) params.set("filetypeId", filetypeId); if (schemetypeId) params.set("schemetypeId", schemetypeId); if (sourcetypeId) params.set("sourcetypeId", sourcetypeId); if (casetypeId) params.set("casetypeId", casetypeId); if (isDemo) params.set("isDemo", "1"); return `/zxdb/releases?${params.toString()}`; - }, [appliedQ, data?.page, year, sort, dLanguageId, dMachinetypeId, filetypeId, schemetypeId, sourcetypeId, casetypeId, isDemo]); + }, [appliedQ, data?.page, year, sort, dLanguageId, dMachinetypeIds, filetypeId, schemetypeId, sourcetypeId, casetypeId, isDemo]); return (
@@ -291,15 +306,34 @@ export default function ReleasesExplorer({
- - {dMachinetypeId === "" && ( -
Preferred: {preferredMachineNames.join(", ")}
- )} +
+ {orderedMachines.map((m) => { + const active = dMachinetypeIds.includes(m.id); + return ( + + ); + })} +
+
Preferred: {preferredMachineNames.join(", ")}
diff --git a/src/app/zxdb/releases/page.tsx b/src/app/zxdb/releases/page.tsx index 6aad703..aeed10a 100644 --- a/src/app/zxdb/releases/page.tsx +++ b/src/app/zxdb/releases/page.tsx @@ -7,6 +7,16 @@ export const metadata = { export const dynamic = "force-dynamic"; +function parseIdList(value: string | string[] | undefined) { + if (!value) return undefined; + const raw = Array.isArray(value) ? value.join(",") : value; + const ids = raw + .split(",") + .map((id) => Number(id.trim())) + .filter((id) => Number.isFinite(id) && id > 0); + return ids.length ? ids : undefined; +} + export default async function Page({ searchParams }: { searchParams: Promise<{ [key: string]: string | string[] | undefined }> }) { const sp = await searchParams; const hasParams = Object.values(sp).some((value) => value !== undefined); @@ -16,8 +26,9 @@ export default async function Page({ searchParams }: { searchParams: Promise<{ [ const year = yearStr ? Number(yearStr) : undefined; const sort = ((Array.isArray(sp.sort) ? sp.sort[0] : sp.sort) ?? "year_desc") as "year_desc" | "year_asc" | "title" | "entry_id_desc"; const dLanguageId = (Array.isArray(sp.dLanguageId) ? sp.dLanguageId[0] : sp.dLanguageId) ?? ""; - const dMachinetypeIdStr = (Array.isArray(sp.dMachinetypeId) ? sp.dMachinetypeId[0] : sp.dMachinetypeId) ?? ""; - const dMachinetypeId = dMachinetypeIdStr ? Number(dMachinetypeIdStr) : undefined; + const preferredMachineIds = [27, 26, 8, 9]; + const dMachinetypeIds = parseIdList(sp.dMachinetypeId) ?? preferredMachineIds; + const dMachinetypeIdStr = dMachinetypeIds.join(","); const filetypeIdStr = (Array.isArray(sp.filetypeId) ? sp.filetypeId[0] : sp.filetypeId) ?? ""; const filetypeId = filetypeIdStr ? Number(filetypeIdStr) : undefined; const schemetypeId = (Array.isArray(sp.schemetypeId) ? sp.schemetypeId[0] : sp.schemetypeId) ?? ""; @@ -27,7 +38,7 @@ export default async function Page({ searchParams }: { searchParams: Promise<{ [ const isDemo = isDemoStr ? (isDemoStr === "true" || isDemoStr === "1") : undefined; const [initial, langs, machines, filetypes, schemes, sources, cases] = await Promise.all([ - searchReleases({ page, pageSize: 20, q, year, sort, dLanguageId: dLanguageId || undefined, dMachinetypeId, filetypeId, schemetypeId: schemetypeId || undefined, sourcetypeId: sourcetypeId || undefined, casetypeId: casetypeId || undefined, isDemo }), + searchReleases({ page, pageSize: 20, q, year, sort, dLanguageId: dLanguageId || undefined, dMachinetypeId: dMachinetypeIds, filetypeId, schemetypeId: schemetypeId || undefined, sourcetypeId: sourcetypeId || undefined, casetypeId: casetypeId || undefined, isDemo }), listLanguages(), listMachinetypes(), listFiletypes(), diff --git a/src/server/repo/zxdb.ts b/src/server/repo/zxdb.ts index 6345738..b9f73cc 100644 --- a/src/server/repo/zxdb.ts +++ b/src/server/repo/zxdb.ts @@ -63,7 +63,7 @@ export interface SearchParams { // Optional simple filters (ANDed together) genreId?: number; languageId?: string; - machinetypeId?: number; + machinetypeId?: number | number[]; // Sorting sort?: "title" | "id_desc"; // Search scope (defaults to titles only) @@ -177,7 +177,10 @@ export async function searchEntries(params: SearchParams): Promise 0 + : typeof params.machinetypeId === "number"; + const preferMachineOrder = hasMachineFilter ? null : sql`case when ${entries.machinetypeId} = 27 then 0 @@ -190,7 +193,7 @@ export async function searchEntries(params: SearchParams): Promise> = []; + const whereClauses: Array> = []; if (typeof params.genreId === "number") { whereClauses.push(eq(entries.genretypeId, params.genreId)); } @@ -199,6 +202,9 @@ export async function searchEntries(params: SearchParams): Promise 0) { + const ids = params.machinetypeId.map((id) => sql`${id}`); + whereClauses.push(sql`${entries.machinetypeId} in (${sql.join(ids, sql`, `)})`); } const whereExpr = whereClauses.length ? and(...whereClauses) : undefined; @@ -1655,7 +1661,12 @@ export async function getEntryFacets(params: SearchParams): Promise } if (params.genreId) whereParts.push(sql`e.genretype_id = ${params.genreId}`); if (params.languageId) whereParts.push(sql`e.language_id = ${params.languageId}`); - if (params.machinetypeId) whereParts.push(sql`e.machinetype_id = ${params.machinetypeId}`); + if (typeof params.machinetypeId === "number") { + whereParts.push(sql`e.machinetype_id = ${params.machinetypeId}`); + } else if (Array.isArray(params.machinetypeId) && params.machinetypeId.length > 0) { + const ids = params.machinetypeId.map((id) => sql`${id}`); + whereParts.push(sql`e.machinetype_id in (${sql.join(ids, sql`, `)})`); + } const whereSql = whereParts.length ? sql.join([sql`where `, sql.join(whereParts, sql` and `)], sql``) : sql``; @@ -1733,7 +1744,7 @@ export interface ReleaseSearchParams { sort?: "year_desc" | "year_asc" | "title" | "entry_id_desc"; // Optional download-based filters (matched via EXISTS on downloads) dLanguageId?: string; // downloads.language_id - dMachinetypeId?: number; // downloads.machinetype_id + dMachinetypeId?: number | number[]; // downloads.machinetype_id filetypeId?: number; // downloads.filetype_id schemetypeId?: string; // downloads.schemetype_id sourcetypeId?: string; // downloads.sourcetype_id @@ -1754,7 +1765,10 @@ export async function searchReleases(params: ReleaseSearchParams): Promise 0 + : params.dMachinetypeId != null; + const preferMachineOrder = hasMachineFilter ? null : sql`case when ${entries.machinetypeId} = 27 then 0 @@ -1780,7 +1794,12 @@ export async function searchReleases(params: ReleaseSearchParams): Promise> = []; if (params.dLanguageId) dlConds.push(sql`d.language_id = ${params.dLanguageId}`); - if (params.dMachinetypeId != null) dlConds.push(sql`d.machinetype_id = ${params.dMachinetypeId}`); + if (typeof params.dMachinetypeId === "number") { + dlConds.push(sql`d.machinetype_id = ${params.dMachinetypeId}`); + } else if (Array.isArray(params.dMachinetypeId) && params.dMachinetypeId.length > 0) { + const ids = params.dMachinetypeId.map((id) => sql`${id}`); + dlConds.push(sql`d.machinetype_id in (${sql.join(ids, sql`, `)})`); + } if (params.filetypeId != null) dlConds.push(sql`d.filetype_id = ${params.filetypeId}`); if (params.schemetypeId) dlConds.push(sql`d.schemetype_id = ${params.schemetypeId}`); if (params.sourcetypeId) dlConds.push(sql`d.sourcetype_id = ${params.sourcetypeId}`);