since3-portal/modules/admin/actions/manage-client.ts

109 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use server";
import * as z from "zod";
import { revalidatePath } from "next/cache";
import { createAdminClient } from "@/lib/supabase/admin";
import { createClient } from "@/lib/supabase/server";
import { verifySession } from "@/lib/supabase/dal";
import { generatePassword } from "@/lib/generate-password";
import { MIN_PASSWORD_LENGTH } from "@/modules/admin/lib/constants";
import { CLIENT_STATUSES } from "@/modules/admin/lib/client-status";
export type ResetPasswordState = { error: string } | { success: true; password: string } | undefined;
export type UpdateClientState = { error: string } | { success: true } | undefined;
const IdSchema = z.string().uuid();
// Nur Admins, und nur Konten mit Rolle "client" – Admin-Konten lassen sich hierüber nicht ändern.
async function assertClientTarget(profileId: string) {
const session = await verifySession();
if (!session.isAdmin) return { error: "Keine Berechtigung." };
if (!IdSchema.safeParse(profileId).success) return { error: "Ungültiger Client." };
const supabase = await createClient();
const { data } = await supabase.from("profiles").select("role").eq("id", profileId).maybeSingle();
if (data?.role !== "client") return { error: "Client nicht gefunden." };
return null;
}
const ResetSchema = z.discriminatedUnion("mode", [
z.object({ mode: z.literal("generate") }),
z.object({
mode: z.literal("manual"),
password: z.string().min(MIN_PASSWORD_LENGTH, { error: `Mindestens ${MIN_PASSWORD_LENGTH} Zeichen.` }).max(72),
}),
]);
export async function resetClientPassword(_prev: ResetPasswordState, formData: FormData): Promise<ResetPasswordState> {
const profileId = String(formData.get("profileId") ?? "");
const denied = await assertClientTarget(profileId);
if (denied) return denied;
const parsed = ResetSchema.safeParse({
mode: formData.get("mode"),
password: formData.get("password") ?? undefined,
});
if (!parsed.success) return { error: parsed.error.issues[0]?.message ?? "Ungültige Eingabe." };
const password = parsed.data.mode === "generate" ? generatePassword() : parsed.data.password;
const admin = createAdminClient();
const { error } = await admin.auth.admin.updateUserById(profileId, { password });
if (error) return { error: "Passwort konnte nicht geändert werden." };
return { success: true, password };
}
const UpdateSchema = z.object({
name: z.string().trim().min(3, { error: "Mindestens drei Zeichen eingeben." }),
email: z.email({ error: "Bitte eine gültige Email eingeben." }),
});
export async function updateClientAccount(_prev: UpdateClientState, formData: FormData): Promise<UpdateClientState> {
const profileId = String(formData.get("profileId") ?? "");
const denied = await assertClientTarget(profileId);
if (denied) return denied;
const parsed = UpdateSchema.safeParse({ name: formData.get("name"), email: formData.get("email") });
if (!parsed.success) return { error: parsed.error.issues[0]?.message ?? "Ungültige Eingabe." };
const email = parsed.data.email.toLowerCase();
// Login-Email lebt in auth.users; ohne email_confirm würde Supabase erst eine Bestätigungsmail verlangen.
const admin = createAdminClient();
const { error: authError } = await admin.auth.admin.updateUserById(profileId, {
email,
email_confirm: true,
user_metadata: { name: parsed.data.name },
});
if (authError) {
return { error: authError.code === "email_exists" ? "Diese Email ist bereits vergeben." : "Client konnte nicht aktualisiert werden." };
}
const supabase = await createClient();
const { error } = await supabase.from("profiles").update({ name: parsed.data.name, email }).eq("id", profileId);
if (error) return { error: "Login-Email wurde geändert, das Profil aber nicht. Bitte erneut speichern." };
revalidatePath("/admin/clients");
revalidatePath(`/admin/clients/${profileId}`, "layout");
return { success: true };
}
const StatusSchema = z.enum(CLIENT_STATUSES, { error: "Bitte einen gültigen Status wählen." });
export async function updateClientStatus(_prev: UpdateClientState, formData: FormData): Promise<UpdateClientState> {
const profileId = String(formData.get("profileId") ?? "");
const denied = await assertClientTarget(profileId);
if (denied) return denied;
const parsed = StatusSchema.safeParse(formData.get("status"));
if (!parsed.success) return { error: parsed.error.issues[0]?.message ?? "Ungültige Eingabe." };
const supabase = await createClient();
const { error } = await supabase.from("profiles").update({ status: parsed.data }).eq("id", profileId);
if (error) return { error: "Status konnte nicht gespeichert werden." };
revalidatePath("/admin/clients");
return { success: true };
}