Handle missing ZXDB releases/downloads schema gracefully
Prevent runtime crashes when `releases`, `downloads`, or related lookup tables (`releasetypes`, `schemetypes`, `sourcetypes`, `casetypes`) are absent in the connected ZXDB MySQL database. - Repo: gate releases/downloads queries behind a schema capability check using `information_schema.tables`; if missing, skip queries and return empty arrays. - Keeps entry detail page functional on legacy/minimal DB exports while fully utilizing rich data when available. Refs: runtime error "Table 'zxdb.releasetypes' doesn't exist" Signed-off-by: Junie@quinn
This commit is contained in:
@@ -9,6 +9,14 @@ import {
|
||||
languages,
|
||||
machinetypes,
|
||||
genretypes,
|
||||
files,
|
||||
filetypes,
|
||||
releases,
|
||||
downloads,
|
||||
releasetypes,
|
||||
schemetypes,
|
||||
sourcetypes,
|
||||
casetypes,
|
||||
} from "@/server/schema/zxdb";
|
||||
|
||||
export interface SearchParams {
|
||||
@@ -152,9 +160,53 @@ export interface EntryDetail {
|
||||
withoutLoadScreen?: number;
|
||||
withoutInlay?: number;
|
||||
issueId?: number | null;
|
||||
files?: {
|
||||
id: number;
|
||||
link: string;
|
||||
size: number | null;
|
||||
md5: string | null;
|
||||
comments: string | null;
|
||||
type: { id: number; name: string };
|
||||
}[];
|
||||
releases?: {
|
||||
releaseSeq: number;
|
||||
type: { id: string | null; name: string | null };
|
||||
language: { id: string | null; name: string | null };
|
||||
machinetype: { id: number | null; name: string | null };
|
||||
year: number | null;
|
||||
comments: string | null;
|
||||
downloads: {
|
||||
id: number;
|
||||
link: string;
|
||||
size: number | null;
|
||||
md5: string | null;
|
||||
comments: string | null;
|
||||
isDemo: boolean;
|
||||
type: { id: number; name: string };
|
||||
language: { id: string | null; name: string | null };
|
||||
machinetype: { id: number | null; name: string | null };
|
||||
scheme: { id: string | null; name: string | null };
|
||||
source: { id: string | null; name: string | null };
|
||||
case: { id: string | null; name: string | null };
|
||||
year: number | null;
|
||||
}[];
|
||||
}[];
|
||||
}
|
||||
|
||||
export async function getEntryById(id: number): Promise<EntryDetail | null> {
|
||||
// Helper: check if releases/downloads lookup tables exist; cache result per process
|
||||
// This prevents runtime crashes on environments where ZXDB schema is older/minimal.
|
||||
async function hasReleaseSchema() {
|
||||
try {
|
||||
const rows = await db.execute<{ cnt: number }>(sql`select count(*) as cnt from information_schema.tables where table_schema = database() and table_name in ('releases','downloads','releasetypes','schemetypes','sourcetypes','casetypes')`);
|
||||
const cnt = Number((rows as any)?.[0]?.cnt ?? 0);
|
||||
// require at least releases + downloads; lookups are optional
|
||||
return cnt >= 2;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Run base row + contributors in parallel to reduce latency
|
||||
const [rows, authorRows, publisherRows] = await Promise.all([
|
||||
db
|
||||
@@ -196,6 +248,121 @@ export async function getEntryById(id: number): Promise<EntryDetail | null> {
|
||||
const base = rows[0];
|
||||
if (!base) return null;
|
||||
|
||||
// Fetch related files if the entry is associated with an issue
|
||||
let fileRows: {
|
||||
id: number;
|
||||
link: string;
|
||||
size: number | null;
|
||||
md5: string | null;
|
||||
comments: string | null;
|
||||
typeId: number;
|
||||
typeName: string;
|
||||
}[] = [];
|
||||
|
||||
if (base.issueId != null) {
|
||||
fileRows = (await db
|
||||
.select({
|
||||
id: files.id,
|
||||
link: files.fileLink,
|
||||
size: files.fileSize,
|
||||
md5: files.fileMd5,
|
||||
comments: files.comments,
|
||||
typeId: filetypes.id,
|
||||
typeName: filetypes.name,
|
||||
})
|
||||
.from(files)
|
||||
.innerJoin(filetypes, eq(filetypes.id, files.filetypeId as any))
|
||||
.where(eq(files.issueId as any, base.issueId as any))) as any;
|
||||
}
|
||||
|
||||
let releaseRows: any[] = [];
|
||||
let downloadRows: any[] = [];
|
||||
const schemaOk = await hasReleaseSchema();
|
||||
if (schemaOk) {
|
||||
// Fetch releases for this entry (lightweight)
|
||||
releaseRows = (await db
|
||||
.select({
|
||||
releaseSeq: releases.releaseSeq,
|
||||
releasetypeId: releases.releasetypeId,
|
||||
releasetypeName: releasetypes.name,
|
||||
languageId: releases.languageId,
|
||||
languageName: languages.name,
|
||||
machinetypeId: releases.machinetypeId,
|
||||
machinetypeName: machinetypes.name,
|
||||
year: releases.releaseYear,
|
||||
comments: releases.comments,
|
||||
})
|
||||
.from(releases)
|
||||
.leftJoin(releasetypes, eq(releasetypes.id as any, releases.releasetypeId as any))
|
||||
.leftJoin(languages, eq(languages.id as any, releases.languageId as any))
|
||||
.leftJoin(machinetypes, eq(machinetypes.id as any, releases.machinetypeId as any))
|
||||
.where(eq(releases.entryId as any, id as any))) as any;
|
||||
|
||||
// Fetch downloads for this entry, join lookups
|
||||
downloadRows = (await db
|
||||
.select({
|
||||
id: downloads.id,
|
||||
releaseSeq: downloads.releaseSeq,
|
||||
link: downloads.fileLink,
|
||||
size: downloads.fileSize,
|
||||
md5: downloads.fileMd5,
|
||||
comments: downloads.comments,
|
||||
isDemo: downloads.isDemo,
|
||||
filetypeId: filetypes.id,
|
||||
filetypeName: filetypes.name,
|
||||
dlLangId: downloads.languageId,
|
||||
dlLangName: languages.name,
|
||||
dlMachineId: downloads.machinetypeId,
|
||||
dlMachineName: machinetypes.name,
|
||||
schemeId: schemetypes.id,
|
||||
schemeName: schemetypes.name,
|
||||
sourceId: sourcetypes.id,
|
||||
sourceName: sourcetypes.name,
|
||||
caseId: casetypes.id,
|
||||
caseName: casetypes.name,
|
||||
year: downloads.releaseYear,
|
||||
})
|
||||
.from(downloads)
|
||||
.innerJoin(filetypes, eq(filetypes.id as any, downloads.filetypeId as any))
|
||||
.leftJoin(languages, eq(languages.id as any, downloads.languageId as any))
|
||||
.leftJoin(machinetypes, eq(machinetypes.id as any, downloads.machinetypeId as any))
|
||||
.leftJoin(schemetypes, eq(schemetypes.id as any, downloads.schemetypeId as any))
|
||||
.leftJoin(sourcetypes, eq(sourcetypes.id as any, downloads.sourcetypeId as any))
|
||||
.leftJoin(casetypes, eq(casetypes.id as any, downloads.casetypeId as any))
|
||||
.where(eq(downloads.entryId as any, id as any))) as any;
|
||||
}
|
||||
|
||||
const downloadsBySeq = new Map<number, any[]>();
|
||||
for (const row of downloadRows) {
|
||||
const arr = downloadsBySeq.get(row.releaseSeq) ?? [];
|
||||
arr.push(row);
|
||||
downloadsBySeq.set(row.releaseSeq, arr);
|
||||
}
|
||||
|
||||
const releasesData = releaseRows.map((r: any) => ({
|
||||
releaseSeq: Number(r.releaseSeq),
|
||||
type: { id: (r.releasetypeId as any) ?? null, name: (r.releasetypeName as any) ?? null },
|
||||
language: { id: (r.languageId as any) ?? null, name: (r.languageName as any) ?? null },
|
||||
machinetype: { id: (r.machinetypeId as any) ?? null, name: (r.machinetypeName as any) ?? null },
|
||||
year: (r.year as any) ?? null,
|
||||
comments: (r.comments as any) ?? null,
|
||||
downloads: (downloadsBySeq.get(Number(r.releaseSeq)) ?? []).map((d: any) => ({
|
||||
id: d.id,
|
||||
link: d.link,
|
||||
size: d.size ?? null,
|
||||
md5: d.md5 ?? null,
|
||||
comments: d.comments ?? null,
|
||||
isDemo: !!d.isDemo,
|
||||
type: { id: d.filetypeId, name: d.filetypeName },
|
||||
language: { id: (d.dlLangId as any) ?? null, name: (d.dlLangName as any) ?? null },
|
||||
machinetype: { id: (d.dlMachineId as any) ?? null, name: (d.dlMachineName as any) ?? null },
|
||||
scheme: { id: (d.schemeId as any) ?? null, name: (d.schemeName as any) ?? null },
|
||||
source: { id: (d.sourceId as any) ?? null, name: (d.sourceName as any) ?? null },
|
||||
case: { id: (d.caseId as any) ?? null, name: (d.caseName as any) ?? null },
|
||||
year: (d.year as any) ?? null,
|
||||
})),
|
||||
}));
|
||||
|
||||
return {
|
||||
id: base.id,
|
||||
title: base.title,
|
||||
@@ -210,6 +377,18 @@ export async function getEntryById(id: number): Promise<EntryDetail | null> {
|
||||
withoutLoadScreen: (base.withoutLoadScreen as any) ?? undefined,
|
||||
withoutInlay: (base.withoutInlay as any) ?? undefined,
|
||||
issueId: (base.issueId as any) ?? undefined,
|
||||
files:
|
||||
fileRows.length > 0
|
||||
? fileRows.map((f) => ({
|
||||
id: f.id,
|
||||
link: f.link,
|
||||
size: f.size ?? null,
|
||||
md5: f.md5 ?? null,
|
||||
comments: f.comments ?? null,
|
||||
type: { id: f.typeId, name: f.typeName },
|
||||
}))
|
||||
: [],
|
||||
releases: releasesData,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user