Correct local file path resolution for ZXDB/WoS mirrors

- Remove optional path prefix and prepend the required local string.
- Avoid hardcoded 'SC' or 'WoS' subdirectories in path mapping.
- Maintain binary state: show local link only if env var is set and file exists.

Signed-off: junie@McFiver.local
This commit is contained in:
2026-02-17 12:30:55 +00:00
parent cbee214a6b
commit 77b5e76a08
6 changed files with 87 additions and 22 deletions

View File

@@ -1,5 +1,8 @@
import { and, desc, eq, sql, asc } from "drizzle-orm";
import { cache } from "react";
import fs from "fs";
import path from "path";
import { env } from "@/env";
// import { alias } from "drizzle-orm/mysql-core";
import { db } from "@/server/db";
import {
@@ -108,6 +111,30 @@ export interface EntryFacets {
};
}
/**
* Resolves a local link for a given file link if mirroring is enabled and the file exists.
*/
function resolveLocalLink(fileLink: string): string | null {
let localPath: string | null = null;
const zxdbPrefix = env.ZXDB_FILE_PREFIX || "/zxdb/sinclair/";
const wosPrefix = env.WOS_FILE_PREFIX || "/pub/sinclair/";
if (fileLink.startsWith(zxdbPrefix) && env.ZXDB_LOCAL_FILEPATH) {
const sub = fileLink.slice(zxdbPrefix.replace(/\/$/, "").length);
localPath = path.join(env.ZXDB_LOCAL_FILEPATH, sub);
} else if (fileLink.startsWith(wosPrefix) && env.WOS_LOCAL_FILEPATH) {
const sub = fileLink.slice(wosPrefix.replace(/\/$/, "").length);
localPath = path.join(env.WOS_LOCAL_FILEPATH, sub);
}
if (localPath && fs.existsSync(localPath)) {
return localPath;
}
return null;
}
function buildEntrySearchUnion(pattern: string, scope: EntrySearchScope) {
const parts: Array<ReturnType<typeof sql>> = [
sql`select ${searchByTitles.entryId} as entry_id from ${searchByTitles} where lower(${searchByTitles.entryTitle}) like ${pattern}`,
@@ -479,6 +506,7 @@ export interface EntryDetail {
case: { id: string | null; name: string | null };
year: number | null;
releaseSeq: number;
localLink?: string | null;
}[];
releases?: {
releaseSeq: number;
@@ -501,6 +529,7 @@ export interface EntryDetail {
source: { id: string | null; name: string | null };
case: { id: string | null; name: string | null };
year: number | null;
localLink?: string | null;
}[];
}[];
// Additional relationships surfaced on the entry detail page
@@ -710,6 +739,7 @@ export async function getEntryById(id: number): Promise<EntryDetail | null> {
source: { id: (d.sourceId) ?? null, name: (d.sourceName) ?? null },
case: { id: (d.caseId) ?? null, name: (d.caseName) ?? null },
year: d.year != null ? Number(d.year) : null,
localLink: resolveLocalLink(d.link),
})),
}));
@@ -1148,6 +1178,7 @@ export async function getEntryById(id: number): Promise<EntryDetail | null> {
case: { id: (d.caseId) ?? null, name: (d.caseName) ?? null },
year: d.year != null ? Number(d.year) : null,
releaseSeq: Number(d.releaseSeq),
localLink: resolveLocalLink(d.link),
})),
aliases: aliasRows.map((a) => ({ releaseSeq: Number(a.releaseSeq), languageId: a.languageId, title: a.title })),
webrefs: webrefRows.map((w) => ({ link: w.link, languageId: w.languageId, website: { id: Number(w.websiteId), name: w.websiteName, link: w.websiteLink } })),
@@ -2104,6 +2135,7 @@ export interface ReleaseDetail {
source: { id: string | null; name: string | null };
case: { id: string | null; name: string | null };
year: number | null;
localLink?: string | null;
}>;
scraps: Array<{
id: number;
@@ -2119,6 +2151,7 @@ export interface ReleaseDetail {
source: { id: string | null; name: string | null };
case: { id: string | null; name: string | null };
year: number | null;
localLink?: string | null;
}>;
files: Array<{
id: number;
@@ -2371,6 +2404,7 @@ export async function getReleaseDetail(entryId: number, releaseSeq: number): Pro
source: { id: d.sourceId ?? null, name: d.sourceName ?? null },
case: { id: d.caseId ?? null, name: d.caseName ?? null },
year: d.year != null ? Number(d.year) : null,
localLink: resolveLocalLink(d.link),
})),
scraps: (scrapRows as ScrapRow[]).map((s) => ({
id: Number(s.id),
@@ -2386,6 +2420,7 @@ export async function getReleaseDetail(entryId: number, releaseSeq: number): Pro
source: { id: s.sourceId ?? null, name: s.sourceName ?? null },
case: { id: s.caseId ?? null, name: s.caseName ?? null },
year: s.year != null ? Number(s.year) : null,
localLink: s.link ? resolveLocalLink(s.link) : null,
})),
files: fileRows.map((f) => ({
id: f.id,