feat: implement database reset and demo population scripts with tests

This commit is contained in:
Paul Schoeneck committed 2026-11-03 08:17:53 +01:00
1 parent 4df14584f2
commit a2e5184b3e
4 files changed
+214 -1

No files matched your search

+69
View File
@@ -0,0 +1,69 @@
import "dotenv/config";
import { spawnSync } from "node:child_process";
import { createRequire } from "node:module";
import { fileURLToPath } from "node:url";
import { db } from "../src/lib/db";
async function reset() {
const [mode, confirmation, ...extra] = process.argv.slice(2);
if (!["clear", "demo"].includes(mode) || confirmation !== "--confirm-reset" || extra.length) {
throw new Error("Aufruf: npm run db:clear|db:reset -- --confirm-reset. Alle Instanzdaten und Zugaenge werden geloescht!");
}
if (!process.env.DATABASE_URL) throw new Error("DATABASE_URL fehlt. Zuerst npm run configure ausfuehren.");
const target = new URL(process.env.DATABASE_URL);
if (!["postgres:", "postgresql:"].includes(target.protocol)
|| !["localhost", "127.0.0.1", "[::1]"].includes(target.hostname)
|| !target.pathname.slice(1) || ["/postgres", "/template0", "/template1"].includes(target.pathname)) {
throw new Error("Reset nur fuer eine ausdruecklich konfigurierte lokale Anwendungsdatenbank erlaubt.");
}
if (mode === "demo" && process.env.BOOTSTRAP_PASSWORD !== undefined && process.env.BOOTSTRAP_PASSWORD.length < 12) {
throw new Error("BOOTSTRAP_PASSWORD muss mindestens 12 Zeichen haben. Es wurde nichts geloescht.");
}
console.log(`ACHTUNG: Alle Instanzdaten einschliesslich Konten, Sitzungen und Audit-Verlauf in ${target.hostname}:${target.port || "5432"}${target.pathname} werden geloescht.`);
const client = await db.connect();
try {
await client.query("BEGIN");
await client.query("SET LOCAL lock_timeout = '5s'");
await client.query("SELECT pg_advisory_xact_lock(724139002)");
await client.query("ALTER TABLE public.audit_log DISABLE TRIGGER audit_immutable");
await client.query(`TRUNCATE TABLE
public.applications, public.admission_rounds, public.application_limits,
public.attendance, public.day_overrides, public.weekly_schedules,
public.pickup_contacts, public.alone_rules, public.children, public.audit_log,
public.sessions, public.login_attempts, public.staff, public.pickup_slots
RESTART IDENTITY`);
await client.query("ALTER SEQUENCE public.attendance_revision RESTART WITH 1");
await client.query("ALTER TABLE public.audit_log ENABLE TRIGGER audit_immutable");
await client.query(`INSERT INTO public.pickup_slots(starts_at, ends_at) VALUES
('14:00', '14:15'), ('15:00', '15:15'), ('16:00', '16:15'), ('17:00', '17:15')`);
await client.query("COMMIT");
console.log("Instanzdaten geloescht. Schema, Migrationen und vier Standardslots sind bereit.");
} catch (error) {
await client.query("ROLLBACK");
throw error;
} finally {
client.release();
}
if (mode === "clear") {
console.log("Keine Konten vorhanden. Neuer Zugang: npm run db:setup. Demodaten: npm run db:seed.");
return;
}
const tsx = createRequire(import.meta.url).resolve("tsx/cli");
for (const script of ["setup.ts", "seed-demo.ts"]) {
const result = spawnSync(process.execPath, [tsx, fileURLToPath(new URL(script, import.meta.url))], {
cwd: fileURLToPath(new URL("../", import.meta.url)),
env: process.env,
stdio: "inherit",
});
if (result.error) throw result.error;
if (result.status !== 0) {
throw new Error(`${script} fehlgeschlagen. Daten wurden bereits geleert; db:setup und db:seed nach Fehlerbehebung erneut ausfuehren.`);
}
}
console.log("Demo-Instanz bereit. Mit dem neu ausgegebenen Zugang anmelden; alte Sitzungen sind ungueltig.");
}
reset().catch((error: unknown) => {
console.error("Instanz-Reset fehlgeschlagen:", error);
process.exitCode = 1;
}).finally(() => db.end());