deckhearth/scripts/reset-db.js
Randall Stillwell 1cc2e28423 Migrate Deck Hearth off Vercel/Neon to homelab Dokploy stack.
Replace @vercel/postgres, Blob, and Upstash with lib/sql.js, MinIO object
storage, and CT 102 Redis rate limits. Add Dockerfile for Dokploy deploy,
homelab runbooks, Neon data-copy helper, and point CI smoke/visual at the
homelab URL instead of Vercel previews.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-15 09:32:13 -05:00

181 lines
5.9 KiB
JavaScript

#!/usr/bin/env node
/**
* Reset Database Script
*
* This script drops and recreates all tables in your Neon database, then
* seeds an admin user. DESTRUCTIVE — never run against production.
*
* Required env (in .env.local):
* POSTGRES_URL — Neon connection string
* ADMIN_INITIAL_PASSWORD — strong password for the seeded admin user
* (generate with `openssl rand -base64 24`)
*
* Mirrors the post-`drop-public-setup` shape of `setup-neon-db.js`
* (commit b63b509) — same ESM imports, same fail-loud env-var check,
* same no-password-echo convention. Convoy: `fix-reset-db-script`
* (2026-05-25).
*/
import dotenv from 'dotenv';
dotenv.config({ path: '.env.local' });
import { sql } from '../lib/sql.js';
import bcrypt from 'bcryptjs';
async function resetDatabase() {
const adminPassword = process.env.ADMIN_INITIAL_PASSWORD;
if (!adminPassword || !adminPassword.trim()) {
console.error(
'❌ ADMIN_INITIAL_PASSWORD environment variable is not set.\n' +
'\n' +
' Set it in .env.local for local dev, or as a CI secret if you run reset from CI.\n' +
' Generate a strong password with: openssl rand -base64 24\n' +
' See README.md → "First-time admin setup" for the full flow.\n'
);
process.exit(1);
}
try {
console.log('✅ Connecting to Neon database...');
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');
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');
const hashedPassword = await bcrypt.hash(adminPassword, 12);
await sql`
INSERT INTO users (email, password, role)
VALUES (${'admin@deckhearth.com'}, ${hashedPassword}, ${'admin'})
ON CONFLICT (email) DO NOTHING
`;
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 ready (email: admin@deckhearth.com)');
} catch (error) {
console.error('❌ Database reset failed:', error.message);
process.exit(1);
}
}
resetDatabase();