Scaffolds the cors-tighten convoy. Originally queued as a
one-file follow-up to fix-auth-bypass Brief 4 (only
pages/api/auth/verify.js), but a fresh repo-wide grep at
convoy creation found 24 files carrying the identical
scaffolded wildcard-CORS + redundant-OPTIONS pattern. The
documented narrow scope (verify.js only) materially
understates the actual exposure.
Two scope options spec'd in the convoy file for architect
ratification at gate 1:
Option A — narrow (verify.js only, ~10 LOC). Matches the
queued-convoy entry literally. Queues a separate
cors-sweep-all-routes for the remaining 23.
Option B — expanded (all 24 files, ~240 LOC). Single
mechanical sweep, same precedent as Brief 4 applied to
login + register, closes P0 #5 fully in one PR.
Parent recommends Option B. Architect verifies scope safety
(spot-check 5+ of 24 for pattern drift), confirms or splits,
adds optional CI regression lock if B wins.
Four decisions queued (scope option, OPTIONS-handler shape,
verify.js method-gate tightening, test coverage). No
operator action required — no env vars, no secrets, no infra.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
||
|---|---|---|
| .convoys | ||
| .cursor | ||
| .github | ||
| components | ||
| docs | ||
| lib | ||
| pages | ||
| public | ||
| scripts | ||
| styles | ||
| test | ||
| tests | ||
| .agent-context-manifest.yml | ||
| .gitignore | ||
| .npmrc | ||
| AGENTS.md | ||
| eslint.config.mjs | ||
| next.config.js | ||
| package-lock.json | ||
| package.json | ||
| playwright.config.js | ||
| postcss.config.js | ||
| README.md | ||
| tailwind.config.js | ||
| TESTING_GUIDE.md | ||
| vercel.json | ||
| vitest.config.js | ||
TCG Vault
A modern trading card game collection manager built with Next.js and Neon Database.
🚀 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.mdGotcha #9) - Backend: Next.js API Routes
- Database: Neon PostgreSQL (serverless)
- Authentication: JWT with bcrypt (24-hour expiry;
lib/auth-secret.jsis the single source of truth forJWT_SECRET) - Rate limiting:
@upstash/ratelimiton/api/auth/login+/api/auth/register(5 attempts / 15 min per IP) - Testing: Vitest (unit); Playwright queued
- Styling: Tailwind CSS
- Deployment: Vercel
📦 Installation
-
Clone the repository
git clone <repository-url> cd tcg-vault -
Install dependencies
npm install -
Set up environment variables
cp .env.example .env.localUpdate
.env.localwith your Neon database URL and a real JWT secret:POSTGRES_URL="postgresql://your-username:your-password@your-host/your-database" JWT_SECRET="<generate with: openssl rand -hex 32>" # Required for `npm run setup-db` — used once to hash the initial admin password. # Set in .env.local for local dev, or as a CI secret if you run setup from CI. ADMIN_INITIAL_PASSWORD="<generate with: openssl rand -base64 24>" # Optional — exercise the rate limiter locally. Without them, `lib/rate-limit.js` # warn-and-no-ops in dev. In production these are auto-provisioned by the # Vercel Upstash Marketplace integration. KV_REST_API_URL="https://<your-upstash-host>.upstash.io" KV_REST_API_TOKEN="<your-upstash-rest-token>"JWT_SECRETis required —lib/auth-secret.jsthrows at import time if it's unset.ADMIN_INITIAL_PASSWORDis required fornpm run setup-db— the script exits with code 1 if it's unset. -
Set up the database
npm run setup-db -
Start development server
npm run dev
🗄️ Database Schema
The application uses the following tables:
users- User accounts and authenticationcards- Card information and metadatauser_cards- User's card collectionscollections- Named card collectionscollection_cards- Cards in collectionsdecks- Deck definitionsdeck_cards- Cards in decks
🔧 API Endpoints
Authentication
POST /api/auth/register- User registrationPOST /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 infix-auth-bypassBrief 3 (commitfc0dd73) and CI now blocks their reintroduction. Don't recreate them.
🚀 Deployment
This app is configured for deployment on Vercel:
- Connect your repository to Vercel
- Set environment variables in Vercel dashboard
- Deploy automatically on push to main branch
📁 Project Structure
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
├── lib/ # Utility libraries
│ └── database.js # Database adapter
├── scripts/ # Database setup scripts
├── public/ # Static assets
└── .env.local # Environment variables
🔐 First-time admin setup
npm run setup-db creates a single admin user the first time it runs. 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_PASSWORDin.env.localbefore runningnpm run setup-db. Useopenssl rand -base64 24(or any other strong source) to generate the value. - CI / Vercel: set
ADMIN_INITIAL_PASSWORDas a project 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@tcgvault.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-dbis idempotent (ON CONFLICT (email) DO NOTHING) — re-running it withADMIN_INITIAL_PASSWORDset 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 queuedrotate-default-adminfollow-up convoy.
🤝 Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Submit a pull request
📄 License
MIT License - see LICENSE file for details