#!/usr/bin/env node /** * Add user profile fields to support enhanced profile and settings * * This script adds new columns to the users table for: * - Profile information (first_name, last_name, username, bio, avatar_url) * - Preferences (favorite_games, collection_visibility, preferred_currency, etc.) * - Settings (notifications, display preferences) */ import dotenv from 'dotenv'; import { neon } from '@neondatabase/serverless'; // Load environment variables from .env.local dotenv.config({ path: '.env.local' }); async function addUserProfileFields() { const sql = neon(process.env.POSTGRES_URL); try { console.log('āœ… Connecting to Neon database...'); // Add basic profile fields console.log('šŸ‘¤ Adding basic profile fields...'); await sql` ALTER TABLE users ADD COLUMN IF NOT EXISTS first_name VARCHAR(255), ADD COLUMN IF NOT EXISTS last_name VARCHAR(255), ADD COLUMN IF NOT EXISTS username VARCHAR(255) UNIQUE, ADD COLUMN IF NOT EXISTS bio TEXT, ADD COLUMN IF NOT EXISTS avatar_url TEXT `; console.log('āœ… Added basic profile fields'); // Add preference fields console.log('āš™ļø Adding preference fields...'); await sql` ALTER TABLE users ADD COLUMN IF NOT EXISTS favorite_games JSONB DEFAULT '["MTG"]', ADD COLUMN IF NOT EXISTS collection_visibility VARCHAR(20) DEFAULT 'private', ADD COLUMN IF NOT EXISTS preferred_currency VARCHAR(3) DEFAULT 'USD', ADD COLUMN IF NOT EXISTS cards_per_page INTEGER DEFAULT 50, ADD COLUMN IF NOT EXISTS default_view VARCHAR(10) DEFAULT 'grid' `; console.log('āœ… Added preference fields'); // Add notification settings console.log('šŸ”” Adding notification settings...'); await sql` ALTER TABLE users ADD COLUMN IF NOT EXISTS notifications_email BOOLEAN DEFAULT true, ADD COLUMN IF NOT EXISTS notifications_marketing BOOLEAN DEFAULT false, ADD COLUMN IF NOT EXISTS two_factor_enabled BOOLEAN DEFAULT false `; console.log('āœ… Added notification settings'); // Add display settings console.log('šŸŽØ Adding display settings...'); await sql` ALTER TABLE users ADD COLUMN IF NOT EXISTS theme VARCHAR(10) DEFAULT 'system', ADD COLUMN IF NOT EXISTS language VARCHAR(5) DEFAULT 'en' `; console.log('āœ… Added display settings'); // Create user_settings table for more complex settings console.log('šŸ“Š Creating user_settings table...'); await sql` CREATE TABLE IF NOT EXISTS user_settings ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, setting_key VARCHAR(100) NOT NULL, setting_value JSONB NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(user_id, setting_key) ) `; console.log('āœ… Created user_settings table'); // Create user_avatars table for avatar management console.log('šŸ–¼ļø Creating user_avatars table...'); await sql` CREATE TABLE IF NOT EXISTS user_avatars ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, filename VARCHAR(255) NOT NULL, original_name VARCHAR(255), mime_type VARCHAR(100), file_size INTEGER, file_path TEXT NOT NULL, is_active BOOLEAN DEFAULT true, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `; console.log('āœ… Created user_avatars table'); // Add indexes for performance console.log('šŸš€ Adding indexes for performance...'); await sql`CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)`; await sql`CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)`; await sql`CREATE INDEX IF NOT EXISTS idx_user_settings_user_id ON user_settings(user_id)`; await sql`CREATE INDEX IF NOT EXISTS idx_user_settings_key ON user_settings(setting_key)`; await sql`CREATE INDEX IF NOT EXISTS idx_user_avatars_user_id ON user_avatars(user_id)`; await sql`CREATE INDEX IF NOT EXISTS idx_user_avatars_active ON user_avatars(user_id, is_active)`; console.log('āœ… Added performance indexes'); // Add constraints and validation (skip if they already exist) console.log('šŸ”’ Adding constraints and validation...'); try { await sql` ALTER TABLE users ADD CONSTRAINT check_collection_visibility CHECK (collection_visibility IN ('private', 'public', 'unlisted')) `; } catch (error) { if (!error.message.includes('already exists')) { throw error; } } try { await sql` ALTER TABLE users ADD CONSTRAINT check_preferred_currency CHECK (preferred_currency IN ('USD', 'EUR', 'GBP', 'CAD', 'JPY')) `; } catch (error) { if (!error.message.includes('already exists')) { throw error; } } try { await sql` ALTER TABLE users ADD CONSTRAINT check_cards_per_page CHECK (cards_per_page IN (25, 50, 100)) `; } catch (error) { if (!error.message.includes('already exists')) { throw error; } } try { await sql` ALTER TABLE users ADD CONSTRAINT check_default_view CHECK (default_view IN ('grid', 'list')) `; } catch (error) { if (!error.message.includes('already exists')) { throw error; } } try { await sql` ALTER TABLE users ADD CONSTRAINT check_theme CHECK (theme IN ('light', 'dark', 'system')) `; } catch (error) { if (!error.message.includes('already exists')) { throw error; } } try { await sql` ALTER TABLE users ADD CONSTRAINT check_language CHECK (language IN ('en', 'es', 'fr', 'de', 'ja')) `; } catch (error) { if (!error.message.includes('already exists')) { throw error; } } console.log('āœ… Added constraints and validation'); // Update existing users with default values console.log('šŸ”„ Updating existing users with default values...'); await sql` UPDATE users SET favorite_games = '["MTG"]'::jsonb, collection_visibility = 'private', preferred_currency = 'USD', cards_per_page = 50, default_view = 'grid', notifications_email = true, notifications_marketing = false, two_factor_enabled = false, theme = 'system', language = 'en' WHERE favorite_games IS NULL OR collection_visibility IS NULL OR preferred_currency IS NULL OR cards_per_page IS NULL OR default_view IS NULL OR notifications_email IS NULL OR notifications_marketing IS NULL OR two_factor_enabled IS NULL OR theme IS NULL OR language IS NULL `; console.log('āœ… Updated existing users with defaults'); console.log('\nšŸŽ‰ User profile fields added successfully!'); console.log('\nšŸ“‹ Summary of changes:'); console.log(' šŸ“ Basic Profile: first_name, last_name, username, bio, avatar_url'); console.log(' āš™ļø Preferences: favorite_games, collection_visibility, preferred_currency, cards_per_page, default_view'); console.log(' šŸ”” Notifications: notifications_email, notifications_marketing, two_factor_enabled'); console.log(' šŸŽØ Display: theme, language'); console.log(' šŸ“Š New Tables: user_settings, user_avatars'); console.log(' šŸš€ Performance: Added indexes for fast queries'); console.log(' šŸ”’ Validation: Added constraints for data integrity'); } catch (error) { console.error('āŒ Failed to add user profile fields:', error.message); console.error('Full error:', error); process.exit(1); } } // Run the migration if this script is executed directly if (import.meta.url === `file://${process.argv[1]}`) { addUserProfileFields(); } export { addUserProfileFields };