"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.pool = void 0; exports.runMigrations = runMigrations; const pg_1 = require("pg"); const pool = new pg_1.Pool({ connectionString: process.env.DATABASE_URL, }); exports.pool = pool; async function runMigrations() { const client = await pool.connect(); try { await client.query('BEGIN'); // Users table await client.query(` CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, role VARCHAR(50) DEFAULT 'admin', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); `); // Stores table await client.query(` CREATE TABLE IF NOT EXISTS stores ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, slug VARCHAR(255) UNIQUE NOT NULL, dutchie_url TEXT NOT NULL, active BOOLEAN DEFAULT true, scrape_enabled BOOLEAN DEFAULT true, last_scraped_at TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); `); // Categories table (shop, brands, specials) await client.query(` CREATE TABLE IF NOT EXISTS categories ( id SERIAL PRIMARY KEY, store_id INTEGER REFERENCES stores(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, slug VARCHAR(255) NOT NULL, dutchie_url TEXT NOT NULL, scrape_enabled BOOLEAN DEFAULT true, last_scraped_at TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(store_id, slug) ); `); // Products table await client.query(` CREATE TABLE IF NOT EXISTS products ( id SERIAL PRIMARY KEY, store_id INTEGER REFERENCES stores(id) ON DELETE CASCADE, category_id INTEGER REFERENCES categories(id) ON DELETE SET NULL, dutchie_product_id VARCHAR(255), name VARCHAR(500) NOT NULL, slug VARCHAR(500), description TEXT, price DECIMAL(10, 2), original_price DECIMAL(10, 2), strain_type VARCHAR(100), thc_percentage DECIMAL(5, 2), cbd_percentage DECIMAL(5, 2), brand VARCHAR(255), weight VARCHAR(100), image_url TEXT, local_image_path TEXT, dutchie_url TEXT NOT NULL, in_stock BOOLEAN DEFAULT true, is_special BOOLEAN DEFAULT false, metadata JSONB, first_seen_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, last_seen_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(store_id, dutchie_product_id) ); `); // Campaigns table await client.query(` CREATE TABLE IF NOT EXISTS campaigns ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, slug VARCHAR(255) UNIQUE NOT NULL, description TEXT, display_style VARCHAR(50) DEFAULT 'grid', active BOOLEAN DEFAULT true, start_date TIMESTAMP, end_date TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); `); // Campaign products (many-to-many with ordering) await client.query(` CREATE TABLE IF NOT EXISTS campaign_products ( id SERIAL PRIMARY KEY, campaign_id INTEGER REFERENCES campaigns(id) ON DELETE CASCADE, product_id INTEGER REFERENCES products(id) ON DELETE CASCADE, display_order INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(campaign_id, product_id) ); `); // Click tracking await client.query(` CREATE TABLE IF NOT EXISTS clicks ( id SERIAL PRIMARY KEY, product_id INTEGER REFERENCES products(id) ON DELETE CASCADE, campaign_id INTEGER REFERENCES campaigns(id) ON DELETE SET NULL, ip_address VARCHAR(45), user_agent TEXT, referrer TEXT, clicked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); `); // Create index on clicked_at for analytics queries await client.query(` CREATE INDEX IF NOT EXISTS idx_clicks_clicked_at ON clicks(clicked_at); CREATE INDEX IF NOT EXISTS idx_clicks_product_id ON clicks(product_id); CREATE INDEX IF NOT EXISTS idx_clicks_campaign_id ON clicks(campaign_id); `); // Proxies table await client.query(` CREATE TABLE IF NOT EXISTS proxies ( id SERIAL PRIMARY KEY, host VARCHAR(255) NOT NULL, port INTEGER NOT NULL, protocol VARCHAR(10) NOT NULL, username VARCHAR(255), password VARCHAR(255), active BOOLEAN DEFAULT true, is_anonymous BOOLEAN DEFAULT false, last_tested_at TIMESTAMP, test_result VARCHAR(50), response_time_ms INTEGER, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(host, port, protocol) ); `); // Settings table await client.query(` CREATE TABLE IF NOT EXISTS settings ( key VARCHAR(255) PRIMARY KEY, value TEXT NOT NULL, description TEXT, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); `); // Insert default settings await client.query(` INSERT INTO settings (key, value, description) VALUES ('scrape_interval_hours', '4', 'How often to scrape stores (in hours)'), ('scrape_specials_time', '00:01', 'Time to scrape specials daily (HH:MM in 24h format)'), ('analytics_retention_days', '365', 'How many days to keep analytics data'), ('proxy_timeout_ms', '3000', 'Proxy timeout in milliseconds'), ('proxy_test_url', 'https://httpbin.org/ip', 'URL to test proxies against') ON CONFLICT (key) DO NOTHING; `); await client.query('COMMIT'); console.log('✅ Migrations completed successfully'); } catch (error) { await client.query('ROLLBACK'); console.error('❌ Migration failed:', error); throw error; } finally { client.release(); } } // Run migrations if this file is executed directly if (require.main === module) { runMigrations() .then(() => process.exit(0)) .catch(() => process.exit(1)); }