3da1b4242c
Feature 1.2: Python FastAPI microservice wrapping nba_api - GET /stats/season-avg, /stats/last-n, /stats/splits, /players/search - Redis caching (24hr/1hr/6hr/7day), 0.6s rate limiting, PRA derived stat - 27 Python tests passing Feature 1.4: Complete Supabase database schema - 6 tables: users, picks, scan_sessions, bets, outcomes, performance - RLS enabled on all tables with auth.uid() policies - 3 triggers: auto-create user, updated_at, scan count reset - 37 schema validation tests passing - Migration SQL ready, pending manual apply (WSL2 DNS blocker) Total: 92 tests (65 Node.js + 27 Python), all passing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
137 lines
3.6 KiB
JavaScript
137 lines
3.6 KiB
JavaScript
require('dotenv').config();
|
|
const fs = require('fs');
|
|
const postgres = require('postgres');
|
|
|
|
const DB_PASSWORD = process.env.SUPABASE_DB_PASSWORD;
|
|
const PROJECT_REF = process.env.SUPABASE_URL.match(/https:\/\/(.+?)\.supabase/)[1];
|
|
|
|
// Use session mode (port 5432) for DDL statements
|
|
const sql = postgres({
|
|
host: `aws-0-us-east-1.pooler.supabase.com`,
|
|
port: 5432,
|
|
database: 'postgres',
|
|
username: `postgres.${PROJECT_REF}`,
|
|
password: DB_PASSWORD,
|
|
ssl: { rejectUnauthorized: false },
|
|
connection: { application_name: 'betonblk-migration' },
|
|
prepare: false,
|
|
});
|
|
|
|
async function applyMigration() {
|
|
// Test connection first
|
|
try {
|
|
const result = await sql`SELECT current_database(), current_user`;
|
|
console.log('Connected:', result[0]);
|
|
} catch (err) {
|
|
console.error('Connection failed:', err.message);
|
|
|
|
// Try transaction mode as fallback
|
|
console.log('Trying transaction mode on port 6543...');
|
|
const sql2 = postgres({
|
|
host: `aws-0-us-east-1.pooler.supabase.com`,
|
|
port: 6543,
|
|
database: 'postgres',
|
|
username: `postgres.${PROJECT_REF}`,
|
|
password: DB_PASSWORD,
|
|
ssl: { rejectUnauthorized: false },
|
|
prepare: false,
|
|
});
|
|
|
|
try {
|
|
const result2 = await sql2`SELECT current_database(), current_user`;
|
|
console.log('Connected via transaction mode:', result2[0]);
|
|
await runMigration(sql2);
|
|
await sql2.end();
|
|
return;
|
|
} catch (err2) {
|
|
console.error('Transaction mode also failed:', err2.message);
|
|
|
|
// Try direct connection
|
|
console.log('Trying direct connection...');
|
|
const sql3 = postgres({
|
|
host: `db.${PROJECT_REF}.supabase.co`,
|
|
port: 5432,
|
|
database: 'postgres',
|
|
username: 'postgres',
|
|
password: DB_PASSWORD,
|
|
ssl: { rejectUnauthorized: false },
|
|
prepare: false,
|
|
});
|
|
|
|
try {
|
|
const result3 = await sql3`SELECT current_database(), current_user`;
|
|
console.log('Connected directly:', result3[0]);
|
|
await runMigration(sql3);
|
|
await sql3.end();
|
|
return;
|
|
} catch (err3) {
|
|
console.error('Direct connection failed:', err3.message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
await runMigration(sql);
|
|
await sql.end();
|
|
}
|
|
|
|
async function runMigration(db) {
|
|
const migration = fs.readFileSync('supabase/migrations/001_initial_schema.sql', 'utf8');
|
|
const statements = splitStatements(migration);
|
|
|
|
console.log(`Applying ${statements.length} statements...`);
|
|
let ok = 0;
|
|
let errors = 0;
|
|
|
|
for (let i = 0; i < statements.length; i++) {
|
|
const stmt = statements[i].trim();
|
|
if (!stmt || stmt.startsWith('--')) continue;
|
|
try {
|
|
await db.unsafe(stmt);
|
|
ok++;
|
|
console.log(`[${i + 1}/${statements.length}] OK`);
|
|
} catch (err) {
|
|
errors++;
|
|
console.error(`[${i + 1}/${statements.length}] ERROR: ${err.message}`);
|
|
console.error(`Statement: ${stmt.substring(0, 120)}...`);
|
|
}
|
|
}
|
|
|
|
console.log(`Migration complete. ${ok} succeeded, ${errors} failed.`);
|
|
}
|
|
|
|
function splitStatements(sqlText) {
|
|
const results = [];
|
|
let current = '';
|
|
let inDollarQuote = false;
|
|
|
|
const lines = sqlText.split('\n');
|
|
for (const line of lines) {
|
|
const trimmed = line.trim();
|
|
if (trimmed.startsWith('--')) {
|
|
current += line + '\n';
|
|
continue;
|
|
}
|
|
|
|
const dollarCount = (line.match(/\$\$/g) || []).length;
|
|
if (dollarCount % 2 !== 0) {
|
|
inDollarQuote = !inDollarQuote;
|
|
}
|
|
|
|
current += line + '\n';
|
|
|
|
if (!inDollarQuote && trimmed.endsWith(';')) {
|
|
results.push(current);
|
|
current = '';
|
|
}
|
|
}
|
|
|
|
if (current.trim()) {
|
|
results.push(current);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
applyMigration().catch(console.error);
|