import { neon } from '@neondatabase/serverless'; import cardsData from '../src/data/cards.json'; // Initialize Neon client const sql = neon(process.env.DATABASE_URL!); interface JsonCard { id: number; name: string; set_name?: string; set_code?: string; card_number?: string; rarity?: string; game: string; mana_cost?: string; cmc?: number; card_type?: string; colors?: string[]; oracle_text?: string; flavor_text?: string; power?: string; toughness?: string; loyalty?: string; artist?: string; image_url?: string; stock_image_url?: string; artwork_crop_coords?: any; current_price?: number; market_price?: number; low_price?: number; high_price?: number; price_last_updated?: string; ocr_confidence?: number; ocr_raw_text?: string; scryfall_id?: string; tcg_player_id?: string; verified: boolean; created_at?: string; updated_at?: string; } async function migrateCards() { console.log('Starting card migration to Neon database...'); try { // First, ensure the database schema exists console.log('Setting up database schema...'); await sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`; await sql` CREATE TABLE IF NOT EXISTS cards ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, set_name VARCHAR(255), set_code VARCHAR(50), card_number VARCHAR(50), rarity VARCHAR(100), game VARCHAR(50) NOT NULL, mana_cost VARCHAR(50), cmc INTEGER, card_type VARCHAR(100), colors JSONB, oracle_text TEXT, flavor_text TEXT, power VARCHAR(50), toughness VARCHAR(50), loyalty VARCHAR(50), artist VARCHAR(255), image_url VARCHAR(500), stock_image_url VARCHAR(500), artwork_crop_coords JSONB, current_price DECIMAL(10,2), market_price DECIMAL(10,2), low_price DECIMAL(10,2), high_price DECIMAL(10,2), price_last_updated TIMESTAMP, ocr_confidence DECIMAL(5,2), ocr_raw_text TEXT, scryfall_id VARCHAR(100), tcg_player_id VARCHAR(100), verified BOOLEAN DEFAULT true, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ) `; // Create indexes await sql`CREATE INDEX IF NOT EXISTS idx_cards_name ON cards(name)`; await sql`CREATE INDEX IF NOT EXISTS idx_cards_game ON cards(game)`; await sql`CREATE INDEX IF NOT EXISTS idx_cards_set_name ON cards(set_name)`; await sql`CREATE INDEX IF NOT EXISTS idx_cards_rarity ON cards(rarity)`; console.log('Database schema created successfully'); // Clear existing data (optional - remove this if you want to keep existing data) await sql`TRUNCATE TABLE cards RESTART IDENTITY`; console.log('Cleared existing card data'); // Migrate cards from JSON const cards = cardsData as JsonCard[]; console.log(`Migrating ${cards.length} cards...`); let migratedCount = 0; for (const card of cards) { try { await sql` INSERT INTO cards ( name, set_name, set_code, card_number, rarity, game, mana_cost, cmc, card_type, colors, oracle_text, flavor_text, power, toughness, loyalty, artist, image_url, stock_image_url, artwork_crop_coords, current_price, market_price, low_price, high_price, price_last_updated, ocr_confidence, ocr_raw_text, scryfall_id, tcg_player_id, verified, created_at, updated_at ) VALUES ( ${card.name}, ${card.set_name}, ${card.set_code}, ${card.card_number}, ${card.rarity}, ${card.game}, ${card.mana_cost}, ${card.cmc}, ${card.card_type}, ${JSON.stringify(card.colors)}, ${card.oracle_text}, ${card.flavor_text}, ${card.power}, ${card.toughness}, ${card.loyalty}, ${card.artist}, ${card.image_url}, ${card.stock_image_url}, ${JSON.stringify(card.artwork_crop_coords)}, ${card.current_price}, ${card.market_price}, ${card.low_price}, ${card.high_price}, ${card.price_last_updated ? new Date(card.price_last_updated) : null}, ${card.ocr_confidence}, ${card.ocr_raw_text}, ${card.scryfall_id}, ${card.tcg_player_id}, ${card.verified}, ${card.created_at ? new Date(card.created_at) : new Date()}, ${card.updated_at ? new Date(card.updated_at) : new Date()} ) `; migratedCount++; if (migratedCount % 10 === 0) { console.log(`Migrated ${migratedCount}/${cards.length} cards...`); } } catch (error) { console.error(`Error migrating card ${card.name}:`, error); } } console.log(`✅ Successfully migrated ${migratedCount} cards to Neon database!`); // Create other tables for collections and decks console.log('Creating user tables...'); await sql` CREATE TABLE IF NOT EXISTS user_collections ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, description TEXT, is_public BOOLEAN DEFAULT false, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ) `; await sql` CREATE TABLE IF NOT EXISTS collection_cards ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), collection_id UUID REFERENCES user_collections(id) ON DELETE CASCADE, card_id INTEGER REFERENCES cards(id), quantity INTEGER DEFAULT 1, condition VARCHAR(50) DEFAULT 'near-mint', notes TEXT, user_images JSONB, purchase_price DECIMAL(10,2), purchase_date DATE, added_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ) `; await sql` CREATE TABLE IF NOT EXISTS user_decks ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, description TEXT, game VARCHAR(50) NOT NULL, format VARCHAR(100), is_public BOOLEAN DEFAULT false, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ) `; await sql` CREATE TABLE IF NOT EXISTS deck_cards ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), deck_id UUID REFERENCES user_decks(id) ON DELETE CASCADE, card_id INTEGER REFERENCES cards(id), quantity INTEGER DEFAULT 1, is_commander BOOLEAN DEFAULT false, is_sideboard BOOLEAN DEFAULT false, added_at TIMESTAMP DEFAULT NOW() ) `; await sql` CREATE TABLE IF NOT EXISTS user_preferences ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id VARCHAR(255) UNIQUE NOT NULL, default_view VARCHAR(50) DEFAULT 'card', items_per_page INTEGER DEFAULT 20, enable_animations BOOLEAN DEFAULT true, enable_ocr BOOLEAN DEFAULT true, theme VARCHAR(50) DEFAULT 'light', privacy_settings JSONB DEFAULT '{"collections_public": false, "decks_public": false}', created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ) `; await sql` CREATE TABLE IF NOT EXISTS collection_shares ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), collection_id UUID REFERENCES user_collections(id) ON DELETE CASCADE, shared_with_user_id VARCHAR(255) NOT NULL, permission_level VARCHAR(50) DEFAULT 'view', shared_by_user_id VARCHAR(255) NOT NULL, shared_at TIMESTAMP DEFAULT NOW() ) `; // Create remaining indexes await sql`CREATE INDEX IF NOT EXISTS idx_user_collections_user_id ON user_collections(user_id)`; await sql`CREATE INDEX IF NOT EXISTS idx_collection_cards_collection_id ON collection_cards(collection_id)`; await sql`CREATE INDEX IF NOT EXISTS idx_collection_cards_card_id ON collection_cards(card_id)`; await sql`CREATE INDEX IF NOT EXISTS idx_user_decks_user_id ON user_decks(user_id)`; await sql`CREATE INDEX IF NOT EXISTS idx_deck_cards_deck_id ON deck_cards(deck_id)`; await sql`CREATE INDEX IF NOT EXISTS idx_deck_cards_card_id ON deck_cards(card_id)`; await sql`CREATE INDEX IF NOT EXISTS idx_user_preferences_user_id ON user_preferences(user_id)`; await sql`CREATE INDEX IF NOT EXISTS idx_collection_shares_collection_id ON collection_shares(collection_id)`; await sql`CREATE INDEX IF NOT EXISTS idx_collection_shares_shared_with_user_id ON collection_shares(shared_with_user_id)`; console.log('✅ All database tables and indexes created successfully!'); console.log('🎉 Migration completed successfully!'); } catch (error) { console.error('❌ Migration failed:', error); process.exit(1); } } // Run migration if this file is executed directly if (require.main === module) { migrateCards().then(() => { console.log('Migration script completed'); process.exit(0); }); } export { migrateCards };