247 lines
8.1 KiB
JavaScript
247 lines
8.1 KiB
JavaScript
|
|
#!/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 };
|