Add non-destructive dev seed on startup

This commit is contained in:
t.indorf
2026-05-06 22:31:07 +02:00
parent 9f452fccac
commit b686e714ff
77 changed files with 10862 additions and 87 deletions
+97
View File
@@ -0,0 +1,97 @@
"use server";
import { hash } from "bcryptjs";
import { Prisma, UserRole } from "@prisma/client";
import { z } from "zod";
import { signIn } from "@/auth";
import { prisma } from "@/lib/prisma";
// =====================================================================
// /register · Server Action
// ---------------------------------------------------------------------
// Legt einen "heimatlosen" Admin-Account an (kitaId === null) und loggt
// ihn direkt ein. Die Tenant-Erstellung folgt im Onboarding-Wizard.
// =====================================================================
const registerSchema = z.object({
email: z.string().email("Bitte eine gültige E-Mail-Adresse angeben.").toLowerCase().trim(),
// bcrypt akzeptiert max. 72 Bytes — wir capen davor zur Sicherheit
password: z
.string()
.min(8, "Mindestens 8 Zeichen.")
.max(72, "Maximal 72 Zeichen."),
firstName: z.string().min(1, "Pflichtfeld.").max(100).trim(),
lastName: z.string().min(1, "Pflichtfeld.").max(100).trim(),
// HTML-Checkboxen senden "on" wenn aktiviert, sonst nichts
acceptPrivacyPolicy: z.literal("on", {
error: "Bitte Datenschutzerklärung akzeptieren.",
}),
});
const PRIVACY_POLICY_VERSION = "2026-05-01";
export type RegisterState = {
errors?: {
email?: string[];
password?: string[];
firstName?: string[];
lastName?: string[];
acceptPrivacyPolicy?: string[];
_form?: string[];
};
};
export async function registerAction(
_prevState: RegisterState,
formData: FormData,
): Promise<RegisterState> {
const parsed = registerSchema.safeParse(Object.fromEntries(formData));
if (!parsed.success) {
return { errors: parsed.error.flatten().fieldErrors };
}
const { email, password, firstName, lastName } = parsed.data;
const passwordHash = await hash(password, 12);
const now = new Date();
try {
await prisma.user.create({
data: {
email,
passwordHash,
firstName,
lastName,
// Gründer wird "heimatloser" Admin — beim Onboarding-Abschluss
// wird die Rolle bestätigt und der User mit einer Kita verknüpft.
role: UserRole.ADMIN,
kitaId: null,
privacyPolicyAcceptedAt: now,
privacyPolicyVersion: PRIVACY_POLICY_VERSION,
emailVerifiedAt: now,
},
});
} catch (err) {
if (
err instanceof Prisma.PrismaClientKnownRequestError &&
err.code === "P2002"
) {
return {
errors: {
email: ["Mit dieser E-Mail-Adresse existiert bereits ein Account."],
},
};
}
throw err;
}
// Direkt einloggen → wirft NEXT_REDIRECT, das wir nicht abfangen.
await signIn("credentials", {
email,
password,
redirectTo: "/onboarding",
});
// Unreachable signIn redirected.
return {};
}