deckhearth/README.md
Randall Stillwell f2ba333daf docs: sweep README + AGENTS.md to post-homelab reality
- README: Dokploy/CT102 Postgres stack, lib/sql.js, six rate-limit
  classes over REDIS_URL, Playwright smoke+visual, MinIO/S3 vars,
  inline .env.local contract (no .env.example exists), deckhearth.git
  clone URL, project-structure refresh.
- AGENTS.md: infra banner for the Vercel/Neon -> homelab move, repo
  rename to stwl-labs/deckhearth, Data/Auth/Hosting overview bullets,
  DB-access convention re-pointed at lib/sql.js, Gotcha #12 rewritten
  for REDIS_URL + scan limiter (+ row in limiter table), test counts
  refreshed (231/231 across 43 files), §5 env contract, §7 marked
  legacy-pending-decommission with DOKPLOY_DEPLOY runbook pointer.
2026-08-23 22:34:54 -05:00

9.4 KiB
Raw Permalink Blame History

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