feat(brand): unify on Deck Hearth across in-repo strings + infra (P1 brand decision)
Resolves the launch-blocking 'TCG Vault vs Deck Hearth' inconsistency called out in AGENTS.md line 5 since project setup. Operator gate-0 decision: Deck Hearth wins. Two briefs applied serially. B1 (mechanical): 7-file display + comment sweep. B2 (infrastructure): Redis prefix rename in lib/rate-limit.js (5 prefixes, accept one-time counter reset), package.json + lockfile regen (STOP-on-churn confirmed only name lines changed), admin/alice/bob email rename in seed scripts + login pre-fill + NEW idempotent migration script scripts/migrations/2026-05-24-rename-admin-email.js. Risk 4 PRESERVE applied: test/lib/permission-middleware.test.js retains admin@tcgvault.com literal with 7-line architect-authored why comment (documents pre-fix-auth-bypass bug shape; preserves historical truth per project's gotcha-documentation convention). All 5 D-decisions ratified at gate-1 (Deck Hearth / deck-hearth / deckhearth / admin@deckhearth.com / full deckhearth Redis prefix). Local: lint 128 baseline (B1 + B2), vitest 21/21 (B1 + B2). CI all green: Playwright smoke 3/3 against rebranded preview in 1m4s, forbidden-cors-headers pass, forbidden-endpoints pass, Screenshot diff pass, Vercel deployment complete. Cross-validation lineage: 4th convoy where the same 3-test smoke spec defends auth surface through sweeping change (after PR #15 Layout default-user, PR #19 CORS, PR #20 rate-limit, now this PR #21 brand rename). OPERATOR POST-MERGE ACTION REQUIRED: run 'node scripts/migrations/2026-05-24-rename-admin-email.js' against prod Neon DB before next admin login (ordering: migration FIRST, then any subsequent setup-db invocation). Migration is ESM, idempotent, UNIQUE-collision-safe. PR #21 architect-commit 50ce9ab, B1 ac8c998, B2 1c18d21.
2026-05-25 03:28:29 -04:00
# Deck Hearth
2025-07-21 15:04:48 -04:00
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 23:34:54 -04:00
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.
2025-07-21 15:04:48 -04:00
2025-07-23 22:26:54 -04:00
## 🚀 Features
2025-07-21 15:04:48 -04:00
2025-07-23 22:26:54 -04:00
- **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
2025-07-21 15:04:48 -04:00
2025-07-23 22:26:54 -04:00
## 🛠️ Tech Stack
2025-07-21 15:04:48 -04:00
docs: post-convoy cleanup for fix-auth-bypass
Closes out the fix-auth-bypass convoy (PRs #6–#11, merged through
1629afb) on the docs side. Code already on main; this PR is docs only.
Updates:
AGENTS.md
- §1 auth bullet refreshed (auth-secret SoT, 24h TTL, no synthetic
admin, login/register rate limit)
- §3 conventions point at lib/auth-secret.js + lib/rate-limit.js
- §4 gotchas #2/#3/#5 converted to "Resolved" notes in place
(NOT renumbered, to preserve cross-references)
- new #12 documents the KV_REST_API_* env-var convention
- §5 setup list adds the rate-limit env vars
- §6 testing rewritten for Vitest (16 unit tests, blocking CI gate)
.cursor/rules/auth-and-permissions.mdc
- canonical-surface table gains lib/auth-secret.js + lib/rate-limit.js
- token model now 24h (was 7d) with fail-loud explanation
- server-side authorization patterns lead with null → 401 contract
.cursor/rules/api-routes.mdc
- removes the "CRITICAL — known bug" callout (resolved by Brief 2)
- adds a "Rate limiting" section with verbatim shape + env-var notes
- "Dev/test endpoints" → "Removed" historical note so future agents
searching for test-db understand why it's gone
.convoys/fix-auth-bypass.md (restored — was on convoy branch only)
- frontmatter → status: shipped
- new "Convoy outcome" section: briefs + commits + resolved gotchas,
R1-R12 risk walk, env-var-rename deviation record, queued follow-up
convoys, lessons learned
.convoys/fix-auth-bypass/brief-{1..5}-*.md (restored from convoy branch)
- audit-trail completeness; convoy plan references them by name
- brief 4 additionally updated: UPSTASH_REDIS_REST_* → KV_REST_API_*
across init rules, smoke, pre-deploy checklist
- brief 4 has a new "Post-merge addendum" explaining the rename
.convoys/ship-readiness.md
- P0 #1, #2, #4 → RESOLVED with merge-commit citations
- P0 #5 (CORS), #6 (rate limit) → PARTIAL with deferral pointers
(cors-tighten and add-rate-limiting convoys)
- each item gains an "As-shipped" line for self-containment
README.md
- Next.js 15 → 16, TypeScript claim corrected to JS-with-devDep
- auth + rate-limit + testing bullets updated
- env-var template extended with KV_REST_API_*
- deleted dev-endpoints note added to the API list
- "Default Admin Account" section LEFT ALONE — drop-public-setup territory
Verified: build exit 0 (with JWT_SECRET set), 16/16 vitest tests pass,
lint baseline unchanged (128/81/47).
Convoy: fix-auth-bypass / role-doc-writer (closeout)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 12:27:48 -04:00
- **Frontend**: Next.js 16 (Pages router), React 18, JavaScript (TypeScript is a devDep only — see `AGENTS.md` Gotcha #9 )
2025-07-23 22:26:54 -04:00
- **Backend**: Next.js API Routes
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 23:34:54 -04:00
- **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`
docs: post-convoy cleanup for fix-auth-bypass
Closes out the fix-auth-bypass convoy (PRs #6–#11, merged through
1629afb) on the docs side. Code already on main; this PR is docs only.
Updates:
AGENTS.md
- §1 auth bullet refreshed (auth-secret SoT, 24h TTL, no synthetic
admin, login/register rate limit)
- §3 conventions point at lib/auth-secret.js + lib/rate-limit.js
- §4 gotchas #2/#3/#5 converted to "Resolved" notes in place
(NOT renumbered, to preserve cross-references)
- new #12 documents the KV_REST_API_* env-var convention
- §5 setup list adds the rate-limit env vars
- §6 testing rewritten for Vitest (16 unit tests, blocking CI gate)
.cursor/rules/auth-and-permissions.mdc
- canonical-surface table gains lib/auth-secret.js + lib/rate-limit.js
- token model now 24h (was 7d) with fail-loud explanation
- server-side authorization patterns lead with null → 401 contract
.cursor/rules/api-routes.mdc
- removes the "CRITICAL — known bug" callout (resolved by Brief 2)
- adds a "Rate limiting" section with verbatim shape + env-var notes
- "Dev/test endpoints" → "Removed" historical note so future agents
searching for test-db understand why it's gone
.convoys/fix-auth-bypass.md (restored — was on convoy branch only)
- frontmatter → status: shipped
- new "Convoy outcome" section: briefs + commits + resolved gotchas,
R1-R12 risk walk, env-var-rename deviation record, queued follow-up
convoys, lessons learned
.convoys/fix-auth-bypass/brief-{1..5}-*.md (restored from convoy branch)
- audit-trail completeness; convoy plan references them by name
- brief 4 additionally updated: UPSTASH_REDIS_REST_* → KV_REST_API_*
across init rules, smoke, pre-deploy checklist
- brief 4 has a new "Post-merge addendum" explaining the rename
.convoys/ship-readiness.md
- P0 #1, #2, #4 → RESOLVED with merge-commit citations
- P0 #5 (CORS), #6 (rate limit) → PARTIAL with deferral pointers
(cors-tighten and add-rate-limiting convoys)
- each item gains an "As-shipped" line for self-containment
README.md
- Next.js 15 → 16, TypeScript claim corrected to JS-with-devDep
- auth + rate-limit + testing bullets updated
- env-var template extended with KV_REST_API_*
- deleted dev-endpoints note added to the API list
- "Default Admin Account" section LEFT ALONE — drop-public-setup territory
Verified: build exit 0 (with JWT_SECRET set), 16/16 vitest tests pass,
lint baseline unchanged (128/81/47).
Convoy: fix-auth-bypass / role-doc-writer (closeout)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 12:27:48 -04:00
- **Authentication**: JWT with bcrypt (24-hour expiry; `lib/auth-secret.js` is the single source of truth for `JWT_SECRET` )
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 23:34:54 -04:00
- **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`
2025-07-21 15:04:48 -04:00
2025-07-23 22:26:54 -04:00
## 📦 Installation
2025-07-21 15:04:48 -04:00
2025-07-23 22:26:54 -04:00
1. **Clone the repository**
```bash
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 23:34:54 -04:00
git clone https://github.com/stwl-labs/deckhearth.git
cd deckhearth
2025-07-23 22:26:54 -04:00
```
2025-07-21 15:04:48 -04:00
2025-07-23 22:26:54 -04:00
2. **Install dependencies**
```bash
npm install
```
2025-07-21 15:04:48 -04:00
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 23:34:54 -04:00
3. **Create `.env.local`** (there is no committed template — use the shape below)
2025-07-23 22:26:54 -04:00
```env
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 23:34:54 -04:00
# 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"
docs: post-convoy cleanup for fix-auth-bypass
Closes out the fix-auth-bypass convoy (PRs #6–#11, merged through
1629afb) on the docs side. Code already on main; this PR is docs only.
Updates:
AGENTS.md
- §1 auth bullet refreshed (auth-secret SoT, 24h TTL, no synthetic
admin, login/register rate limit)
- §3 conventions point at lib/auth-secret.js + lib/rate-limit.js
- §4 gotchas #2/#3/#5 converted to "Resolved" notes in place
(NOT renumbered, to preserve cross-references)
- new #12 documents the KV_REST_API_* env-var convention
- §5 setup list adds the rate-limit env vars
- §6 testing rewritten for Vitest (16 unit tests, blocking CI gate)
.cursor/rules/auth-and-permissions.mdc
- canonical-surface table gains lib/auth-secret.js + lib/rate-limit.js
- token model now 24h (was 7d) with fail-loud explanation
- server-side authorization patterns lead with null → 401 contract
.cursor/rules/api-routes.mdc
- removes the "CRITICAL — known bug" callout (resolved by Brief 2)
- adds a "Rate limiting" section with verbatim shape + env-var notes
- "Dev/test endpoints" → "Removed" historical note so future agents
searching for test-db understand why it's gone
.convoys/fix-auth-bypass.md (restored — was on convoy branch only)
- frontmatter → status: shipped
- new "Convoy outcome" section: briefs + commits + resolved gotchas,
R1-R12 risk walk, env-var-rename deviation record, queued follow-up
convoys, lessons learned
.convoys/fix-auth-bypass/brief-{1..5}-*.md (restored from convoy branch)
- audit-trail completeness; convoy plan references them by name
- brief 4 additionally updated: UPSTASH_REDIS_REST_* → KV_REST_API_*
across init rules, smoke, pre-deploy checklist
- brief 4 has a new "Post-merge addendum" explaining the rename
.convoys/ship-readiness.md
- P0 #1, #2, #4 → RESOLVED with merge-commit citations
- P0 #5 (CORS), #6 (rate limit) → PARTIAL with deferral pointers
(cors-tighten and add-rate-limiting convoys)
- each item gains an "As-shipped" line for self-containment
README.md
- Next.js 15 → 16, TypeScript claim corrected to JS-with-devDep
- auth + rate-limit + testing bullets updated
- env-var template extended with KV_REST_API_*
- deleted dev-endpoints note added to the API list
- "Default Admin Account" section LEFT ALONE — drop-public-setup territory
Verified: build exit 0 (with JWT_SECRET set), 16/16 vitest tests pass,
lint baseline unchanged (128/81/47).
Convoy: fix-auth-bypass / role-doc-writer (closeout)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 12:27:48 -04:00
JWT_SECRET="< generate with: openssl rand -hex 32 > "
2026-05-23 15:57:31 -04:00
# Required for `npm run setup-db` — used once to hash the initial admin password.
ADMIN_INITIAL_PASSWORD="< generate with: openssl rand -base64 24 > "
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 23:34:54 -04:00
# 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"
2025-07-21 15:04:48 -04:00
```
docs: post-convoy cleanup for fix-auth-bypass
Closes out the fix-auth-bypass convoy (PRs #6–#11, merged through
1629afb) on the docs side. Code already on main; this PR is docs only.
Updates:
AGENTS.md
- §1 auth bullet refreshed (auth-secret SoT, 24h TTL, no synthetic
admin, login/register rate limit)
- §3 conventions point at lib/auth-secret.js + lib/rate-limit.js
- §4 gotchas #2/#3/#5 converted to "Resolved" notes in place
(NOT renumbered, to preserve cross-references)
- new #12 documents the KV_REST_API_* env-var convention
- §5 setup list adds the rate-limit env vars
- §6 testing rewritten for Vitest (16 unit tests, blocking CI gate)
.cursor/rules/auth-and-permissions.mdc
- canonical-surface table gains lib/auth-secret.js + lib/rate-limit.js
- token model now 24h (was 7d) with fail-loud explanation
- server-side authorization patterns lead with null → 401 contract
.cursor/rules/api-routes.mdc
- removes the "CRITICAL — known bug" callout (resolved by Brief 2)
- adds a "Rate limiting" section with verbatim shape + env-var notes
- "Dev/test endpoints" → "Removed" historical note so future agents
searching for test-db understand why it's gone
.convoys/fix-auth-bypass.md (restored — was on convoy branch only)
- frontmatter → status: shipped
- new "Convoy outcome" section: briefs + commits + resolved gotchas,
R1-R12 risk walk, env-var-rename deviation record, queued follow-up
convoys, lessons learned
.convoys/fix-auth-bypass/brief-{1..5}-*.md (restored from convoy branch)
- audit-trail completeness; convoy plan references them by name
- brief 4 additionally updated: UPSTASH_REDIS_REST_* → KV_REST_API_*
across init rules, smoke, pre-deploy checklist
- brief 4 has a new "Post-merge addendum" explaining the rename
.convoys/ship-readiness.md
- P0 #1, #2, #4 → RESOLVED with merge-commit citations
- P0 #5 (CORS), #6 (rate limit) → PARTIAL with deferral pointers
(cors-tighten and add-rate-limiting convoys)
- each item gains an "As-shipped" line for self-containment
README.md
- Next.js 15 → 16, TypeScript claim corrected to JS-with-devDep
- auth + rate-limit + testing bullets updated
- env-var template extended with KV_REST_API_*
- deleted dev-endpoints note added to the API list
- "Default Admin Account" section LEFT ALONE — drop-public-setup territory
Verified: build exit 0 (with JWT_SECRET set), 16/16 vitest tests pass,
lint baseline unchanged (128/81/47).
Convoy: fix-auth-bypass / role-doc-writer (closeout)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 12:27:48 -04:00
`JWT_SECRET` is **required** — `lib/auth-secret.js` throws at import time if it's unset.
2026-05-23 15:57:31 -04:00
`ADMIN_INITIAL_PASSWORD` is **required** for `npm run setup-db` — the script exits with code 1 if it's unset.
2025-07-21 15:04:48 -04:00
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 23:34:54 -04:00
> 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`.
2025-07-23 22:26:54 -04:00
4. **Set up the database**
2025-07-21 15:04:48 -04:00
```bash
2025-07-23 22:26:54 -04:00
npm run setup-db
2025-07-21 15:04:48 -04:00
```
2026-05-27 00:01:58 -04:00
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` .
2025-07-23 22:26:54 -04:00
5. **Start development server**
2025-07-21 15:04:48 -04:00
```bash
2025-07-23 22:26:54 -04:00
npm run dev
2025-07-21 15:04:48 -04:00
```
2026-05-27 00:01:58 -04:00
## 🧱 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.
2025-07-23 22:26:54 -04:00
## 🗄️ 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
docs: post-convoy cleanup for fix-auth-bypass
Closes out the fix-auth-bypass convoy (PRs #6–#11, merged through
1629afb) on the docs side. Code already on main; this PR is docs only.
Updates:
AGENTS.md
- §1 auth bullet refreshed (auth-secret SoT, 24h TTL, no synthetic
admin, login/register rate limit)
- §3 conventions point at lib/auth-secret.js + lib/rate-limit.js
- §4 gotchas #2/#3/#5 converted to "Resolved" notes in place
(NOT renumbered, to preserve cross-references)
- new #12 documents the KV_REST_API_* env-var convention
- §5 setup list adds the rate-limit env vars
- §6 testing rewritten for Vitest (16 unit tests, blocking CI gate)
.cursor/rules/auth-and-permissions.mdc
- canonical-surface table gains lib/auth-secret.js + lib/rate-limit.js
- token model now 24h (was 7d) with fail-loud explanation
- server-side authorization patterns lead with null → 401 contract
.cursor/rules/api-routes.mdc
- removes the "CRITICAL — known bug" callout (resolved by Brief 2)
- adds a "Rate limiting" section with verbatim shape + env-var notes
- "Dev/test endpoints" → "Removed" historical note so future agents
searching for test-db understand why it's gone
.convoys/fix-auth-bypass.md (restored — was on convoy branch only)
- frontmatter → status: shipped
- new "Convoy outcome" section: briefs + commits + resolved gotchas,
R1-R12 risk walk, env-var-rename deviation record, queued follow-up
convoys, lessons learned
.convoys/fix-auth-bypass/brief-{1..5}-*.md (restored from convoy branch)
- audit-trail completeness; convoy plan references them by name
- brief 4 additionally updated: UPSTASH_REDIS_REST_* → KV_REST_API_*
across init rules, smoke, pre-deploy checklist
- brief 4 has a new "Post-merge addendum" explaining the rename
.convoys/ship-readiness.md
- P0 #1, #2, #4 → RESOLVED with merge-commit citations
- P0 #5 (CORS), #6 (rate limit) → PARTIAL with deferral pointers
(cors-tighten and add-rate-limiting convoys)
- each item gains an "As-shipped" line for self-containment
README.md
- Next.js 15 → 16, TypeScript claim corrected to JS-with-devDep
- auth + rate-limit + testing bullets updated
- env-var template extended with KV_REST_API_*
- deleted dev-endpoints note added to the API list
- "Default Admin Account" section LEFT ALONE — drop-public-setup territory
Verified: build exit 0 (with JWT_SECRET set), 16/16 vitest tests pass,
lint baseline unchanged (128/81/47).
Convoy: fix-auth-bypass / role-doc-writer (closeout)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 12:27:48 -04:00
> **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.
2025-07-23 22:26:54 -04:00
## 🚀 Deployment
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 23:34:54 -04:00
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 ).
2025-07-23 22:26:54 -04:00
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 23:34:54 -04:00
> **Transition note:** the Vercel + Neon era is being decommissioned
> (`migrate-neon-to-homelab` convoy, phases 6– 8). `vercel.json` and `.vercel/`
> remain in the tree until that decommission lands; CI already gates against
> the homelab deployment.
2025-07-23 22:26:54 -04:00
## 📁 Project Structure
2025-07-21 15:04:48 -04:00
```
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 23:34:54 -04:00
deckhearth/ (formerly tcg-vault)
2025-07-23 22:26:54 -04:00
├── pages/ # Next.js pages and API routes
│ ├── api/ # API endpoints
│ │ ├── auth/ # Authentication routes
│ │ └── admin/ # Admin routes
│ ├── _app.js # App wrapper
│ └── index.js # Home page
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 23:34:54 -04:00
├── 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)
2025-07-21 15:04:48 -04:00
```
2026-05-23 15:57:31 -04:00
## 🔐 First-time admin setup
2026-05-27 00:01:58 -04:00
`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
2026-05-23 15:57:31 -04:00
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.
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 23:34:54 -04:00
- **CI:** set `ADMIN_INITIAL_PASSWORD` as a repo secret if setup
2026-05-23 15:57:31 -04:00
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.
feat(brand): unify on Deck Hearth across in-repo strings + infra (P1 brand decision)
Resolves the launch-blocking 'TCG Vault vs Deck Hearth' inconsistency called out in AGENTS.md line 5 since project setup. Operator gate-0 decision: Deck Hearth wins. Two briefs applied serially. B1 (mechanical): 7-file display + comment sweep. B2 (infrastructure): Redis prefix rename in lib/rate-limit.js (5 prefixes, accept one-time counter reset), package.json + lockfile regen (STOP-on-churn confirmed only name lines changed), admin/alice/bob email rename in seed scripts + login pre-fill + NEW idempotent migration script scripts/migrations/2026-05-24-rename-admin-email.js. Risk 4 PRESERVE applied: test/lib/permission-middleware.test.js retains admin@tcgvault.com literal with 7-line architect-authored why comment (documents pre-fix-auth-bypass bug shape; preserves historical truth per project's gotcha-documentation convention). All 5 D-decisions ratified at gate-1 (Deck Hearth / deck-hearth / deckhearth / admin@deckhearth.com / full deckhearth Redis prefix). Local: lint 128 baseline (B1 + B2), vitest 21/21 (B1 + B2). CI all green: Playwright smoke 3/3 against rebranded preview in 1m4s, forbidden-cors-headers pass, forbidden-endpoints pass, Screenshot diff pass, Vercel deployment complete. Cross-validation lineage: 4th convoy where the same 3-test smoke spec defends auth surface through sweeping change (after PR #15 Layout default-user, PR #19 CORS, PR #20 rate-limit, now this PR #21 brand rename). OPERATOR POST-MERGE ACTION REQUIRED: run 'node scripts/migrations/2026-05-24-rename-admin-email.js' against prod Neon DB before next admin login (ordering: migration FIRST, then any subsequent setup-db invocation). Migration is ESM, idempotent, UNIQUE-collision-safe. PR #21 architect-commit 50ce9ab, B1 ac8c998, B2 1c18d21.
2026-05-25 03:28:29 -04:00
- **Admin email:** the seed creates `admin@deckhearth.com` . Change the password
2026-05-23 15:57:31 -04:00
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.
2025-07-21 15:04:48 -04:00
feat(brand): unify on Deck Hearth across in-repo strings + infra (P1 brand decision)
Resolves the launch-blocking 'TCG Vault vs Deck Hearth' inconsistency called out in AGENTS.md line 5 since project setup. Operator gate-0 decision: Deck Hearth wins. Two briefs applied serially. B1 (mechanical): 7-file display + comment sweep. B2 (infrastructure): Redis prefix rename in lib/rate-limit.js (5 prefixes, accept one-time counter reset), package.json + lockfile regen (STOP-on-churn confirmed only name lines changed), admin/alice/bob email rename in seed scripts + login pre-fill + NEW idempotent migration script scripts/migrations/2026-05-24-rename-admin-email.js. Risk 4 PRESERVE applied: test/lib/permission-middleware.test.js retains admin@tcgvault.com literal with 7-line architect-authored why comment (documents pre-fix-auth-bypass bug shape; preserves historical truth per project's gotcha-documentation convention). All 5 D-decisions ratified at gate-1 (Deck Hearth / deck-hearth / deckhearth / admin@deckhearth.com / full deckhearth Redis prefix). Local: lint 128 baseline (B1 + B2), vitest 21/21 (B1 + B2). CI all green: Playwright smoke 3/3 against rebranded preview in 1m4s, forbidden-cors-headers pass, forbidden-endpoints pass, Screenshot diff pass, Vercel deployment complete. Cross-validation lineage: 4th convoy where the same 3-test smoke spec defends auth surface through sweeping change (after PR #15 Layout default-user, PR #19 CORS, PR #20 rate-limit, now this PR #21 brand rename). OPERATOR POST-MERGE ACTION REQUIRED: run 'node scripts/migrations/2026-05-24-rename-admin-email.js' against prod Neon DB before next admin login (ordering: migration FIRST, then any subsequent setup-db invocation). Migration is ESM, idempotent, UNIQUE-collision-safe. PR #21 architect-commit 50ce9ab, B1 ac8c998, B2 1c18d21.
2026-05-25 03:28:29 -04:00
> **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.
2025-07-23 22:26:54 -04:00
## 🤝 Contributing
2025-07-21 15:04:48 -04:00
1. Fork the repository
2. Create a feature branch
3. Make your changes
2025-07-23 22:26:54 -04:00
4. Submit a pull request
2025-07-21 15:04:48 -04:00
2025-07-23 22:26:54 -04:00
## 📄 License
2025-07-21 15:04:48 -04:00
2025-07-23 22:26:54 -04:00
MIT License - see LICENSE file for details