import 'dotenv/config'; import { db } from './index.js'; import { sql } from 'drizzle-orm'; const dbType = process.env.DB_TYPE || 'sqlite'; console.log(`Database type: ${dbType}`); console.log(`Database URL: ${process.env.DATABASE_URL?.substring(0, 30)}...`); async function migrate() { console.log('Running migrations...'); if (dbType === 'sqlite') { // SQLite migrations await (db as any).run(sql` CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, email TEXT NOT NULL UNIQUE, password TEXT, name TEXT NOT NULL, phone TEXT, role TEXT NOT NULL DEFAULT 'user', language_preference TEXT, is_claimed INTEGER NOT NULL DEFAULT 1, google_id TEXT, ruc_number TEXT, account_status TEXT NOT NULL DEFAULT 'active', created_at TEXT NOT NULL, updated_at TEXT NOT NULL ) `); // Add new user columns if they don't exist (for existing databases) try { await (db as any).run(sql`ALTER TABLE users ADD COLUMN is_claimed INTEGER NOT NULL DEFAULT 1`); } catch (e) { /* column may already exist */ } try { await (db as any).run(sql`ALTER TABLE users ADD COLUMN google_id TEXT`); } catch (e) { /* column may already exist */ } try { await (db as any).run(sql`ALTER TABLE users ADD COLUMN ruc_number TEXT`); } catch (e) { /* column may already exist */ } try { await (db as any).run(sql`ALTER TABLE users ADD COLUMN account_status TEXT NOT NULL DEFAULT 'active'`); } catch (e) { /* column may already exist */ } // Magic link tokens table await (db as any).run(sql` CREATE TABLE IF NOT EXISTS magic_link_tokens ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id), token TEXT NOT NULL UNIQUE, type TEXT NOT NULL, expires_at TEXT NOT NULL, used_at TEXT, created_at TEXT NOT NULL ) `); // User sessions table await (db as any).run(sql` CREATE TABLE IF NOT EXISTS user_sessions ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id), token TEXT NOT NULL UNIQUE, user_agent TEXT, ip_address TEXT, last_active_at TEXT NOT NULL, expires_at TEXT NOT NULL, created_at TEXT NOT NULL ) `); await (db as any).run(sql` CREATE TABLE IF NOT EXISTS events ( id TEXT PRIMARY KEY, title TEXT NOT NULL, title_es TEXT, description TEXT NOT NULL, description_es TEXT, short_description TEXT, short_description_es TEXT, start_datetime TEXT NOT NULL, end_datetime TEXT, location TEXT NOT NULL, location_url TEXT, price REAL NOT NULL DEFAULT 0, currency TEXT NOT NULL DEFAULT 'PYG', capacity INTEGER NOT NULL DEFAULT 50, status TEXT NOT NULL DEFAULT 'draft', banner_url TEXT, external_booking_enabled INTEGER NOT NULL DEFAULT 0, external_booking_url TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ) `); // Add external booking columns to events if they don't exist (for existing databases) try { await (db as any).run(sql`ALTER TABLE events ADD COLUMN external_booking_enabled INTEGER NOT NULL DEFAULT 0`); } catch (e) { /* column may already exist */ } try { await (db as any).run(sql`ALTER TABLE events ADD COLUMN external_booking_url TEXT`); } catch (e) { /* column may already exist */ } // Add short description columns to events try { await (db as any).run(sql`ALTER TABLE events ADD COLUMN short_description TEXT`); } catch (e) { /* column may already exist */ } try { await (db as any).run(sql`ALTER TABLE events ADD COLUMN short_description_es TEXT`); } catch (e) { /* column may already exist */ } await (db as any).run(sql` CREATE TABLE IF NOT EXISTS tickets ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id), event_id TEXT NOT NULL REFERENCES events(id), attendee_name TEXT NOT NULL, attendee_email TEXT NOT NULL, attendee_phone TEXT NOT NULL, preferred_language TEXT, status TEXT NOT NULL DEFAULT 'pending', checkin_at TEXT, qr_code TEXT, created_at TEXT NOT NULL ) `); // Add new columns if they don't exist (for existing databases) try { await (db as any).run(sql`ALTER TABLE tickets ADD COLUMN attendee_name TEXT`); } catch (e) { /* column may already exist */ } try { await (db as any).run(sql`ALTER TABLE tickets ADD COLUMN attendee_email TEXT`); } catch (e) { /* column may already exist */ } try { await (db as any).run(sql`ALTER TABLE tickets ADD COLUMN attendee_phone TEXT`); } catch (e) { /* column may already exist */ } try { await (db as any).run(sql`ALTER TABLE tickets ADD COLUMN preferred_language TEXT`); } catch (e) { /* column may already exist */ } try { await (db as any).run(sql`ALTER TABLE tickets ADD COLUMN admin_note TEXT`); } catch (e) { /* column may already exist */ } // Migration: Add first/last name columns try { await (db as any).run(sql`ALTER TABLE tickets ADD COLUMN attendee_first_name TEXT`); } catch (e) { /* column may already exist */ } try { await (db as any).run(sql`ALTER TABLE tickets ADD COLUMN attendee_last_name TEXT`); } catch (e) { /* column may already exist */ } try { await (db as any).run(sql`ALTER TABLE tickets ADD COLUMN attendee_ruc TEXT`); } catch (e) { /* column may already exist */ } // Migration: Copy data from attendee_name to attendee_first_name if attendee_first_name is empty try { await (db as any).run(sql` UPDATE tickets SET attendee_first_name = attendee_name WHERE attendee_first_name IS NULL AND attendee_name IS NOT NULL `); } catch (e) { /* migration may have already run */ } // Migration: Add checked_in_by_admin_id column to tickets try { await (db as any).run(sql`ALTER TABLE tickets ADD COLUMN checked_in_by_admin_id TEXT REFERENCES users(id)`); } catch (e) { /* column may already exist */ } // Migration: Add booking_id column to tickets for multi-ticket bookings try { await (db as any).run(sql`ALTER TABLE tickets ADD COLUMN booking_id TEXT`); } catch (e) { /* column may already exist */ } // Make attendee_email and attendee_phone nullable (recreate table if needed or just allow nulls for new entries) // SQLite doesn't support altering column constraints, so we'll just ensure new entries work await (db as any).run(sql` CREATE TABLE IF NOT EXISTS payments ( id TEXT PRIMARY KEY, ticket_id TEXT NOT NULL REFERENCES tickets(id), provider TEXT NOT NULL, amount REAL NOT NULL, currency TEXT NOT NULL DEFAULT 'PYG', status TEXT NOT NULL DEFAULT 'pending', reference TEXT, paid_at TEXT, paid_by_admin_id TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ) `); // Add new columns if they don't exist (for existing databases) try { await (db as any).run(sql`ALTER TABLE payments ADD COLUMN paid_at TEXT`); } catch (e) { /* column may already exist */ } try { await (db as any).run(sql`ALTER TABLE payments ADD COLUMN paid_by_admin_id TEXT`); } catch (e) { /* column may already exist */ } try { await (db as any).run(sql`ALTER TABLE payments ADD COLUMN user_marked_paid_at TEXT`); } catch (e) { /* column may already exist */ } try { await (db as any).run(sql`ALTER TABLE payments ADD COLUMN admin_note TEXT`); } catch (e) { /* column may already exist */ } try { await (db as any).run(sql`ALTER TABLE payments ADD COLUMN payer_name TEXT`); } catch (e) { /* column may already exist */ } try { await (db as any).run(sql`ALTER TABLE payments ADD COLUMN reminder_sent_at TEXT`); } catch (e) { /* column may already exist */ } // Invoices table await (db as any).run(sql` CREATE TABLE IF NOT EXISTS invoices ( id TEXT PRIMARY KEY, payment_id TEXT NOT NULL REFERENCES payments(id), user_id TEXT NOT NULL REFERENCES users(id), invoice_number TEXT NOT NULL UNIQUE, ruc_number TEXT, legal_name TEXT, amount REAL NOT NULL, currency TEXT NOT NULL DEFAULT 'PYG', pdf_url TEXT, status TEXT NOT NULL DEFAULT 'generated', created_at TEXT NOT NULL ) `); // Payment options table await (db as any).run(sql` CREATE TABLE IF NOT EXISTS payment_options ( id TEXT PRIMARY KEY, tpago_enabled INTEGER NOT NULL DEFAULT 0, tpago_link TEXT, tpago_instructions TEXT, tpago_instructions_es TEXT, bank_transfer_enabled INTEGER NOT NULL DEFAULT 0, bank_name TEXT, bank_account_holder TEXT, bank_account_number TEXT, bank_alias TEXT, bank_phone TEXT, bank_notes TEXT, bank_notes_es TEXT, lightning_enabled INTEGER NOT NULL DEFAULT 1, cash_enabled INTEGER NOT NULL DEFAULT 1, cash_instructions TEXT, cash_instructions_es TEXT, updated_at TEXT NOT NULL, updated_by TEXT REFERENCES users(id) ) `); // Add allow_duplicate_bookings column to payment_options if it doesn't exist try { await (db as any).run(sql`ALTER TABLE payment_options ADD COLUMN allow_duplicate_bookings INTEGER NOT NULL DEFAULT 0`); } catch (e) { /* column may already exist */ } // Event payment overrides table await (db as any).run(sql` CREATE TABLE IF NOT EXISTS event_payment_overrides ( id TEXT PRIMARY KEY, event_id TEXT NOT NULL REFERENCES events(id), tpago_enabled INTEGER, tpago_link TEXT, tpago_instructions TEXT, tpago_instructions_es TEXT, bank_transfer_enabled INTEGER, bank_name TEXT, bank_account_holder TEXT, bank_account_number TEXT, bank_alias TEXT, bank_phone TEXT, bank_notes TEXT, bank_notes_es TEXT, lightning_enabled INTEGER, cash_enabled INTEGER, cash_instructions TEXT, cash_instructions_es TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ) `); await (db as any).run(sql` CREATE TABLE IF NOT EXISTS contacts ( id TEXT PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL, message TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'new', created_at TEXT NOT NULL ) `); await (db as any).run(sql` CREATE TABLE IF NOT EXISTS email_subscribers ( id TEXT PRIMARY KEY, email TEXT NOT NULL UNIQUE, name TEXT, status TEXT NOT NULL DEFAULT 'active', created_at TEXT NOT NULL ) `); await (db as any).run(sql` CREATE TABLE IF NOT EXISTS media ( id TEXT PRIMARY KEY, file_url TEXT NOT NULL, type TEXT NOT NULL, related_id TEXT, related_type TEXT, created_at TEXT NOT NULL ) `); await (db as any).run(sql` CREATE TABLE IF NOT EXISTS audit_logs ( id TEXT PRIMARY KEY, user_id TEXT REFERENCES users(id), action TEXT NOT NULL, target TEXT, target_id TEXT, details TEXT, timestamp TEXT NOT NULL ) `); // Email system tables await (db as any).run(sql` CREATE TABLE IF NOT EXISTS email_templates ( id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, slug TEXT NOT NULL UNIQUE, subject TEXT NOT NULL, subject_es TEXT, body_html TEXT NOT NULL, body_html_es TEXT, body_text TEXT, body_text_es TEXT, description TEXT, variables TEXT, is_system INTEGER NOT NULL DEFAULT 0, is_active INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ) `); await (db as any).run(sql` CREATE TABLE IF NOT EXISTS email_logs ( id TEXT PRIMARY KEY, template_id TEXT REFERENCES email_templates(id), event_id TEXT REFERENCES events(id), recipient_email TEXT NOT NULL, recipient_name TEXT, subject TEXT NOT NULL, body_html TEXT, status TEXT NOT NULL DEFAULT 'pending', error_message TEXT, sent_at TEXT, sent_by TEXT REFERENCES users(id), created_at TEXT NOT NULL ) `); try { await (db as any).run(sql`ALTER TABLE email_logs ADD COLUMN resend_attempts INTEGER NOT NULL DEFAULT 0`); } catch (e) { /* column may already exist */ } try { await (db as any).run(sql`ALTER TABLE email_logs ADD COLUMN last_resent_at TEXT`); } catch (e) { /* column may already exist */ } await (db as any).run(sql` CREATE TABLE IF NOT EXISTS email_settings ( id TEXT PRIMARY KEY, key TEXT NOT NULL UNIQUE, value TEXT NOT NULL, updated_at TEXT NOT NULL ) `); // Site settings table await (db as any).run(sql` CREATE TABLE IF NOT EXISTS site_settings ( id TEXT PRIMARY KEY, timezone TEXT NOT NULL DEFAULT 'America/Asuncion', site_name TEXT NOT NULL DEFAULT 'Spanglish', site_description TEXT, site_description_es TEXT, contact_email TEXT, contact_phone TEXT, facebook_url TEXT, instagram_url TEXT, twitter_url TEXT, linkedin_url TEXT, featured_event_id TEXT REFERENCES events(id), maintenance_mode INTEGER NOT NULL DEFAULT 0, maintenance_message TEXT, maintenance_message_es TEXT, updated_at TEXT NOT NULL, updated_by TEXT REFERENCES users(id) ) `); // Add featured_event_id column to site_settings if it doesn't exist try { await (db as any).run(sql`ALTER TABLE site_settings ADD COLUMN featured_event_id TEXT REFERENCES events(id)`); } catch (e) { /* column may already exist */ } // Legal pages table for admin-editable legal content await (db as any).run(sql` CREATE TABLE IF NOT EXISTS legal_pages ( id TEXT PRIMARY KEY, slug TEXT NOT NULL UNIQUE, title TEXT NOT NULL, title_es TEXT, content_text TEXT NOT NULL, content_text_es TEXT, content_markdown TEXT NOT NULL, content_markdown_es TEXT, updated_at TEXT NOT NULL, updated_by TEXT REFERENCES users(id), created_at TEXT NOT NULL ) `); // FAQ questions table await (db as any).run(sql` CREATE TABLE IF NOT EXISTS faq_questions ( id TEXT PRIMARY KEY, question TEXT NOT NULL, question_es TEXT, answer TEXT NOT NULL, answer_es TEXT, enabled INTEGER NOT NULL DEFAULT 1, show_on_homepage INTEGER NOT NULL DEFAULT 0, rank INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ) `); // Legal settings table for legal page placeholder values await (db as any).run(sql` CREATE TABLE IF NOT EXISTS legal_settings ( id TEXT PRIMARY KEY, company_name TEXT, legal_entity_name TEXT, ruc_number TEXT, company_address TEXT, company_city TEXT, company_country TEXT, support_email TEXT, legal_email TEXT, governing_law TEXT, jurisdiction_city TEXT, updated_at TEXT NOT NULL, updated_by TEXT REFERENCES users(id) ) `); } else { // PostgreSQL migrations await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY, email VARCHAR(255) NOT NULL UNIQUE, password VARCHAR(255), name VARCHAR(255) NOT NULL, phone VARCHAR(50), role VARCHAR(20) NOT NULL DEFAULT 'user', language_preference VARCHAR(10), is_claimed INTEGER NOT NULL DEFAULT 1, google_id VARCHAR(255), ruc_number VARCHAR(15), account_status VARCHAR(20) NOT NULL DEFAULT 'active', created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL ) `); // Add new user columns for existing PostgreSQL databases try { await (db as any).execute(sql`ALTER TABLE users ADD COLUMN is_claimed INTEGER NOT NULL DEFAULT 1`); } catch (e) { /* column may already exist */ } try { await (db as any).execute(sql`ALTER TABLE users ADD COLUMN google_id VARCHAR(255)`); } catch (e) { /* column may already exist */ } try { await (db as any).execute(sql`ALTER TABLE users ADD COLUMN ruc_number VARCHAR(15)`); } catch (e) { /* column may already exist */ } try { await (db as any).execute(sql`ALTER TABLE users ADD COLUMN account_status VARCHAR(20) NOT NULL DEFAULT 'active'`); } catch (e) { /* column may already exist */ } // Magic link tokens table await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS magic_link_tokens ( id UUID PRIMARY KEY, user_id UUID NOT NULL REFERENCES users(id), token VARCHAR(255) NOT NULL UNIQUE, type VARCHAR(30) NOT NULL, expires_at TIMESTAMP NOT NULL, used_at TIMESTAMP, created_at TIMESTAMP NOT NULL ) `); // User sessions table await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS user_sessions ( id UUID PRIMARY KEY, user_id UUID NOT NULL REFERENCES users(id), token VARCHAR(500) NOT NULL UNIQUE, user_agent TEXT, ip_address VARCHAR(45), last_active_at TIMESTAMP NOT NULL, expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP NOT NULL ) `); await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS events ( id UUID PRIMARY KEY, title VARCHAR(255) NOT NULL, title_es VARCHAR(255), description TEXT NOT NULL, description_es TEXT, short_description VARCHAR(300), short_description_es VARCHAR(300), start_datetime TIMESTAMP NOT NULL, end_datetime TIMESTAMP, location VARCHAR(500) NOT NULL, location_url VARCHAR(500), price DECIMAL(10, 2) NOT NULL DEFAULT 0, currency VARCHAR(10) NOT NULL DEFAULT 'PYG', capacity INTEGER NOT NULL DEFAULT 50, status VARCHAR(20) NOT NULL DEFAULT 'draft', banner_url VARCHAR(500), external_booking_enabled INTEGER NOT NULL DEFAULT 0, external_booking_url VARCHAR(500), created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL ) `); // Add external booking columns to events if they don't exist (for existing databases) try { await (db as any).execute(sql`ALTER TABLE events ADD COLUMN external_booking_enabled INTEGER NOT NULL DEFAULT 0`); } catch (e) { /* column may already exist */ } try { await (db as any).execute(sql`ALTER TABLE events ADD COLUMN external_booking_url VARCHAR(500)`); } catch (e) { /* column may already exist */ } // Add short description columns to events try { await (db as any).execute(sql`ALTER TABLE events ADD COLUMN short_description VARCHAR(300)`); } catch (e) { /* column may already exist */ } try { await (db as any).execute(sql`ALTER TABLE events ADD COLUMN short_description_es VARCHAR(300)`); } catch (e) { /* column may already exist */ } await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS tickets ( id UUID PRIMARY KEY, user_id UUID NOT NULL REFERENCES users(id), event_id UUID NOT NULL REFERENCES events(id), attendee_first_name VARCHAR(255) NOT NULL, attendee_last_name VARCHAR(255), attendee_email VARCHAR(255), attendee_phone VARCHAR(50), attendee_ruc VARCHAR(15), preferred_language VARCHAR(10), status VARCHAR(20) NOT NULL DEFAULT 'pending', checkin_at TIMESTAMP, qr_code VARCHAR(255), admin_note TEXT, created_at TIMESTAMP NOT NULL ) `); // Add attendee_ruc column if it doesn't exist try { await (db as any).execute(sql`ALTER TABLE tickets ADD COLUMN attendee_ruc VARCHAR(15)`); } catch (e) { /* column may already exist */ } // Add checked_in_by_admin_id column to tickets try { await (db as any).execute(sql`ALTER TABLE tickets ADD COLUMN checked_in_by_admin_id UUID REFERENCES users(id)`); } catch (e) { /* column may already exist */ } // Migration: Add booking_id column to tickets for multi-ticket bookings try { await (db as any).execute(sql`ALTER TABLE tickets ADD COLUMN booking_id UUID`); } catch (e) { /* column may already exist */ } await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS payments ( id UUID PRIMARY KEY, ticket_id UUID NOT NULL REFERENCES tickets(id), provider VARCHAR(50) NOT NULL, amount DECIMAL(10, 2) NOT NULL, currency VARCHAR(10) NOT NULL DEFAULT 'PYG', status VARCHAR(20) NOT NULL DEFAULT 'pending', reference VARCHAR(255), user_marked_paid_at TIMESTAMP, payer_name VARCHAR(255), paid_at TIMESTAMP, paid_by_admin_id UUID, admin_note TEXT, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL ) `); // Add payer_name column if it doesn't exist try { await (db as any).execute(sql`ALTER TABLE payments ADD COLUMN payer_name VARCHAR(255)`); } catch (e) { /* column may already exist */ } try { await (db as any).execute(sql`ALTER TABLE payments ADD COLUMN reminder_sent_at TIMESTAMP`); } catch (e) { /* column may already exist */ } // Invoices table await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS invoices ( id UUID PRIMARY KEY, payment_id UUID NOT NULL REFERENCES payments(id), user_id UUID NOT NULL REFERENCES users(id), invoice_number VARCHAR(50) NOT NULL UNIQUE, ruc_number VARCHAR(15), legal_name VARCHAR(255), amount DECIMAL(10, 2) NOT NULL, currency VARCHAR(10) NOT NULL DEFAULT 'PYG', pdf_url VARCHAR(500), status VARCHAR(20) NOT NULL DEFAULT 'generated', created_at TIMESTAMP NOT NULL ) `); await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS payment_options ( id UUID PRIMARY KEY, tpago_enabled INTEGER NOT NULL DEFAULT 0, tpago_link VARCHAR(500), tpago_instructions TEXT, tpago_instructions_es TEXT, bank_transfer_enabled INTEGER NOT NULL DEFAULT 0, bank_name VARCHAR(255), bank_account_holder VARCHAR(255), bank_account_number VARCHAR(100), bank_alias VARCHAR(100), bank_phone VARCHAR(50), bank_notes TEXT, bank_notes_es TEXT, lightning_enabled INTEGER NOT NULL DEFAULT 1, cash_enabled INTEGER NOT NULL DEFAULT 1, cash_instructions TEXT, cash_instructions_es TEXT, updated_at TIMESTAMP NOT NULL, updated_by UUID REFERENCES users(id) ) `); // Add allow_duplicate_bookings column to payment_options if it doesn't exist try { await (db as any).execute(sql`ALTER TABLE payment_options ADD COLUMN allow_duplicate_bookings INTEGER NOT NULL DEFAULT 0`); } catch (e) { /* column may already exist */ } await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS event_payment_overrides ( id UUID PRIMARY KEY, event_id UUID NOT NULL REFERENCES events(id), tpago_enabled INTEGER, tpago_link VARCHAR(500), tpago_instructions TEXT, tpago_instructions_es TEXT, bank_transfer_enabled INTEGER, bank_name VARCHAR(255), bank_account_holder VARCHAR(255), bank_account_number VARCHAR(100), bank_alias VARCHAR(100), bank_phone VARCHAR(50), bank_notes TEXT, bank_notes_es TEXT, lightning_enabled INTEGER, cash_enabled INTEGER, cash_instructions TEXT, cash_instructions_es TEXT, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL ) `); await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS contacts ( id UUID PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, message TEXT NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'new', created_at TIMESTAMP NOT NULL ) `); await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS email_subscribers ( id UUID PRIMARY KEY, email VARCHAR(255) NOT NULL UNIQUE, name VARCHAR(255), status VARCHAR(20) NOT NULL DEFAULT 'active', created_at TIMESTAMP NOT NULL ) `); await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS media ( id UUID PRIMARY KEY, file_url VARCHAR(500) NOT NULL, type VARCHAR(20) NOT NULL, related_id UUID, related_type VARCHAR(50), created_at TIMESTAMP NOT NULL ) `); await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS audit_logs ( id UUID PRIMARY KEY, user_id UUID REFERENCES users(id), action VARCHAR(100) NOT NULL, target VARCHAR(100), target_id UUID, details TEXT, timestamp TIMESTAMP NOT NULL ) `); // Email system tables await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS email_templates ( id UUID PRIMARY KEY, name VARCHAR(255) NOT NULL UNIQUE, slug VARCHAR(100) NOT NULL UNIQUE, subject VARCHAR(500) NOT NULL, subject_es VARCHAR(500), body_html TEXT NOT NULL, body_html_es TEXT, body_text TEXT, body_text_es TEXT, description TEXT, variables TEXT, is_system INTEGER NOT NULL DEFAULT 0, is_active INTEGER NOT NULL DEFAULT 1, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL ) `); await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS email_logs ( id UUID PRIMARY KEY, template_id UUID REFERENCES email_templates(id), event_id UUID REFERENCES events(id), recipient_email VARCHAR(255) NOT NULL, recipient_name VARCHAR(255), subject VARCHAR(500) NOT NULL, body_html TEXT, status VARCHAR(20) NOT NULL DEFAULT 'pending', error_message TEXT, sent_at TIMESTAMP, sent_by UUID REFERENCES users(id), created_at TIMESTAMP NOT NULL ) `); try { await (db as any).execute(sql`ALTER TABLE email_logs ADD COLUMN resend_attempts INTEGER NOT NULL DEFAULT 0`); } catch (e) { /* column may already exist */ } try { await (db as any).execute(sql`ALTER TABLE email_logs ADD COLUMN last_resent_at TIMESTAMP`); } catch (e) { /* column may already exist */ } await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS email_settings ( id UUID PRIMARY KEY, key VARCHAR(100) NOT NULL UNIQUE, value TEXT NOT NULL, updated_at TIMESTAMP NOT NULL ) `); // Site settings table await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS site_settings ( id UUID PRIMARY KEY, timezone VARCHAR(100) NOT NULL DEFAULT 'America/Asuncion', site_name VARCHAR(255) NOT NULL DEFAULT 'Spanglish', site_description TEXT, site_description_es TEXT, contact_email VARCHAR(255), contact_phone VARCHAR(50), facebook_url VARCHAR(500), instagram_url VARCHAR(500), twitter_url VARCHAR(500), linkedin_url VARCHAR(500), featured_event_id UUID REFERENCES events(id), maintenance_mode INTEGER NOT NULL DEFAULT 0, maintenance_message TEXT, maintenance_message_es TEXT, updated_at TIMESTAMP NOT NULL, updated_by UUID REFERENCES users(id) ) `); // Add featured_event_id column to site_settings if it doesn't exist try { await (db as any).execute(sql`ALTER TABLE site_settings ADD COLUMN featured_event_id UUID REFERENCES events(id)`); } catch (e) { /* column may already exist */ } // Legal pages table for admin-editable legal content await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS legal_pages ( id UUID PRIMARY KEY, slug VARCHAR(100) NOT NULL UNIQUE, title VARCHAR(255) NOT NULL, title_es VARCHAR(255), content_text TEXT NOT NULL, content_text_es TEXT, content_markdown TEXT NOT NULL, content_markdown_es TEXT, updated_at TIMESTAMP NOT NULL, updated_by UUID REFERENCES users(id), created_at TIMESTAMP NOT NULL ) `); // FAQ questions table await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS faq_questions ( id UUID PRIMARY KEY, question TEXT NOT NULL, question_es TEXT, answer TEXT NOT NULL, answer_es TEXT, enabled INTEGER NOT NULL DEFAULT 1, show_on_homepage INTEGER NOT NULL DEFAULT 0, rank INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL ) `); // Legal settings table for legal page placeholder values await (db as any).execute(sql` CREATE TABLE IF NOT EXISTS legal_settings ( id UUID PRIMARY KEY, company_name VARCHAR(255), legal_entity_name VARCHAR(255), ruc_number VARCHAR(50), company_address TEXT, company_city VARCHAR(100), company_country VARCHAR(100), support_email VARCHAR(255), legal_email VARCHAR(255), governing_law VARCHAR(255), jurisdiction_city VARCHAR(100), updated_at TIMESTAMP NOT NULL, updated_by UUID REFERENCES users(id) ) `); } console.log('Migrations completed successfully!'); process.exit(0); } migrate().catch((err) => { console.error('Migration failed:', err); process.exit(1); });