fast fertig

This commit is contained in:
2026-07-26 20:27:24 +02:00
parent 1370ad4ff7
commit c2fbb1b86f
19 changed files with 836 additions and 395 deletions
+31 -22
View File
@@ -1,10 +1,11 @@
"use server";
import { randomUUID } from "node:crypto";
import { hashSync } from "bcryptjs";
import { redirect, unstable_rethrow } from "next/navigation";
import { requireAdminUser } from "@/lib/auth";
import { db, initDatabase, transaction } from "@/lib/db";
import { emailValue, idValue, passwordValue, roleValue, text } from "@/lib/validation";
import { idValue, passwordValue, roleValue, text } from "@/lib/validation";
import type { UserRole } from "@/lib/types";
function go(kind: "error" | "success", message: string): never {
@@ -13,7 +14,7 @@ function go(kind: "error" | "success", message: string): never {
function errorMessage(error: unknown) {
if (error instanceof Error && error.message.includes("UNIQUE constraint failed")) {
return "Diese E-Mail-Adresse ist bereits vergeben.";
return "Dieser Benutzername ist bereits vergeben.";
}
return error instanceof Error ? error.message : "Die Aktion konnte nicht ausgeführt werden.";
}
@@ -23,12 +24,16 @@ export async function createUserAction(formData: FormData) {
initDatabase();
try {
const name = text(formData, "name", { required: true, max: 100 });
const email = emailValue(formData);
const existing = db
.prepare("SELECT 1 FROM users WHERE name = ? COLLATE NOCASE AND active = 1")
.get(name);
if (existing) throw new Error("Dieser Benutzername ist bereits vergeben.");
const password = passwordValue(formData);
const role = roleValue(formData);
const internalEmail = `user-${name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "konto"}-${randomUUID().slice(0, 8)}@local.invalid`;
db.prepare(
"INSERT INTO users (name, email, password_hash, role) VALUES (?, ?, ?, ?)",
).run(name, email, hashSync(password, 12), role);
).run(name, internalEmail, hashSync(password, 12), role);
go("success", "Benutzer wurde angelegt.");
} catch (error) {
unstable_rethrow(error);
@@ -43,20 +48,20 @@ export async function changeUserRoleAction(formData: FormData) {
const id = idValue(formData);
const role = roleValue(formData);
transaction(() => {
const target = db.prepare("SELECT role FROM users WHERE id = ?").get(id) as
const target = db.prepare("SELECT role FROM users WHERE id = ? AND active = 1").get(id) as
| { role: UserRole }
| undefined;
if (!target) throw new Error("Benutzer wurde nicht gefunden.");
if (target.role === "admin" && role === "member") {
const count = db.prepare("SELECT COUNT(*) AS count FROM users WHERE role = 'admin'").get() as {
count: number;
};
const count = db
.prepare("SELECT COUNT(*) AS count FROM users WHERE role = 'admin' AND active = 1")
.get() as { count: number };
if (count.count <= 1) throw new Error("Der letzte Administrator kann nicht herabgestuft werden.");
if (id === currentUser.id) {
throw new Error("Die eigene Administratorrolle kann nicht herabgestuft werden.");
}
}
db.prepare("UPDATE users SET role = ? WHERE id = ?").run(role, id);
db.prepare("UPDATE users SET role = ? WHERE id = ? AND active = 1").run(role, id);
});
go("success", "Rolle wurde geändert.");
} catch (error) {
@@ -72,26 +77,30 @@ export async function deleteUserAction(formData: FormData) {
const id = idValue(formData);
if (id === currentUser.id) throw new Error("Das aktuell angemeldete Konto kann nicht gelöscht werden.");
transaction(() => {
const target = db.prepare("SELECT role FROM users WHERE id = ?").get(id) as
const target = db
.prepare("SELECT role FROM users WHERE id = ? AND active = 1")
.get(id) as
| { role: UserRole }
| undefined;
if (!target) throw new Error("Benutzer wurde nicht gefunden.");
if (target.role === "admin") {
const count = db.prepare("SELECT COUNT(*) AS count FROM users WHERE role = 'admin'").get() as {
count: number;
};
const count = db
.prepare("SELECT COUNT(*) AS count FROM users WHERE role = 'admin' AND active = 1")
.get() as { count: number };
if (count.count <= 1) throw new Error("Der letzte Administrator kann nicht gelöscht werden.");
}
try {
db.prepare("DELETE FROM users WHERE id = ?").run(id);
} catch (error) {
if (error instanceof Error && error.message.includes("FOREIGN KEY")) {
throw new Error("Benutzer mit vorhandenen Einträgen kann nicht gelöscht werden.");
}
throw error;
}
db.prepare(
`UPDATE users
SET name = name || ' (gelöscht)',
email = ?,
password_hash = ?,
role = 'member',
active = 0,
deleted_at = CURRENT_TIMESTAMP
WHERE id = ? AND active = 1`,
).run(`deleted-${id}-${randomUUID()}@invalid.local`, hashSync(randomUUID(), 12), id);
});
go("success", "Benutzer wurde gelöscht.");
go("success", "Benutzerkonto wurde gelöscht; vorhandene Einträge bleiben erhalten.");
} catch (error) {
unstable_rethrow(error);
go("error", errorMessage(error));