Files
explorer/docs/ZXDB.md
D. Rimron-Soutter 32985c33b9 Proxy local ZXDB/WoS mirror downloads through application API
- Created `src/app/api/zxdb/download/route.ts` to serve local files.
- Updated `resolveLocalLink` in `src/server/repo/zxdb.ts` to return
  API-relative URLs with `source` and `path` parameters.
- Encodes the relative subpath to ensure correct URL construction.
- Includes security checks in the API route to prevent path traversal.
- Updated `docs/ZXDB.md` to reflect the proxy mechanism.

Signed-off: junie@lucy.xalior.com
2026-02-17 12:43:26 +00:00

6.8 KiB
Raw Blame History

ZXDB Guide

This document explains how the ZXDB Explorer works in this project, how to set up the database connection, and how to use the builtin API and UI for software discovery.

What is ZXDB?

ZXDB (https://github.com/zxdb/ZXDB) is a communitymaintained database of ZX Spectrum software, publications, and related entities. In this project, we connect to a MySQL ZXDB instance in readonly mode and expose a fast, crosslinked explorer UI under /zxdb.

Prerequisites

  • MySQL server with ZXDB data (or at minimum the tables; data is needed to browse).
  • Ability to run the helper SQL that builds search tables (required for efficient LIKE searches).
  • A readonly MySQL user for the app (recommended).
  • The ZXDB submodule is checked in for schemas/scripts; use pnpm setup:zxdb-local after cloning to keep local SQL dumps untracked.

Database setup

  1. Import ZXDB data into MySQL.

  2. Create helper search tables (required).

    • Run https://github.com/zxdb/ZXDB/blob/master/scripts/ZXDB_help_search.sql on your ZXDB database.
    • This creates search_by_titles, search_by_names, search_by_authors, search_by_publishers, search_by_aliases, search_by_origins, search_by_magrefs, search_by_magazines, and search_by_issues tables used for search scopes and magazine references.
  3. Create a readonly role/user (recommended).

    • Create user zxdb_readonly.
    • Grant SELECT, SHOW VIEW on your zxdb database to the user.

Environment configuration

Set the connection string in .env:

ZXDB_URL=mysql://zxdb_readonly:password@hostname:3306/zxdb

Notes:

  • The URL must start with mysql://. Env is validated at boot by src/env.ts (Zod), failing fast if misconfigured.
  • The app uses a singleton mysql2 pool (src/server/db.ts) and Drizzle ORM for typed queries.

Local File Mirrors

The explorer can optionally show "Local Mirror" links for downloads if you have local copies of the ZXDB and World of Spectrum (WoS) file archives.

Configuration

To enable local mirrors, set the following variables in your .env:

# Absolute paths to your local mirrors
ZXDB_LOCAL_FILEPATH=/path/to/your/zxdb/mirror
WOS_LOCAL_FILEPATH=/path/to/your/wos/mirror

# Optional: Remote path prefixes to strip from database links before prepending local paths
ZXDB_FILE_PREFIX=/zxdb/sinclair/
WOS_FILE_PREFIX=/pub/sinclair/

How it works

  1. The app identifies if a download link matches ZXDB_FILE_PREFIX or WOS_FILE_PREFIX.
  2. It strips the prefix from the database link.
  3. It joins the remaining relative path to the corresponding *_LOCAL_FILEPATH.
  4. It checks if the file exists on the local disk.
  5. If the file exists and the environment variable is set, a "Local Mirror" link is displayed in the UI, pointing to a proxy download API (/api/zxdb/download).

Note: Obtaining these mirrors is left as an exercise to the host. The paths do not need to share a common parent directory. Both mirrors are optional and independent; you can configure one, both, or neither.

Running

pnpm install
pnpm dev
# open http://localhost:4000 and navigate to /zxdb

Explorer UI overview

  • /zxdb — Search entries by title and filter by genre, language, and machine type; sort and paginate results.
  • /zxdb/entries — Entries search with scope toggles (titles/aliases/origins) and facets.
  • /zxdb/entries/[id] — Entry details with related releases, downloads, origins, relations, and media.
  • /zxdb/releases — Releases search + filters.
  • /zxdb/releases/[entryId]/[releaseSeq] — Release detail: magazine references, downloads, scraps, and issue files.
  • /zxdb/labels and /zxdb/labels/[id] — Browse/search labels (people/companies), permissions, licenses, and authored/published entries.
  • /zxdb/genres, /zxdb/languages, /zxdb/machinetypes — Category hubs with linked detail pages listing entries.
  • /zxdb/magazines and /zxdb/magazines/[id] — Magazine list and issue navigation.
  • /zxdb/issues/[id] — Issue detail with contents and references.

Crosslinking: All entities are permalinks using stable IDs. Navigation uses Next Link so pages are prefetched.

Performance: Detail and index pages are serverrendered with initial data and use ISR (revalidate = 3600) to reduce timetofirstcontent. Queries select only required columns and leverage helper tables for text search.

HTTP API reference (selected)

All endpoints are under /api/zxdb and validate inputs with Zod. Responses are JSON.

  • Search entries

    • GET /api/zxdb/search
    • Query params:
      • q — string (freetext search; normalized via helper tables)
      • page, pageSize — pagination (default pageSize=20, max=100)
      • genreId, languageId, machinetypeId — optional filters
      • sorttitle or id_desc
      • scopetitle, title_aliases, or title_aliases_origins
      • facets — boolean; if truthy, includes facet counts for genres/languages/machines
  • Entry detail

    • GET /api/zxdb/entries/[id]
    • Returns: entry core fields, joined genre/language/machinetype names, authors and publishers.
  • Labels

    • GET /api/zxdb/labels/search?q=...
    • GET /api/zxdb/labels/[id]?page=1&pageSize=20 — includes authored and published lists.
  • Categories

    • GET /api/zxdb/genres and /api/zxdb/genres/[id]?page=1
    • GET /api/zxdb/languages and /api/zxdb/languages/[id]?page=1
    • GET /api/zxdb/machinetypes and /api/zxdb/machinetypes/[id]?page=1

Runtime: API routes declare export const runtime = "nodejs" to support mysql2.

Implementation notes

  • Drizzle models map ZXDB lookup table column text to property name for ergonomics (e.g., languages.textname).
  • Next.js 15 dynamic params must be awaited in App Router pages and API routes. Example:
    export default async function Page({ params }: { params: Promise<{ id: string }> }) {
      const { id } = await params;
      // ...
    }
    
  • Repository queries parallelize independent calls with Promise.all for lower latency.

Troubleshooting

  • 400 from dynamic API routes: ensure you await ctx.params before Zod validation.
  • Missing facets or scope toggles: ensure helper tables from ZXDB_help_search.sql exist.
  • Unknown column errors for lookup names: ZXDB tables use column text for names; Drizzle schema must select text as name.
  • Slow entry page: confirm serverrendering is active and ISR is set; client components should not fetch on the first paint when initial props are provided.
  • MySQL auth or network errors: verify ZXDB_URL and that your user has read permissions.

Roadmap

  • Issue-centric media grouping and richer magazine metadata.
  • Additional cross-links for tags, relations, and permissions as UI expands.
  • A11y polish and higher-level navigation enhancements.