Fix build errors
This commit is contained in:
@@ -6,7 +6,7 @@ import type { Config } from "drizzle-kit";
|
|||||||
export default {
|
export default {
|
||||||
schema: "./src/server/schema/**/*.ts",
|
schema: "./src/server/schema/**/*.ts",
|
||||||
out: "./drizzle",
|
out: "./drizzle",
|
||||||
driver: "mysql2",
|
dialect: "mysql",
|
||||||
dbCredentials: {
|
dbCredentials: {
|
||||||
// Read from env at runtime when using drizzle-kit
|
// Read from env at runtime when using drizzle-kit
|
||||||
url: process.env.ZXDB_URL!,
|
url: process.env.ZXDB_URL!,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Register } from '@/utils/register_parser';
|
|
||||||
import RegisterDetail from '@/app/registers/RegisterDetail';
|
import RegisterDetail from '@/app/registers/RegisterDetail';
|
||||||
import {Container, Row} from "react-bootstrap";
|
import {Container, Row} from "react-bootstrap";
|
||||||
import { getRegisters } from '@/services/register.service';
|
import { getRegisters } from '@/services/register.service';
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ export default function ZxdbExplorer({
|
|||||||
const json: Paged<Item> = await res.json();
|
const json: Paged<Item> = await res.json();
|
||||||
setData(json);
|
setData(json);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error(e);
|
console.error(e);
|
||||||
setData({ items: [], page: 1, pageSize, total: 0 });
|
setData({ items: [], page: 1, pageSize, total: 0 });
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -91,7 +91,6 @@ export default function EntriesExplorer({
|
|||||||
const json: Paged<Item> = await res.json();
|
const json: Paged<Item> = await res.json();
|
||||||
setData(json);
|
setData(json);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error(e);
|
console.error(e);
|
||||||
setData({ items: [], page: 1, pageSize, total: 0 });
|
setData({ items: [], page: 1, pageSize, total: 0 });
|
||||||
} finally {
|
} finally {
|
||||||
@@ -105,7 +104,6 @@ export default function EntriesExplorer({
|
|||||||
setData(initial);
|
setData(initial);
|
||||||
setPage(initial.page);
|
setPage(initial.page);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [initial]);
|
}, [initial]);
|
||||||
|
|
||||||
// Client fetch when filters/paging/sort change; also keep URL in sync
|
// Client fetch when filters/paging/sort change; also keep URL in sync
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export type EntryDetailData = {
|
|||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function EntryDetailClient({ data }: { data: EntryDetailData }) {
|
export default function EntryDetailClient({ data }: { data: EntryDetailData | null }) {
|
||||||
if (!data) return <div className="alert alert-warning">Not found</div>;
|
if (!data) return <div className="alert alert-warning">Not found</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -17,10 +17,14 @@ export default function LabelDetailClient({ id, initial, initialTab, initialQ }:
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
// Names are now delivered by SSR payload to minimize pop-in.
|
// Names are now delivered by SSR payload to minimize pop-in.
|
||||||
|
|
||||||
if (!initial || !initial.label) return <div className="alert alert-warning">Not found</div>;
|
// Hooks must be called unconditionally
|
||||||
|
const current = useMemo<Paged<Item> | null>(
|
||||||
|
() => (tab === "authored" ? initial?.authored : initial?.published) ?? null,
|
||||||
|
[initial, tab]
|
||||||
|
);
|
||||||
|
const totalPages = useMemo(() => (current ? Math.max(1, Math.ceil(current.total / current.pageSize)) : 1), [current]);
|
||||||
|
|
||||||
const current = useMemo(() => (tab === "authored" ? initial.authored : initial.published), [initial, tab]);
|
if (!initial || !initial.label) return <div className="alert alert-warning">Not found</div>;
|
||||||
const totalPages = useMemo(() => Math.max(1, Math.ceil(current.total / current.pageSize)), [current]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -98,19 +102,19 @@ export default function LabelDetailClient({ id, initial, initialTab, initialQ }:
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="d-flex align-items-center gap-2 mt-2">
|
<div className="d-flex align-items-center gap-2 mt-2">
|
||||||
<span>Page {current.page} / {totalPages}</span>
|
<span>Page {current ? current.page : 1} / {totalPages}</span>
|
||||||
<div className="ms-auto d-flex gap-2">
|
<div className="ms-auto d-flex gap-2">
|
||||||
<Link
|
<Link
|
||||||
className={`btn btn-sm btn-outline-secondary ${current.page <= 1 ? "disabled" : ""}`}
|
className={`btn btn-sm btn-outline-secondary ${current && current.page <= 1 ? "disabled" : ""}`}
|
||||||
aria-disabled={current.page <= 1}
|
aria-disabled={current ? current.page <= 1 : true}
|
||||||
href={`/zxdb/labels/${id}?${(() => { const p = new URLSearchParams(); p.set("tab", tab); if (q) p.set("q", q); p.set("page", String(Math.max(1, current.page - 1))); return p.toString(); })()}`}
|
href={`/zxdb/labels/${id}?${(() => { const p = new URLSearchParams(); p.set("tab", tab); if (q) p.set("q", q); p.set("page", String(Math.max(1, (current ? current.page : 1) - 1))); return p.toString(); })()}`}
|
||||||
>
|
>
|
||||||
Prev
|
Prev
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
className={`btn btn-sm btn-outline-secondary ${current.page >= totalPages ? "disabled" : ""}`}
|
className={`btn btn-sm btn-outline-secondary ${current && current.page >= totalPages ? "disabled" : ""}`}
|
||||||
aria-disabled={current.page >= totalPages}
|
aria-disabled={current ? current.page >= totalPages : true}
|
||||||
href={`/zxdb/labels/${id}?${(() => { const p = new URLSearchParams(); p.set("tab", tab); if (q) p.set("q", q); p.set("page", String(Math.min(totalPages, current.page + 1))); return p.toString(); })()}`}
|
href={`/zxdb/labels/${id}?${(() => { const p = new URLSearchParams(); p.set("tab", tab); if (q) p.set("q", q); p.set("page", String(Math.min(totalPages, (current ? current.page : 1) + 1))); return p.toString(); })()}`}
|
||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -104,7 +104,6 @@ export default function ReleasesExplorer({
|
|||||||
const json: Paged<Item> = await res.json();
|
const json: Paged<Item> = await res.json();
|
||||||
setData(json);
|
setData(json);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error(e);
|
console.error(e);
|
||||||
setData({ items: [], page: 1, pageSize, total: 0 });
|
setData({ items: [], page: 1, pageSize, total: 0 });
|
||||||
} finally {
|
} finally {
|
||||||
@@ -117,7 +116,6 @@ export default function ReleasesExplorer({
|
|||||||
setData(initial);
|
setData(initial);
|
||||||
setPage(initial.page);
|
setPage(initial.page);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [initial]);
|
}, [initial]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -141,7 +139,6 @@ export default function ReleasesExplorer({
|
|||||||
}
|
}
|
||||||
updateUrl(page);
|
updateUrl(page);
|
||||||
fetchData(q, page);
|
fetchData(q, page);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [page, year, sort, dLanguageId, dMachinetypeId, filetypeId, schemetypeId, sourcetypeId, casetypeId, isDemo]);
|
}, [page, year, sort, dLanguageId, dMachinetypeId, filetypeId, schemetypeId, sourcetypeId, casetypeId, isDemo]);
|
||||||
|
|
||||||
function onSubmit(e: React.FormEvent) {
|
function onSubmit(e: React.FormEvent) {
|
||||||
@@ -286,7 +283,7 @@ export default function ReleasesExplorer({
|
|||||||
<label className="form-check-label" htmlFor="demoCheck">Demo only</label>
|
<label className="form-check-label" htmlFor="demoCheck">Demo only</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-auto">
|
<div className="col-auto">
|
||||||
<select className="form-select" value={sort} onChange={(e) => { setSort(e.target.value); setPage(1); }}>
|
<select className="form-select" value={sort} onChange={(e) => { setSort(e.target.value as typeof sort); setPage(1); }}>
|
||||||
<option value="year_desc">Sort: Newest</option>
|
<option value="year_desc">Sort: Newest</option>
|
||||||
<option value="year_asc">Sort: Oldest</option>
|
<option value="year_asc">Sort: Oldest</option>
|
||||||
<option value="title">Sort: Title</option>
|
<option value="title">Sort: Title</option>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import * as Icon from "react-bootstrap-icons";
|
import { Navbar, Nav, Container } from "react-bootstrap";
|
||||||
import { Navbar, Nav, Container, Dropdown } from "react-bootstrap";
|
|
||||||
import ThemeDropdown from "@/components/ThemeDropdown";
|
import ThemeDropdown from "@/components/ThemeDropdown";
|
||||||
|
|
||||||
export default function NavbarClient() {
|
export default function NavbarClient() {
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ function formatErrors(errors: z.ZodFormattedError<Map<string, string>, string>)
|
|||||||
const parsed = serverSchema.safeParse(process.env);
|
const parsed = serverSchema.safeParse(process.env);
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
// Fail fast with helpful output in server context
|
// Fail fast with helpful output in server context
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error("❌ Invalid environment variables:\n" + formatErrors(parsed.error.format()));
|
console.error("❌ Invalid environment variables:\n" + formatErrors(parsed.error.format()));
|
||||||
throw new Error("Invalid environment variables");
|
throw new Error("Invalid environment variables");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { and, desc, eq, like, sql, asc } from "drizzle-orm";
|
import { and, desc, eq, like, sql, asc } from "drizzle-orm";
|
||||||
import { alias } from "drizzle-orm/mysql-core";
|
// import { alias } from "drizzle-orm/mysql-core";
|
||||||
import { db } from "@/server/db";
|
import { db } from "@/server/db";
|
||||||
import {
|
import {
|
||||||
entries,
|
entries,
|
||||||
@@ -88,7 +88,7 @@ export async function searchEntries(params: SearchParams): Promise<PagedResult<S
|
|||||||
|
|
||||||
const [items, countRows] = await Promise.all([
|
const [items, countRows] = await Promise.all([
|
||||||
(async () => {
|
(async () => {
|
||||||
let q1 = db
|
const q1 = db
|
||||||
.select({
|
.select({
|
||||||
id: entries.id,
|
id: entries.id,
|
||||||
title: entries.title,
|
title: entries.title,
|
||||||
@@ -100,12 +100,12 @@ export async function searchEntries(params: SearchParams): Promise<PagedResult<S
|
|||||||
})
|
})
|
||||||
.from(entries)
|
.from(entries)
|
||||||
.leftJoin(machinetypes, eq(machinetypes.id, entries.machinetypeId))
|
.leftJoin(machinetypes, eq(machinetypes.id, entries.machinetypeId))
|
||||||
.leftJoin(languages, eq(languages.id, entries.languageId));
|
.leftJoin(languages, eq(languages.id, entries.languageId))
|
||||||
if (whereExpr) q1 = q1.where(whereExpr);
|
.where(whereExpr ?? sql`true`)
|
||||||
return q1
|
|
||||||
.orderBy(sort === "id_desc" ? desc(entries.id) : entries.title)
|
.orderBy(sort === "id_desc" ? desc(entries.id) : entries.title)
|
||||||
.limit(pageSize)
|
.limit(pageSize)
|
||||||
.offset(offset);
|
.offset(offset);
|
||||||
|
return q1;
|
||||||
})(),
|
})(),
|
||||||
db
|
db
|
||||||
.select({ total: sql<number>`count(*)` })
|
.select({ total: sql<number>`count(*)` })
|
||||||
@@ -373,9 +373,10 @@ export async function getEntryById(id: number): Promise<EntryDetail | null> {
|
|||||||
|
|
||||||
const downloadsBySeq = new Map<number, DownloadRow[]>();
|
const downloadsBySeq = new Map<number, DownloadRow[]>();
|
||||||
for (const row of downloadRows) {
|
for (const row of downloadRows) {
|
||||||
const arr = downloadsBySeq.get(row.releaseSeq) ?? [];
|
const key = Number(row.releaseSeq);
|
||||||
|
const arr = downloadsBySeq.get(key) ?? [];
|
||||||
arr.push(row);
|
arr.push(row);
|
||||||
downloadsBySeq.set(row.releaseSeq, arr);
|
downloadsBySeq.set(key, arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a map of downloads grouped by release_seq
|
// Build a map of downloads grouped by release_seq
|
||||||
@@ -386,22 +387,22 @@ export async function getEntryById(id: number): Promise<EntryDetail | null> {
|
|||||||
type: { id: null, name: null },
|
type: { id: null, name: null },
|
||||||
language: { id: null, name: null },
|
language: { id: null, name: null },
|
||||||
machinetype: { id: null, name: null },
|
machinetype: { id: null, name: null },
|
||||||
year: (r.year) ?? null,
|
year: r.year != null ? Number(r.year) : null,
|
||||||
comments: null,
|
comments: null,
|
||||||
downloads: (downloadsBySeq.get(Number(r.releaseSeq)) ?? []).map((d) => ({
|
downloads: (downloadsBySeq.get(Number(r.releaseSeq)) ?? []).map((d) => ({
|
||||||
id: d.id,
|
id: Number(d.id),
|
||||||
link: d.link,
|
link: d.link,
|
||||||
size: d.size ?? null,
|
size: d.size != null ? Number(d.size) : null,
|
||||||
md5: d.md5 ?? null,
|
md5: d.md5 ?? null,
|
||||||
comments: d.comments ?? null,
|
comments: d.comments ?? null,
|
||||||
isDemo: !!d.isDemo,
|
isDemo: !!d.isDemo,
|
||||||
type: { id: d.filetypeId, name: d.filetypeName },
|
type: { id: Number(d.filetypeId), name: d.filetypeName },
|
||||||
language: { id: (d.dlLangId) ?? null, name: (d.dlLangName) ?? null },
|
language: { id: (d.dlLangId) ?? null, name: (d.dlLangName) ?? null },
|
||||||
machinetype: { id: (d.dlMachineId) ?? null, name: (d.dlMachineName) ?? null },
|
machinetype: { id: d.dlMachineId != null ? Number(d.dlMachineId) : null, name: (d.dlMachineName) ?? null },
|
||||||
scheme: { id: (d.schemeId) ?? null, name: (d.schemeName) ?? null },
|
scheme: { id: (d.schemeId) ?? null, name: (d.schemeName) ?? null },
|
||||||
source: { id: (d.sourceId) ?? null, name: (d.sourceName) ?? null },
|
source: { id: (d.sourceId) ?? null, name: (d.sourceName) ?? null },
|
||||||
case: { id: (d.caseId) ?? null, name: (d.caseName) ?? null },
|
case: { id: (d.caseId) ?? null, name: (d.caseName) ?? null },
|
||||||
year: (d.year) ?? null,
|
year: d.year != null ? Number(d.year) : null,
|
||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -437,19 +438,19 @@ export async function getEntryById(id: number): Promise<EntryDetail | null> {
|
|||||||
: [],
|
: [],
|
||||||
releases: releasesData,
|
releases: releasesData,
|
||||||
downloadsFlat: downloadFlatRows.map((d) => ({
|
downloadsFlat: downloadFlatRows.map((d) => ({
|
||||||
id: d.id,
|
id: Number(d.id),
|
||||||
link: d.link,
|
link: d.link,
|
||||||
size: d.size ?? null,
|
size: d.size != null ? Number(d.size) : null,
|
||||||
md5: d.md5 ?? null,
|
md5: d.md5 ?? null,
|
||||||
comments: d.comments ?? null,
|
comments: d.comments ?? null,
|
||||||
isDemo: !!d.isDemo,
|
isDemo: !!d.isDemo,
|
||||||
type: { id: d.filetypeId, name: d.filetypeName },
|
type: { id: Number(d.filetypeId), name: d.filetypeName },
|
||||||
language: { id: (d.dlLangId) ?? null, name: (d.dlLangName) ?? null },
|
language: { id: (d.dlLangId) ?? null, name: (d.dlLangName) ?? null },
|
||||||
machinetype: { id: (d.dlMachineId) ?? null, name: (d.dlMachineName) ?? null },
|
machinetype: { id: d.dlMachineId != null ? Number(d.dlMachineId) : null, name: (d.dlMachineName) ?? null },
|
||||||
scheme: { id: (d.schemeId) ?? null, name: (d.schemeName) ?? null },
|
scheme: { id: (d.schemeId) ?? null, name: (d.schemeName) ?? null },
|
||||||
source: { id: (d.sourceId) ?? null, name: (d.sourceName) ?? null },
|
source: { id: (d.sourceId) ?? null, name: (d.sourceName) ?? null },
|
||||||
case: { id: (d.caseId) ?? null, name: (d.caseName) ?? null },
|
case: { id: (d.caseId) ?? null, name: (d.caseName) ?? null },
|
||||||
year: (d.year) ?? null,
|
year: d.year != null ? Number(d.year) : null,
|
||||||
releaseSeq: Number(d.releaseSeq),
|
releaseSeq: Number(d.releaseSeq),
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
@@ -457,7 +458,7 @@ export async function getEntryById(id: number): Promise<EntryDetail | null> {
|
|||||||
|
|
||||||
// ----- Labels -----
|
// ----- Labels -----
|
||||||
|
|
||||||
export interface LabelDetail extends LabelSummary {}
|
export type LabelDetail = LabelSummary;
|
||||||
|
|
||||||
export interface LabelSearchParams {
|
export interface LabelSearchParams {
|
||||||
q?: string;
|
q?: string;
|
||||||
@@ -482,20 +483,18 @@ export async function searchLabels(params: LabelSearchParams): Promise<PagedResu
|
|||||||
return { items: items, page, pageSize, total };
|
return { items: items, page, pageSize, total };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using helper search_by_names for efficiency
|
// Using helper search_by_names for efficiency via subselect to avoid raw identifier typing
|
||||||
const pattern = `%${q}%`;
|
const pattern = `%${q}%`;
|
||||||
const countRows = await db
|
const countRows = await db
|
||||||
.select({ total: sql<number>`count(distinct ${sql.identifier("label_id")})` })
|
.select({ total: sql<number>`count(*)` })
|
||||||
.from(sql`search_by_names`)
|
.from(labels)
|
||||||
.where(like(sql.identifier("label_name"), pattern));
|
.where(sql`${labels.id} in (select distinct label_id from search_by_names where label_name like ${pattern})`);
|
||||||
const total = Number(countRows[0]?.total ?? 0);
|
const total = Number(countRows[0]?.total ?? 0);
|
||||||
|
|
||||||
const items = await db
|
const items = await db
|
||||||
.select({ id: labels.id, name: labels.name, labeltypeId: labels.labeltypeId })
|
.select({ id: labels.id, name: labels.name, labeltypeId: labels.labeltypeId })
|
||||||
.from(sql`search_by_names`)
|
.from(labels)
|
||||||
.innerJoin(labels, eq(labels.id, sql.identifier("label_id")))
|
.where(sql`${labels.id} in (select distinct label_id from search_by_names where label_name like ${pattern})`)
|
||||||
.where(like(sql.identifier("label_name"), pattern))
|
|
||||||
.groupBy(labels.id)
|
|
||||||
.orderBy(labels.name)
|
.orderBy(labels.name)
|
||||||
.limit(pageSize)
|
.limit(pageSize)
|
||||||
.offset(offset);
|
.offset(offset);
|
||||||
|
|||||||
Reference in New Issue
Block a user