deckhearth/scripts/reset-db.js
Randall Stillwell 1c18d214c1 feat(brand): infrastructure + email migration for Deck Hearth (B2 of 2)
Closes the pick-a-name convoy. Applies D1-D5 + Risk 4 PRESERVE per
operator gate-1 ratification.

Infrastructure renames:
- lib/rate-limit.js: 5 Redis key prefixes tcgvault:* → deckhearth:* (D5).
  One-time per-15-min / per-1-hour counter reset accepted; no user impact
  because counter windows are short anyway. Existing rate-limit state in
  Upstash will accumulate at the new prefix on first request.
- package.json: name field tcg-vault → deck-hearth (D2)
- package-lock.json: regenerated for the name change; STOP-on-churn
  protocol confirmed only the two name lines changed (no dep churn)
- All three test users (admin/alice/bob) renamed to @deckhearth.com (D4)
- One-off migration script scripts/migrations/2026-05-24-rename-admin-
  email.js (NEW): ESM, idempotent, UNIQUE-collision-safe. Per the
  no-go-zones rule for new migrations. Operator MUST run post-deploy.
- README.md + TESTING_GUIDE.md operator-caveat blockquotes flagged
- pages/login.js demo-credential pre-fill updated

PRESERVED per Risk 4:
- test/lib/permission-middleware.test.js literal admin@tcgvault.com
  with 7-line architect-authored "why" comment block. This is the
  documented pre-fix-auth-bypass bug shape; the regression-lock
  literal stays as historical truth.

Verification:
- npm run lint: 128 problems (baseline preserved)
- npm run test:run: 21/21 pass (preserved literal keeps green)
- Grep across full repo: 0 hits for TCG Vault / tcgvault / tcg-vault
  except the explicit preserve in the test file + .convoys/ historical
- lib/rate-limit.js: 5 deckhearth: prefixes, 0 tcgvault: prefixes
- node --check on the new migration script: exit 0
- git diff package-lock.json: only the 2 "name": lines changed (no churn)

Operator post-merge action:
- Run `node scripts/migrations/2026-05-24-rename-admin-email.js` against
  the production Neon DB. Order matters: migration FIRST, then any
  subsequent `npm run setup-db` invocation. Migration script will refuse
  to run if collision detected (means setup-db already ran post-rename).

Architect brief: .convoys/pick-a-name/brief-2-infrastructure-and-email-migration.md
Architect commit: 50ce9ab
Operator gate-1: D1-D5 + Risk 4 PRESERVE ratified.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 01:58:48 -05:00

164 lines
No EOL
5.1 KiB
JavaScript

#!/usr/bin/env node
/**
* Reset Database Script
*
* This script drops and recreates all tables in your Neon database.
*/
// Load environment variables from .env.local
require('dotenv').config({ path: '.env.local' });
const { neon } = require('@neondatabase/serverless');
async function resetDatabase() {
const sql = neon(process.env.POSTGRES_URL);
try {
console.log('✅ Connecting to Neon database...');
// Drop all tables in correct order (due to foreign key constraints)
console.log('🗑️ Dropping existing tables...');
await sql`DROP TABLE IF EXISTS deck_cards CASCADE`;
await sql`DROP TABLE IF EXISTS decks CASCADE`;
await sql`DROP TABLE IF EXISTS collection_cards CASCADE`;
await sql`DROP TABLE IF EXISTS collections CASCADE`;
await sql`DROP TABLE IF EXISTS user_cards CASCADE`;
await sql`DROP TABLE IF EXISTS cards CASCADE`;
await sql`DROP TABLE IF EXISTS users CASCADE`;
console.log('✅ Dropped all tables');
// Create tables
await sql`
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
role VARCHAR(50) DEFAULT 'user',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`;
console.log('✅ Created users table');
await sql`
CREATE TABLE cards (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
set_name VARCHAR(255),
set_code VARCHAR(50),
card_number VARCHAR(50),
rarity VARCHAR(50),
game VARCHAR(50) NOT NULL,
mana_cost VARCHAR(50),
cmc INTEGER,
card_type VARCHAR(255),
colors JSONB,
oracle_text TEXT,
power VARCHAR(10),
toughness VARCHAR(10),
image_url TEXT,
stock_image_url TEXT,
current_price DECIMAL(10,2),
market_price DECIMAL(10,2),
scryfall_id VARCHAR(255) UNIQUE,
verified BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`;
console.log('✅ Created cards table');
await sql`
CREATE TABLE user_cards (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
card_id INTEGER REFERENCES cards(id) ON DELETE CASCADE,
quantity INTEGER DEFAULT 1,
condition VARCHAR(50) DEFAULT 'NM',
is_foil BOOLEAN DEFAULT false,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(user_id, card_id, is_foil)
)
`;
console.log('✅ Created user_cards table');
await sql`
CREATE TABLE collections (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
is_public BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`;
console.log('✅ Created collections table');
await sql`
CREATE TABLE collection_cards (
id SERIAL PRIMARY KEY,
collection_id INTEGER REFERENCES collections(id) ON DELETE CASCADE,
card_id INTEGER REFERENCES cards(id) ON DELETE CASCADE,
quantity INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(collection_id, card_id)
)
`;
console.log('✅ Created collection_cards table');
await sql`
CREATE TABLE decks (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
game VARCHAR(50),
is_public BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`;
console.log('✅ Created decks table');
await sql`
CREATE TABLE deck_cards (
id SERIAL PRIMARY KEY,
deck_id INTEGER REFERENCES decks(id) ON DELETE CASCADE,
card_id INTEGER REFERENCES cards(id) ON DELETE CASCADE,
quantity INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(deck_id, card_id)
)
`;
console.log('✅ Created deck_cards table');
// Create admin user
const bcrypt = require('bcryptjs');
const hashedPassword = await bcrypt.hash('admin123', 12);
await sql`
INSERT INTO users (email, password, role)
VALUES (${'admin@deckhearth.com'}, ${hashedPassword}, ${'admin'})
`;
console.log('✅ Created admin user');
console.log('🎉 Database reset completed successfully!');
console.log('');
console.log('📋 Database Details:');
console.log(' Database: Neon PostgreSQL');
console.log(' Admin User: admin@deckhearth.com');
console.log(' Admin Password: admin123');
} catch (error) {
console.error('❌ Database reset failed:', error.message);
process.exit(1);
}
}
resetDatabase();