No description
  • TypeScript 80.7%
  • JavaScript 16.3%
  • Dockerfile 2.2%
  • CSS 0.8%
Find a file
Freydis Mende 4458969864
All checks were successful
CI / build (push) Successful in 37s
CI / Docker – build image (push) Successful in 47s
CI / deploy (push) Successful in 0s
rename web network, drop traefik labels
2026-08-30 22:11:07 +02:00
.forgejo/workflows Add /api/health and /api/version, bake in git commit hash, add docker CI job 2026-08-14 10:23:38 +02:00
app Store card images in an S3-compatible bucket instead of on disk 2026-08-14 21:05:21 +02:00
data first commit 2026-06-14 18:25:31 +02:00
lib Store card images in an S3-compatible bucket instead of on disk 2026-08-14 21:05:21 +02:00
prisma first commit 2026-06-14 18:25:31 +02:00
public fix caching 2026-06-14 19:05:47 +02:00
scripts Store card images in an S3-compatible bucket instead of on disk 2026-08-14 21:05:21 +02:00
.gitignore Store card images in an S3-compatible bucket instead of on disk 2026-08-14 21:05:21 +02:00
AGENTS.md Initial commit from Create Next App 2026-06-14 16:40:17 +02:00
CLAUDE.md Initial commit from Create Next App 2026-06-14 16:40:17 +02:00
compose.yaml rename web network, drop traefik labels 2026-08-30 22:11:07 +02:00
Dockerfile Make git-hash Dockerfile stage tolerant of a missing .git 2026-08-14 19:57:33 +02:00
eslint.config.mjs first commit 2026-06-14 18:25:31 +02:00
next.config.ts Handle concurrent slot init races and drop dead image allowlist 2026-07-07 22:07:23 +02:00
package-lock.json Store card images in an S3-compatible bucket instead of on disk 2026-08-14 21:05:21 +02:00
package.json Store card images in an S3-compatible bucket instead of on disk 2026-08-14 21:05:21 +02:00
postcss.config.mjs Initial commit from Create Next App 2026-06-14 16:40:17 +02:00
prisma.config.ts first commit 2026-06-14 18:25:31 +02:00
README.md Store card images in an S3-compatible bucket instead of on disk 2026-08-14 21:05:21 +02:00
tsconfig.json Initial commit from Create Next App 2026-06-14 16:40:17 +02:00

dex-binder

A Gen 1 Pokémon TCG binder tracker focused on Japanese cards. Browse all 151 original Pokémon in a book-style binder, pick the exact Japanese card you own from a visual card browser, and track your collection — with offline support via PWA.

Features

  • Book-style binder — 3×3 card grid per page, paginated as a two-page spread
  • Japanese card browser — 400+ Japanese cards indexed from modern SV-era sets via TCGDex
  • Custom card entry — paste any image URL for cards not in the index (e.g. older or obscure sets)
  • German Pokémon names — fetched from PokéAPI and cached server-side
  • Search — find any Pokémon by German name, English name, or Pokédex number
  • S3-compatible image storage — selected card images are downloaded and saved to an S3-compatible bucket on selection, so the binder keeps working even if the source (TCGDex, pokemontcg.io, a pasted URL) later renames or removes the file
  • Offline support — IndexedDB cache + PWA service worker
  • Persistent storage — SQLite via Prisma, survives restarts

Stack

Layer Technology
Framework Next.js 16 (App Router, TypeScript)
Styling Tailwind CSS
Database SQLite via Prisma v7 + better-sqlite3
Card data TCGDex Japanese API (static seed)
Pokémon names PokéAPI
Offline cache IndexedDB (idb)
PWA Custom service worker + Web App Manifest

Getting Started

Prerequisites

  • Node.js 24+
  • npm

Local development

npm install
npx prisma migrate dev
node scripts/seed-jp-cards.js   # builds data/gen1-jp-cards.json (~434 cards)
npm run dev

Open http://localhost:3000.

Re-seeding card data

The card index in data/gen1-jp-cards.json is a static snapshot built from TCGDex. Re-run the seed script whenever you want to pick up newly indexed sets:

node scripts/seed-jp-cards.js

API

Method Path Description
GET /api/health Health check ({"status":"ok","database":"connected"}, 503 if the DB check fails)
GET /api/version Git commit hash baked in at build time ({"version":"<hash>"}, "unknown" outside Docker)
GET /api/cards Japanese card index lookup
GET/POST /api/slots Binder slot data
GET/PATCH/DELETE /api/slots/:id Manage a single binder slot
GET /api/card-images/:filename Proxy a card image out of the S3-compatible bucket

Usage

  • Click a card slot to mark it as done — opens the Japanese card picker
  • Right-click a completed slot to change the card
  • Click a completed slot again to unmark it
  • Use the search bar to jump to any Pokémon by name or #number
  • If a card isn't in the index, use the "Custom card" section at the bottom of the picker to paste any image URL directly

Deployment

Docker Compose

docker compose up --build

The SQLite database is stored in a named Docker volume (db-data) mounted at /data/dex-binder.db inside the container. Migrations run automatically on startup. The image build bakes in the git commit hash (git rev-parse --short HEAD), exposed at runtime via /api/version.

Environment variables

Variable Default Description
DATABASE_URL file:./dev.db SQLite path (file:<path>)
S3_ENDPOINT — (required) S3-compatible endpoint URL
S3_REGION eu-central-1 Region passed to the S3 client (some self-hosted S3-compatible stores ignore the value but still require one)
S3_BUCKET — (required) Bucket card images are stored in
S3_ACCESS_KEY_ID — (required) Access key for S3_BUCKET
S3_SECRET_ACCESS_KEY — (required) Secret key for S3_BUCKET
DOMAIN dex-binder.localhost Hostname Traefik routes to this app

Image storage

Card images are stored in an S3-compatible bucket rather than on disk, so the app container stays stateless (aside from the SQLite DB) and images survive redeploys/rebuilds. Point S3_ENDPOINT/S3_BUCKET/S3_ACCESS_KEY_ID/S3_SECRET_ACCESS_KEY at any S3-compatible store reachable from the app container.

Access control (mTLS)

Instead of Basic Auth, the router requires a valid client certificate signed by a shared personal CA — no password prompt, and no per-app re-auth on installed iOS PWAs (which otherwise lose the Basic Auth cache on every relaunch). The CA, per-device certificate issuing scripts, and the actual TLS Options definition (with clientAuth) live in the separate mtls-ca repo, since this is shared across apps. Docker labels can only reference a tls.options entry, not define one with clientAuth settings — that requires Traefik's file provider — so this repo's router label points at it via the @file suffix: traefik.http.routers.dex-binder.tls.options=dex-binder-mtls@file. Any device certificate issued from that CA works for both dex-binder and haushaltsbuch — no need to issue a separate certificate per app.

CI

Forgejo Actions workflow at .forgejo/workflows/ci.yaml runs on every push to main and every PR:

  1. buildnpm ci, npx prisma generate, npm run lint, npm run build.
  2. dockerdocker build ., so a broken image build fails the workflow.
  3. deploy — only on pushes to main, after the above pass: POSTs to a webhook (secrets.DEPLOY_WEBHOOK_URL) so Portainer re-pulls this repo and rebuilds/redeploys the stack.