Files
Nexus/packages/db/src/reset-dispo-import.ts
T
Hartmut cd78f72f33 chore: full technical rename planarchy → capakraken
Complete rename of all technical identifiers across the codebase:

Package names (11 packages):
- @planarchy/* → @capakraken/* in all package.json, tsconfig, imports

Import statements: 277 files, 548 occurrences replaced

Database & Docker:
- PostgreSQL user/db: planarchy → capakraken
- Docker volumes: planarchy_pgdata → capakraken_pgdata
- Connection strings updated in docker-compose, .env, CI

CI/CD:
- GitHub Actions workflow: all filter commands updated
- Test database credentials updated

Infrastructure:
- Redis channel: planarchy:sse → capakraken:sse
- Logger service name: planarchy-api → capakraken-api
- Anonymization seed updated
- Start/stop/restart scripts updated

Test data:
- Seed emails: @planarchy.dev → @capakraken.dev
- E2E test credentials: all 11 spec files updated
- Email defaults: @planarchy.app → @capakraken.app
- localStorage keys: planarchy_* → capakraken_*

Documentation: 30+ .md files updated

Verification:
- pnpm install: workspace resolution works
- TypeScript: only pre-existing TS2589 (no new errors)
- Engine: 310/310 tests pass
- Staffing: 37/37 tests pass

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-27 13:18:09 +01:00

192 lines
4.8 KiB
TypeScript

import { execFileSync } from "node:child_process";
import { mkdirSync } from "node:fs";
import { resolve } from "node:path";
import { hash } from "@node-rs/argon2";
import { SystemRole } from "@capakraken/shared";
import { PrismaClient } from "@prisma/client";
import { loadWorkspaceEnv, resolveWorkspacePath } from "./load-workspace-env.js";
loadWorkspaceEnv();
const prisma = new PrismaClient();
const DEFAULT_BACKUP_DIR = resolveWorkspacePath("packages", "db", "backups");
interface ResetOptions {
force: boolean;
skipBackup: boolean;
backupDir: string;
adminEmail: string;
adminPassword: string;
adminName: string;
}
function parseArgs(argv: string[]): ResetOptions {
const options: ResetOptions = {
force: false,
skipBackup: false,
backupDir: DEFAULT_BACKUP_DIR,
adminEmail: "admin@capakraken.dev",
adminPassword: "admin123",
adminName: "Planarchy Admin",
};
for (let index = 0; index < argv.length; index += 1) {
const argument = argv[index];
if (argument === "--force") {
options.force = true;
continue;
}
if (argument === "--skip-backup") {
options.skipBackup = true;
continue;
}
if (argument === "--backup-dir") {
options.backupDir = resolve(argv[index + 1] ?? DEFAULT_BACKUP_DIR);
index += 1;
continue;
}
if (argument === "--admin-email") {
options.adminEmail = argv[index + 1] ?? options.adminEmail;
index += 1;
continue;
}
if (argument === "--admin-password") {
options.adminPassword = argv[index + 1] ?? options.adminPassword;
index += 1;
continue;
}
if (argument === "--admin-name") {
options.adminName = argv[index + 1] ?? options.adminName;
index += 1;
}
}
return options;
}
function createTimestamp() {
return new Date().toISOString().replace(/[:.]/g, "-");
}
function quoteIdentifier(identifier: string): string {
return `"${identifier.replace(/"/g, "\"\"")}"`;
}
function createDatabaseBackup(databaseUrl: string, backupDir: string): string {
mkdirSync(backupDir, { recursive: true });
const backupPath = resolve(backupDir, `dispo-reset-${createTimestamp()}.dump`);
execFileSync(
"pg_dump",
["--format=custom", "--file", backupPath, databaseUrl],
{
stdio: "inherit",
env: process.env,
},
);
return backupPath;
}
async function listPublicTables() {
return prisma.$queryRaw<Array<{ tablename: string }>>`
SELECT tablename
FROM pg_tables
WHERE schemaname = 'public'
AND tablename <> '_prisma_migrations'
ORDER BY tablename ASC
`;
}
async function truncatePublicTables() {
const tables = await listPublicTables();
if (tables.length === 0) {
return [];
}
const quotedTables = tables.map((table) => quoteIdentifier(table.tablename)).join(", ");
await prisma.$executeRawUnsafe(`TRUNCATE TABLE ${quotedTables} RESTART IDENTITY CASCADE`);
return tables.map((table) => table.tablename);
}
async function bootstrapPlatform(adminEmail: string, adminPassword: string, adminName: string) {
const passwordHash = await hash(adminPassword);
const admin = await prisma.user.create({
data: {
email: adminEmail,
name: adminName,
passwordHash,
systemRole: SystemRole.ADMIN,
},
});
await prisma.systemSettings.upsert({
where: { id: "singleton" },
update: {
vacationDefaultDays: 28,
},
create: {
id: "singleton",
vacationDefaultDays: 28,
},
});
return admin;
}
async function main() {
const options = parseArgs(process.argv.slice(2));
const databaseUrl = process.env.DATABASE_URL;
if (!options.force) {
throw new Error("Refusing to reset the database without --force.");
}
if (!databaseUrl) {
throw new Error("DATABASE_URL is not configured.");
}
let backupPath: string | null = null;
if (options.skipBackup) {
console.warn("Skipping pg_dump backup because --skip-backup was provided.");
} else {
try {
backupPath = createDatabaseBackup(databaseUrl, options.backupDir);
console.log(`Backup created at ${backupPath}`);
} catch (error) {
throw new Error(
`Backup failed. Install pg_dump or rerun with --skip-backup if this is an intentional disposable environment.\n${String(error)}`,
);
}
}
const truncatedTables = await truncatePublicTables();
console.log(`Truncated ${truncatedTables.length} public tables.`);
const admin = await bootstrapPlatform(options.adminEmail, options.adminPassword, options.adminName);
console.log(`Bootstrap admin created: ${admin.email}`);
if (backupPath) {
console.log(`Database backup: ${backupPath}`);
}
console.log("Dispo import reset/bootstrap complete.");
}
main()
.catch((error) => {
console.error(error);
process.exitCode = 1;
})
.finally(async () => {
await prisma.$disconnect();
});