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

216 lines
No EOL
9.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`](https://github.com/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**
```bash
git clone https://github.com/stwl-labs/deckhearth.git
cd deckhearth
```
2. **Install dependencies**
```bash
npm install
```
3. **Create `.env.local`** (there is no committed template — use the shape below)
```env
# 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 **required**`lib/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**
```bash
npm run setup-db
```
This applies every pending migration under `migrations/` (via
[`node-pg-migrate`](https://github.com/salsita/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**
```bash
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).
```bash
# 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.local`
`npm 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`](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