Add setup and restore flow

This commit is contained in:
michael committed 2026-08-06 08:22:33 +02:00
1 parent 19e4621aeb
commit 72229cf14c
15 files changed
+511 -94

No files matched your search

+32 -37
View File
@@ -1,42 +1,25 @@
import { query } from "./db.js";
import { hashPassword } from "./auth.js";
async function ensureMigrationAdmin() {
const username = process.env.ADMIN_USERNAME?.trim() || "admin";
const password = process.env.ADMIN_PASSWORD ?? "admin";
const displayName = process.env.ADMIN_DISPLAY_NAME?.trim() || "Admin";
const existingUsers = await query<{ count: string }>("SELECT COUNT(*) AS count FROM users;");
if (Number(existingUsers.rows[0]?.count ?? 0) === 0) {
await query(
`
INSERT INTO users (username, display_name, password_hash, role)
VALUES ($1, $2, $3, 'admin');
`,
[username, displayName, await hashPassword(password)]
);
if (!process.env.ADMIN_PASSWORD) {
console.warn("Initialer Admin wurde mit admin/admin angelegt. Bitte Passwort über ADMIN_PASSWORD setzen oder im Adminbereich ändern.");
}
}
async function findMigrationAdmin() {
const adminResult = await query<{ id: string }>("SELECT id FROM users WHERE role = 'admin' ORDER BY id ASC LIMIT 1;");
if (adminResult.rows.length === 0) {
throw new Error("At least one admin user is required");
}
return adminResult.rows[0].id;
return adminResult.rows[0]?.id ?? null;
}
async function ensureClosureOwnership(table: string, column: string, primaryKeyColumns: string[], adminId: string) {
async function ensureClosureOwnership(table: string, column: string, primaryKeyColumns: string[], adminId: string | null) {
await query(`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS user_id BIGINT REFERENCES users(id);`);
await query(`UPDATE ${table} SET user_id = $1 WHERE user_id IS NULL;`, [adminId]);
await query(`ALTER TABLE ${table} ALTER COLUMN user_id SET NOT NULL;`);
await query(`ALTER TABLE ${table} DROP CONSTRAINT IF EXISTS ${table}_pkey;`);
await query(`ALTER TABLE ${table} ADD CONSTRAINT ${table}_pkey PRIMARY KEY (${primaryKeyColumns.join(", ")}, user_id);`);
if (adminId) {
await query(`UPDATE ${table} SET user_id = $1 WHERE user_id IS NULL;`, [adminId]);
}
await query(`
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM ${table} WHERE user_id IS NULL) THEN
ALTER TABLE ${table} ALTER COLUMN user_id SET NOT NULL;
ALTER TABLE ${table} DROP CONSTRAINT IF EXISTS ${table}_pkey;
ALTER TABLE ${table} ADD CONSTRAINT ${table}_pkey PRIMARY KEY (${primaryKeyColumns.join(", ")}, user_id);
END IF;
END $$;
`);
await query(`CREATE INDEX IF NOT EXISTS idx_${table}_${column}_user ON ${table}(${column}, user_id);`);
}
@@ -74,9 +57,12 @@ export async function migrate() {
await query("CREATE INDEX IF NOT EXISTS idx_auth_sessions_user ON auth_sessions(user_id);");
await query("CREATE INDEX IF NOT EXISTS idx_auth_sessions_expires ON auth_sessions(expires_at);");
const adminId = await ensureMigrationAdmin();
const adminId = await findMigrationAdmin();
if (adminId) {
await query("UPDATE users SET role = 'user', updated_at = now() WHERE role = 'admin' AND id <> $1;", [adminId]);
}
await query("UPDATE users SET role = 'user', updated_at = now() WHERE role = 'admin' AND id <> $1;", [adminId]);
await query("CREATE UNIQUE INDEX IF NOT EXISTS idx_users_single_admin ON users ((role)) WHERE role = 'admin';");
await query(`
@@ -170,8 +156,17 @@ export async function migrate() {
await query("ALTER TABLE sessions ADD COLUMN IF NOT EXISTS recurring_billing_slot_id BIGINT;");
await query("ALTER TABLE sessions ADD COLUMN IF NOT EXISTS recurring_occurrence_date DATE;");
await query("ALTER TABLE sessions ADD COLUMN IF NOT EXISTS recurring_occurrence_key TEXT;");
await query("UPDATE sessions SET user_id = $1 WHERE user_id IS NULL;", [adminId]);
await query("ALTER TABLE sessions ALTER COLUMN user_id SET NOT NULL;");
if (adminId) {
await query("UPDATE sessions SET user_id = $1 WHERE user_id IS NULL;", [adminId]);
}
await query(`
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM sessions WHERE user_id IS NULL) THEN
ALTER TABLE sessions ALTER COLUMN user_id SET NOT NULL;
END IF;
END $$;
`);
await query(`
CREATE TABLE IF NOT EXISTS recurring_billings (