2 * Pax8 Integration Database Migration
3 * Creates missing tables for Pax8 Microsoft 365 integration
6 * - pax8_customers: Maps local customers to Pax8 companies
7 * - pax8_products: Caches Pax8 product catalog
9 * Also adds missing columns to pax8_config
12const { Pool } = require('pg');
13const fs = require('fs');
15const envContent = fs.readFileSync('.env', 'utf8');
17envContent.split('\n').forEach(line => {
18 const match = line.match(/^([^=]+)=(.*)$/);
19 if (match) envVars[match[1]] = match[2];
22const dbUrl = envVars.DATABASE_URL.replace('sslmode=require', 'sslmode=no-verify');
23const pool = new Pool({ connectionString: dbUrl, ssl: { rejectUnauthorized: false } });
25async function migrate() {
26 const client = await pool.connect();
29 await client.query('BEGIN');
31 console.log('Starting Pax8 database migration...\n');
33 // 1. Create pax8_customers table
34 console.log('Creating pax8_customers table...');
36 CREATE TABLE IF NOT EXISTS pax8_customers (
37 id SERIAL PRIMARY KEY,
38 tenant_id UUID NOT NULL REFERENCES tenants(tenant_id) ON DELETE CASCADE,
39 customer_id INTEGER NOT NULL REFERENCES customers(customer_id) ON DELETE CASCADE,
40 pax8_company_id TEXT NOT NULL,
41 pax8_company_name TEXT,
42 last_synced_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
43 sync_status TEXT DEFAULT 'pending' CHECK (sync_status IN ('pending', 'synced', 'failed')),
45 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
46 updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
47 UNIQUE(tenant_id, customer_id),
48 UNIQUE(tenant_id, pax8_company_id)
51 console.log('✅ pax8_customers table created');
53 // Create index for faster lookups
55 CREATE INDEX IF NOT EXISTS idx_pax8_customers_tenant
56 ON pax8_customers(tenant_id)
59 CREATE INDEX IF NOT EXISTS idx_pax8_customers_customer
60 ON pax8_customers(customer_id)
63 CREATE INDEX IF NOT EXISTS idx_pax8_customers_pax8_company
64 ON pax8_customers(pax8_company_id)
66 console.log('✅ Indexes created for pax8_customers');
68 // 2. Create pax8_products table
69 console.log('\nCreating pax8_products table...');
71 CREATE TABLE IF NOT EXISTS pax8_products (
72 id SERIAL PRIMARY KEY,
73 pax8_product_id TEXT UNIQUE NOT NULL,
79 unit_price DECIMAL(10, 2),
83 is_active BOOLEAN DEFAULT true,
84 last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
85 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
88 console.log('✅ pax8_products table created');
90 // Create indexes for product searches
92 CREATE INDEX IF NOT EXISTS idx_pax8_products_vendor
93 ON pax8_products(vendor)
96 CREATE INDEX IF NOT EXISTS idx_pax8_products_category
97 ON pax8_products(category)
100 CREATE INDEX IF NOT EXISTS idx_pax8_products_active
101 ON pax8_products(is_active)
103 console.log('✅ Indexes created for pax8_products');
105 // 3. Add missing columns to pax8_config if they don't exist
106 console.log('\nChecking pax8_config table for missing columns...');
108 // Check if pax8_company_id exists
109 const companyIdCheck = await client.query(`
111 FROM information_schema.columns
112 WHERE table_name = 'pax8_config' AND column_name = 'pax8_company_id'
115 if (companyIdCheck.rows.length === 0) {
117 ALTER TABLE pax8_config
118 ADD COLUMN pax8_company_id TEXT
120 console.log('✅ Added pax8_company_id column to pax8_config');
122 console.log('✓ pax8_company_id column already exists');
125 // Check if last_sync_status exists
126 const syncStatusCheck = await client.query(`
128 FROM information_schema.columns
129 WHERE table_name = 'pax8_config' AND column_name = 'last_sync_status'
132 if (syncStatusCheck.rows.length === 0) {
134 ALTER TABLE pax8_config
135 ADD COLUMN last_sync_status TEXT DEFAULT 'not_configured'
137 console.log('✅ Added last_sync_status column to pax8_config');
139 console.log('✓ last_sync_status column already exists');
142 // Check if sync_error exists
143 const syncErrorCheck = await client.query(`
145 FROM information_schema.columns
146 WHERE table_name = 'pax8_config' AND column_name = 'sync_error'
149 if (syncErrorCheck.rows.length === 0) {
151 ALTER TABLE pax8_config
152 ADD COLUMN sync_error TEXT
154 console.log('✅ Added sync_error column to pax8_config');
156 console.log('✓ sync_error column already exists');
159 await client.query('COMMIT');
161 console.log('\n✅ Migration completed successfully!\n');
163 // Verify tables exist
164 const verification = await client.query(`
166 FROM information_schema.tables
167 WHERE table_schema = 'public'
168 AND (table_name LIKE 'pax8%' OR table_name LIKE '%office365%')
172 console.log('Pax8/Office365 tables:');
173 verification.rows.forEach(row => {
174 console.log(' ✓', row.table_name);
178 await client.query('ROLLBACK');
179 console.error('\n❌ Migration failed:', err.message);
190 console.log('\n✅ Database ready forPax8 integration');
194 console.error('\n❌ Migration error:', err);