EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
migrate-pax8-tables.js
Go to the documentation of this file.
1/**
2 * Pax8 Integration Database Migration
3 * Creates missing tables for Pax8 Microsoft 365 integration
4 *
5 * Tables created:
6 * - pax8_customers: Maps local customers to Pax8 companies
7 * - pax8_products: Caches Pax8 product catalog
8 *
9 * Also adds missing columns to pax8_config
10 */
11
12const { Pool } = require('pg');
13const fs = require('fs');
14
15const envContent = fs.readFileSync('.env', 'utf8');
16const envVars = {};
17envContent.split('\n').forEach(line => {
18 const match = line.match(/^([^=]+)=(.*)$/);
19 if (match) envVars[match[1]] = match[2];
20});
21
22const dbUrl = envVars.DATABASE_URL.replace('sslmode=require', 'sslmode=no-verify');
23const pool = new Pool({ connectionString: dbUrl, ssl: { rejectUnauthorized: false } });
24
25async function migrate() {
26 const client = await pool.connect();
27
28 try {
29 await client.query('BEGIN');
30
31 console.log('Starting Pax8 database migration...\n');
32
33 // 1. Create pax8_customers table
34 console.log('Creating pax8_customers table...');
35 await client.query(`
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')),
44 sync_error TEXT,
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)
49 )
50 `);
51 console.log('✅ pax8_customers table created');
52
53 // Create index for faster lookups
54 await client.query(`
55 CREATE INDEX IF NOT EXISTS idx_pax8_customers_tenant
56 ON pax8_customers(tenant_id)
57 `);
58 await client.query(`
59 CREATE INDEX IF NOT EXISTS idx_pax8_customers_customer
60 ON pax8_customers(customer_id)
61 `);
62 await client.query(`
63 CREATE INDEX IF NOT EXISTS idx_pax8_customers_pax8_company
64 ON pax8_customers(pax8_company_id)
65 `);
66 console.log('✅ Indexes created for pax8_customers');
67
68 // 2. Create pax8_products table
69 console.log('\nCreating pax8_products table...');
70 await client.query(`
71 CREATE TABLE IF NOT EXISTS pax8_products (
72 id SERIAL PRIMARY KEY,
73 pax8_product_id TEXT UNIQUE NOT NULL,
74 name TEXT NOT NULL,
75 vendor TEXT,
76 category TEXT,
77 description TEXT,
78 sku TEXT,
79 unit_price DECIMAL(10, 2),
80 term TEXT,
81 pricing JSONB,
82 metadata JSONB,
83 is_active BOOLEAN DEFAULT true,
84 last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
85 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
86 )
87 `);
88 console.log('✅ pax8_products table created');
89
90 // Create indexes for product searches
91 await client.query(`
92 CREATE INDEX IF NOT EXISTS idx_pax8_products_vendor
93 ON pax8_products(vendor)
94 `);
95 await client.query(`
96 CREATE INDEX IF NOT EXISTS idx_pax8_products_category
97 ON pax8_products(category)
98 `);
99 await client.query(`
100 CREATE INDEX IF NOT EXISTS idx_pax8_products_active
101 ON pax8_products(is_active)
102 `);
103 console.log('✅ Indexes created for pax8_products');
104
105 // 3. Add missing columns to pax8_config if they don't exist
106 console.log('\nChecking pax8_config table for missing columns...');
107
108 // Check if pax8_company_id exists
109 const companyIdCheck = await client.query(`
110 SELECT column_name
111 FROM information_schema.columns
112 WHERE table_name = 'pax8_config' AND column_name = 'pax8_company_id'
113 `);
114
115 if (companyIdCheck.rows.length === 0) {
116 await client.query(`
117 ALTER TABLE pax8_config
118 ADD COLUMN pax8_company_id TEXT
119 `);
120 console.log('✅ Added pax8_company_id column to pax8_config');
121 } else {
122 console.log('✓ pax8_company_id column already exists');
123 }
124
125 // Check if last_sync_status exists
126 const syncStatusCheck = await client.query(`
127 SELECT column_name
128 FROM information_schema.columns
129 WHERE table_name = 'pax8_config' AND column_name = 'last_sync_status'
130 `);
131
132 if (syncStatusCheck.rows.length === 0) {
133 await client.query(`
134 ALTER TABLE pax8_config
135 ADD COLUMN last_sync_status TEXT DEFAULT 'not_configured'
136 `);
137 console.log('✅ Added last_sync_status column to pax8_config');
138 } else {
139 console.log('✓ last_sync_status column already exists');
140 }
141
142 // Check if sync_error exists
143 const syncErrorCheck = await client.query(`
144 SELECT column_name
145 FROM information_schema.columns
146 WHERE table_name = 'pax8_config' AND column_name = 'sync_error'
147 `);
148
149 if (syncErrorCheck.rows.length === 0) {
150 await client.query(`
151 ALTER TABLE pax8_config
152 ADD COLUMN sync_error TEXT
153 `);
154 console.log('✅ Added sync_error column to pax8_config');
155 } else {
156 console.log('✓ sync_error column already exists');
157 }
158
159 await client.query('COMMIT');
160
161 console.log('\n✅ Migration completed successfully!\n');
162
163 // Verify tables exist
164 const verification = await client.query(`
165 SELECT table_name
166 FROM information_schema.tables
167 WHERE table_schema = 'public'
168 AND (table_name LIKE 'pax8%' OR table_name LIKE '%office365%')
169 ORDER BY table_name
170 `);
171
172 console.log('Pax8/Office365 tables:');
173 verification.rows.forEach(row => {
174 console.log(' ✓', row.table_name);
175 });
176
177 } catch (err) {
178 await client.query('ROLLBACK');
179 console.error('\n❌ Migration failed:', err.message);
180 throw err;
181 } finally {
182 client.release();
183 await pool.end();
184 }
185}
186
187// Run migration
188migrate()
189 .then(() => {
190 console.log('\n✅ Database ready forPax8 integration');
191 process.exit(0);
192 })
193 .catch(err => {
194 console.error('\n❌ Migration error:', err);
195 process.exit(1);
196 });