TCG Vault - Trading Card Game Collection Management with OCR Scanning
Find a file
rstillwell 8a5ec11573
Some checks are pending
CI / Lint (push) Waiting to run
CI / Schema map up to date (push) Waiting to run
CI / Forbidden patterns (9 checks) (push) Waiting to run
CI / Migrations apply (node-pg-migrate) (push) Waiting to run
CI / Unit tests (vitest) (push) Waiting to run
Merge pull request 'perf(scanner): three high-impact speed optimizations' (#156) from perf/scanner-speed-optimizations into main
2026-09-01 18:18:13 -04:00
.convoys fix(tests): wire DEFAULT_LAYOUT export, fix custom-frames mocks, and add validateLayout tests 2026-09-01 09:40:04 -05:00
.cursor chore(agent-pipeline): sync 0.6.0/0.7.0 artifacts (#155) 2026-08-14 18:56:01 -05:00
.github Migrate Deck Hearth off Vercel/Neon to homelab Dokploy stack. 2026-08-15 09:32:13 -05:00
components feat(designer): image-based frame zones, ZoneEditor, custom frame/game APIs 2026-09-01 09:23:16 -05:00
docs feat(scanner): add debug instrumentation for vision pipeline timing 2026-09-01 17:02:01 -05:00
lib perf(scanner): three high-impact speed optimizations 2026-09-01 17:17:20 -05:00
migrations feat(designer): image-based frame zones, ZoneEditor, custom frame/game APIs 2026-09-01 09:23:16 -05:00
pages feat(designer): image-based frame zones, ZoneEditor, custom frame/game APIs 2026-09-01 09:23:16 -05:00
public Major redesign: Enhanced card display with particle effects, improved filters, and search functionality 2025-07-23 21:26:54 -05:00
scripts Migrate Deck Hearth off Vercel/Neon to homelab Dokploy stack. 2026-08-15 09:32:13 -05:00
styles refactor(glass): migrate Button.secondary + Input + MobileNav off bespoke glass-surface (#131) 2026-06-05 06:23:55 -05:00
test fix(tests): wire DEFAULT_LAYOUT export, fix custom-frames mocks, and add validateLayout tests 2026-09-01 09:40:04 -05:00
tests convoy: flip visual-diff to a hard merge gate (harden-visual-diff-gate brief 2/2) (#140) 2026-06-12 22:03:17 -05:00
.agent-context-manifest.yml chore(agent-pipeline): sync 0.6.0/0.7.0 artifacts (#155) 2026-08-14 18:56:01 -05:00
.dockerignore Migrate Deck Hearth off Vercel/Neon to homelab Dokploy stack. 2026-08-15 09:32:13 -05:00
.gitignore feat(convoy-metrics): un-gitignore .metrics.jsonl + add CI gate on convoy PRs (#134) 2026-06-12 17:46:28 -05:00
.npmrc Fix build compatibility issues 2025-07-22 10:32:15 -05:00
AGENTS.md fix(tests): wire DEFAULT_LAYOUT export, fix custom-frames mocks, and add validateLayout tests 2026-09-01 09:40:04 -05:00
Dockerfile Migrate Deck Hearth off Vercel/Neon to homelab Dokploy stack. 2026-08-15 09:32:13 -05:00
eslint.config.mjs convoy: enable no-undef ESLint rule + fix 3 latent bugs it surfaced 2026-06-13 01:17:18 -05:00
next.config.js Migrate Deck Hearth off Vercel/Neon to homelab Dokploy stack. 2026-08-15 09:32:13 -05:00
package-lock.json feat(designer): add card designer with starter frames, live preview, and PNG export 2026-08-24 14:53:06 -05:00
package.json feat(designer): add card designer with starter frames, live preview, and PNG export 2026-08-24 14:53:06 -05:00
playwright.config.js Migrate Deck Hearth off Vercel/Neon to homelab Dokploy stack. 2026-08-15 09:32:13 -05:00
postcss.config.js fix(lint): clear lib/config baseline and make CI lint blocking. (#63) 2026-06-02 01:03:42 -05:00
README.md docs: sweep README + AGENTS.md to post-homelab reality 2026-08-23 22:34:54 -05:00
tailwind.config.js fix(lint): clear lib/config baseline and make CI lint blocking. (#63) 2026-06-02 01:03:42 -05:00
TESTING_GUIDE.md fix(scripts): require TEST_USERS_PASSWORD + purge weak literals from test-user helpers (#27) 2026-05-26 22:53:24 -05:00
vitest.config.js fix(layout+pages): default user=null + page audit sweep (P0 #7) (#15) 2026-05-24 14:31:37 -05:00

Deck Hearth

A modern trading card game collection manager built with Next.js and PostgreSQL.

Repo history note: this project was formerly TCG Vault (tcg-vault); the GitHub repo is now stwl-labs/deckhearth. Local checkout folders named tcg-vault are fine.

🚀 Features

  • Card Management: Track your MTG, Pokémon, and Lorcana cards
  • Collection Organization: Create and manage card collections
  • Deck Building: Build and share decks
  • Authentication: Secure user accounts with JWT
  • Admin Panel: Manage cards and users
  • Real-time Pricing: Track card values

🛠️ Tech Stack

  • Frontend: Next.js 16 (Pages router), React 18, JavaScript (TypeScript is a devDep only — see AGENTS.md Gotcha #9)
  • Backend: Next.js API Routes
  • Database: PostgreSQL 17 + pgvector on the axiom homelab (CT 102, 192.168.68.102:5432), accessed via the tagged-template helper in lib/sql.js — see docs/HOMELAB_DATABASE.md
  • Authentication: JWT with bcrypt (24-hour expiry; lib/auth-secret.js is the single source of truth for JWT_SECRET)
  • Rate limiting: lib/rate-limit.js — six named limiters (auth, search, upload, generate, import, scan) backed by ioredis + rate-limiter-flexible against homelab Redis (CT 102). Fails closed in production if REDIS_URL is unset; warn-and-no-op in dev
  • Testing: Vitest (unit) + Playwright (smoke and visual projects)
  • Object storage: MinIO on CT 102 (scan captures; S3-compatible via @aws-sdk/client-s3)
  • Styling: Tailwind CSS + Liquid Glass design tokens (docs/DESIGN_TOKENS.md)
  • Deployment: Dokploy on CT 112, public URL https://deckhearth.stillwell.cloud via Traefik — see docs/DOKPLOY_DEPLOY.md

📦 Installation

  1. Clone the repository

    git clone https://github.com/stwl-labs/deckhearth.git
    cd deckhearth
    
  2. Install dependencies

    npm install
    
  3. Create .env.local (there is no committed template — use the shape below)

    # Required — homelab Postgres (CT 102) or any Postgres 17 instance
    POSTGRES_URL="postgresql://deckhearth:<password>@192.168.68.102:5432/deckhearth"
    # Migrations (`npm run migrate`); on the homelab, same value as POSTGRES_URL
    POSTGRES_URL_DIRECT="postgresql://deckhearth:<password>@192.168.68.102:5432/deckhearth"
    JWT_SECRET="<generate with: openssl rand -hex 32>"
    # Required for `npm run setup-db` — used once to hash the initial admin password.
    ADMIN_INITIAL_PASSWORD="<generate with: openssl rand -base64 24>"
    # Rate limiting (homelab Redis, CT 102 — DB index 5 in prod). Optional in dev —
    # without it, `lib/rate-limit.js` warn-and-no-ops locally; production fails closed.
    REDIS_URL="redis://:password@192.168.68.102:6379/5"
    # Scan-capture uploads (MinIO on CT 102, S3-compatible — see lib/object-storage.js)
    S3_ENDPOINT="http://192.168.68.102:9000"
    S3_BUCKET="deckhearth"
    S3_ACCESS_KEY_ID="..."
    S3_SECRET_ACCESS_KEY="..."
    S3_PUBLIC_BASE_URL="https://cdn.stillwell.cloud/deckhearth"
    S3_REGION="us-east-1"
    

    JWT_SECRET is requiredlib/auth-secret.js throws at import time if it's unset. ADMIN_INITIAL_PASSWORD is required for npm run setup-db — the script exits with code 1 if it's unset.

    Migrating data off the old Neon instance? NEON_DATABASE_URL is read once by npm run migrate-neon-to-homelab. See docs/HOMELAB_DATABASE.md.

  4. Set up the database

    npm run setup-db
    

    This applies every pending migration under migrations/ (via node-pg-migrate) and then seeds the initial admin user. To run just the migration step without seeding, use npm run migrate up.

  5. Start development server

    npm run dev
    

🧱 Schema changes (post-migration-tool convoy)

Schema is now managed by node-pg-migrate. New migrations live under migrations/ at the repo root (the legacy scripts/migrations/ placeholder is preserved for the one pre-existing dated script and is not used going forward).

# 1. Generate a new migration file (JS template, timestamp-prefixed)
npm run migrate create add-foo-column -- -j js

# 2. Edit the generated file under migrations/<timestamp>_add-foo-column.js
#    Put your DDL in up(); write a real down() if rollback is safe.

# 3. Apply it locally (POSTGRES_URL from .env.local)
npm run migrate up

# 4. Commit the migration file + any docs/SCHEMA_MAP.md updates together.

Onboarding a new env is now exactly npm install → seed .env.localnpm run setup-db (which chains npm run migrate up and then seeds the admin user).

Do not edit historical scripts/add-*.js / scripts/fix-*.js / scripts/seed-*.js — those are already-run, append-only jobs. They are preserved for the audit trail; new column / constraint work goes through a migration file instead.

🗄️ Database Schema

The application uses the following tables:

  • users - User accounts and authentication
  • cards - Card information and metadata
  • user_cards - User's card collections
  • collections - Named card collections
  • collection_cards - Cards in collections
  • decks - Deck definitions
  • deck_cards - Cards in decks

🔧 API Endpoints

Authentication

  • POST /api/auth/register - User registration
  • POST /api/auth/login - User login

Admin

  • GET /api/admin - Admin panel data

Health Check

  • GET /api/health - Application health

Note: Earlier revisions of this README also listed GET /api/test-db (and three other unauthenticated dev endpoints: /api/simple, /api/test-auth, /api/setup-database). All four were deleted in fix-auth-bypass Brief 3 (commit fc0dd73) and CI now blocks their reintroduction. Don't recreate them.

🚀 Deployment

Deck Hearth deploys to Dokploy on CT 112 in the axiom homelab, fronted by Traefik on CT 100 at https://deckhearth.stillwell.cloud. The full runbook — Dokploy app settings, environment variables, Traefik route, and the n8n catalog-sync cron that replaced Vercel Cron — lives in docs/DOKPLOY_DEPLOY.md.

Transition note: the Vercel + Neon era is being decommissioned (migrate-neon-to-homelab convoy, phases 68). vercel.json and .vercel/ remain in the tree until that decommission lands; CI already gates against the homelab deployment.

📁 Project Structure

deckhearth/ (formerly tcg-vault)
├── pages/                 # Next.js pages and API routes
│   ├── api/              # API endpoints
│   │   ├── auth/         # Authentication routes
│   │   └── admin/        # Admin routes
│   ├── _app.js           # App wrapper
│   └── index.js          # Home page
├── components/            # React components (+ scanner/ subfamily)
├── lib/                   # Utility libraries
│   └── sql.js            # Canonical Postgres client (tagged templates)
├── migrations/            # node-pg-migrate migrations (source of truth for DDL)
├── scripts/               # Setup, import, and historical one-off jobs
├── docs/                  # Deploy / DB / design-token runbooks
├── test/ + tests/         # Vitest unit specs; Playwright smoke + visual specs
└── .env.local             # Environment variables (not committed)

🔐 First-time admin setup

npm run setup-db first applies every pending migration (via npm run migrate up), then creates a single admin user. The password is read from the ADMIN_INITIAL_PASSWORD environment variable; the script exits with code 1 (and does not open a database connection) if the variable is unset or empty.

  • Local dev: set ADMIN_INITIAL_PASSWORD in .env.local before running npm run setup-db. Use openssl rand -base64 24 (or any other strong source) to generate the value.
  • CI: set ADMIN_INITIAL_PASSWORD as a repo secret if setup ever runs from CI. The env var is only read by the seed script; runtime auth uses the per-user password stored in the database.
  • Admin email: the seed creates admin@deckhearth.com. Change the password immediately after first login via the app's profile settings.

Operators of envs that pre-date this change: npm run setup-db is idempotent (ON CONFLICT (email) DO NOTHING) — re-running it with ADMIN_INITIAL_PASSWORD set will not rotate an existing admin row's password. If your environment was set up before this change and still has the prior weak default, rotate the password manually via the app after logging in, or wait for the queued rotate-default-admin follow-up convoy.

Operators of envs that pre-date the pick-a-name convoy (2026-05-24): the admin row was renamed from admin@tcgvault.com to admin@deckhearth.com. Run node scripts/migrations/2026-05-24-rename-admin-email.js once after deploy to UPDATE any existing @tcgvault.com user rows (the admin row, plus alice/bob if npm run create-test-users was ever run). Re-running the migration after the first run is idempotent and prints "Nothing to migrate." Verify post-migration with psql $POSTGRES_URL -c "SELECT email FROM users WHERE email LIKE '%@tcgvault.com'" — expect zero rows.

🤝 Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Submit a pull request

📄 License

MIT License - see LICENSE file for details