#!/usr/bin/env node // Seeds realistic academy data for the Phase 4.1 §20 destructive restore drill — an owner, an // admin instructor, a cohort, 3 students, progress, attempts, an assessment definition + // session, and an achievement. Uses the REAL Platform API over HTTP for everything that // doesn't need a live Provisioner/k3s Lab (owner login, instructor/cohort/student/assessment // creation — genuinely exercises the real RBAC/creation code paths), and direct SQL inserts // only for progress/attempts/achievements (those need real scenario activity in the full app; // re-running the whole learning engine here would just re-prove what e2e-phase3b.js already // proves — this drill is about DATA DURABILITY, not re-testing scenario validation). // // Usage: PLATFORM_API_URL=http://localhost:PORT PLATFORM_DB_URL=postgres://... node seed-realistic-data.js const { Pool } = require('../../platform/api/node_modules/pg'); const API = process.env.PLATFORM_API_URL || 'http://localhost:4390'; const DB_URL = process.env.PLATFORM_DB_URL; if (!DB_URL) { console.error('Set PLATFORM_DB_URL'); process.exit(1); } function makeSession() { const jar = new Map(); function cookieHeader() { return [...jar].map(([k, v]) => `${k}=${v}`).join('; '); } function absorb(res) { const cookies = typeof res.headers.getSetCookie === 'function' ? res.headers.getSetCookie() : []; for (const c of cookies) { const [pair] = c.split(';'); const eq = pair.indexOf('='); if (eq === -1) continue; jar.set(pair.slice(0, eq).trim(), pair.slice(eq + 1).trim()); } } return { async req(method, path, body) { const headers = { 'Content-Type': 'application/json' }; if (cookieHeader()) headers.Cookie = cookieHeader(); if (!['GET', 'HEAD'].includes(method)) headers['X-CSRF-Token'] = jar.get('rn_csrf_i') || ''; const res = await fetch(`${API}${path}`, { method, headers, body: body ? JSON.stringify(body) : undefined }); absorb(res); const text = await res.text(); let json; try { json = JSON.parse(text); } catch { json = text; } if (!res.ok) throw new Error(`${method} ${path} -> ${res.status}: ${JSON.stringify(json)}`); return json; }, }; } async function main() { const owner = makeSession(); await owner.req('POST', '/api/auth/instructor/login', { email: 'drill-owner@rootnode.test', password: 'DrillOwnerPass!2026' }); console.log(' owner login ok'); const admin = await owner.req('POST', '/api/admin/instructors', { email: 'drill-admin@rootnode.test', display_name: 'Drill Admin', password: 'DrillAdminPass!2026', role: 'admin', }); console.log(' admin instructor created:', admin.instructor_id); const cohort = await owner.req('POST', '/api/admin/cohorts', { cohort_id: 'drill-cohort-01', name: 'Restore Drill Cohort' }); console.log(' cohort created'); const students = []; for (const name of ['Drill Alice', 'Drill Bob', 'Drill Carlos']) { const s = await owner.req('POST', '/api/admin/students', { display_name: name, cohort_id: 'drill-cohort-01' }); students.push(s.student_id); } console.log(' 3 students rostered and enrolled:', students.join(', ')); const assessment = await owner.req('POST', '/api/admin/assessments', { id: 'drill-exam-01', type: 'exam', title: 'Restore Drill Exam', duration_minutes: 30, hint_policy: 'disabled', pool_spec: { required: ['pod-basics-mcq'], random: [] }, }); console.log(' assessment definition created:', assessment.assessment_definition_id || 'drill-exam-01'); // Direct SQL for progress/attempts/achievements — see file header for why. const pool = new Pool({ connectionString: DB_URL }); for (const studentId of students) { // environment_id is a real FK to the Provisioner's environments table (nullable) — left // NULL here rather than faking a row that doesn't exist, same as how a genuinely old // attempt whose environment has long since been cleaned up would look in real data. await pool.query( `INSERT INTO scenario_attempts (student_id, scenario_id, status, result, started_at, completed_at, validation_attempt_count, validation_pass_count) VALUES ($1, 'pod-basics-mcq', 'completed', 'passed', now() - interval '1 hour', now(), 1, 1)`, [studentId] ); await pool.query( `INSERT INTO achievements (student_id, achievement_type, stage_id, achieved_at) VALUES ($1, 'stage_completed', 'foundation', now())`, [studentId] ); } await pool.end(); console.log(' progress/attempts/achievements seeded for all 3 students'); console.log('\nSeed complete.'); } main().catch((e) => { console.error('SEED FAILED:', e.message); process.exit(1); });