feat(platform-api): split lightweight platform service from Lab image
New platform/api/ service owns students, cohorts, curriculum, progress, scenario attempts, and student/admin sessions. No k3s, no --privileged, no terminal runtime — starts instantly, unlike the old identity service which booted an unused k3s cluster just to serve roster/progress APIs. Provisioner calls (student and admin) are consolidated here too, in provisioner-client.js, so it's the only service that talks to the Provisioner directly.
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
// Postgres connection + migration runner — same pattern as platform/provisioner/src/db.js
|
||||
// (check-before-alter idempotency, tracked in schema_migrations). Same database
|
||||
// (rootnode_platform) as the Provisioner, different table set: this service owns
|
||||
// students/cohorts/enrollments/curriculum_assignments/progress/scenario_attempts/
|
||||
// exam_sessions; the Provisioner owns lab_hosts/environments/environment_events. Both connect
|
||||
// to the same Postgres instance — scenario_attempts.environment_id is a real FK across that
|
||||
// shared boundary, which Postgres allows regardless of which service's migrations created
|
||||
// which table.
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { Pool } = require('pg');
|
||||
|
||||
const MIGRATIONS_DIR = path.join(__dirname, '../migrations');
|
||||
|
||||
let _pool = null;
|
||||
function getPool() {
|
||||
if (_pool) return _pool;
|
||||
_pool = new Pool({
|
||||
connectionString: process.env.PLATFORM_DB_URL || 'postgres://postgres:postgres@localhost:5433/rootnode_platform',
|
||||
});
|
||||
return _pool;
|
||||
}
|
||||
|
||||
async function runMigrations() {
|
||||
const pool = getPool();
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
version TEXT PRIMARY KEY,
|
||||
applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
)
|
||||
`);
|
||||
|
||||
const applied = new Set(
|
||||
(await pool.query('SELECT version FROM schema_migrations')).rows.map((r) => r.version)
|
||||
);
|
||||
|
||||
const files = fs.readdirSync(MIGRATIONS_DIR).filter((f) => f.endsWith('.sql')).sort();
|
||||
const ranNow = [];
|
||||
for (const file of files) {
|
||||
if (applied.has(file)) continue;
|
||||
const sql = fs.readFileSync(path.join(MIGRATIONS_DIR, file), 'utf8');
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
await client.query(sql);
|
||||
await client.query('INSERT INTO schema_migrations (version) VALUES ($1)', [file]);
|
||||
await client.query('COMMIT');
|
||||
ranNow.push(file);
|
||||
} catch (e) {
|
||||
await client.query('ROLLBACK');
|
||||
throw new Error(`Migration ${file} failed: ${e.message}`);
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
return ranNow;
|
||||
}
|
||||
|
||||
module.exports = { getPool, runMigrations };
|
||||
Reference in New Issue
Block a user