mirror of
https://github.com/Since-3/since3-portal.git
synced 2026-10-12 20:51:17 +00:00
168 lines
6 KiB
TypeScript
168 lines
6 KiB
TypeScript
"use server";
|
||
|
||
import * as z from "zod";
|
||
import { revalidatePath } from "next/cache";
|
||
import { after } from "next/server";
|
||
import { createAdminClient } from "@/lib/supabase/admin";
|
||
import { createClient } from "@/lib/supabase/server";
|
||
import { verifySession } from "@/lib/supabase/dal";
|
||
import { getClientId } from "@/modules/shared/queries/profile";
|
||
import { getMailRecipient } from "@/modules/shared/queries/mail-recipient";
|
||
import { getServiceById } from "@/modules/shared/data/services-data";
|
||
import { getAppUrl } from "@/modules/shared/mail/app-url";
|
||
import { sendMail } from "@/modules/shared/mail/send-mail";
|
||
import { credentialAddedMail } from "@/modules/shared/mail/templates";
|
||
|
||
const CredentialSchema = z.object({
|
||
profileId: z.string().uuid(),
|
||
service: z.string().min(1, { error: "Dienst ist erforderlich." }),
|
||
username: z.string().min(1, { error: "Benutzername ist erforderlich." }),
|
||
password: z.string().min(1, { error: "Passwort ist erforderlich." }),
|
||
});
|
||
|
||
export type CredentialState = { error: string } | { success: true } | undefined;
|
||
|
||
export async function createCredential(
|
||
_prevState: CredentialState,
|
||
formData: FormData
|
||
): Promise<CredentialState> {
|
||
const session = await verifySession();
|
||
const profileId = String(formData.get("profileId") ?? "");
|
||
|
||
if (!session.isAdmin && session.userId !== profileId) {
|
||
return { error: "Keine Berechtigung." };
|
||
}
|
||
|
||
const validatedFields = CredentialSchema.safeParse({
|
||
profileId,
|
||
service: formData.get("service"),
|
||
username: formData.get("username"),
|
||
password: formData.get("password"),
|
||
});
|
||
|
||
if (!validatedFields.success) {
|
||
const { fieldErrors } = z.flattenError(validatedFields.error);
|
||
const firstError = Object.values(fieldErrors).flat()[0];
|
||
return { error: firstError ?? "Bitte alle Felder ausfüllen." };
|
||
}
|
||
|
||
const clientId = await getClientId(profileId);
|
||
if (!clientId) {
|
||
return { error: "Client hat noch keine Stammdaten hinterlegt." };
|
||
}
|
||
|
||
const supabase = await createClient();
|
||
const { data: credentialId, error } = await supabase.rpc("create_credential", {
|
||
p_client_id: clientId,
|
||
p_service: validatedFields.data.service,
|
||
p_username: validatedFields.data.username,
|
||
p_password: validatedFields.data.password,
|
||
});
|
||
|
||
if (error) return { error: "Eintrag konnte nicht gespeichert werden." };
|
||
|
||
// Nur wenn wir den Zugang hinterlegen – legt der Kunde ihn selbst an, braucht er keine Mail.
|
||
if (session.isAdmin) {
|
||
const serviceLabel = getServiceById(validatedFields.data.service).label;
|
||
after(async () => {
|
||
try {
|
||
const recipient = await getMailRecipient(profileId);
|
||
if (!recipient) return;
|
||
await sendMail({
|
||
to: recipient.email,
|
||
...credentialAddedMail({ name: recipient.name, serviceLabel, appUrl: await getAppUrl() }),
|
||
type: "credential",
|
||
idempotencyKey: `credential-${credentialId}`,
|
||
});
|
||
} catch (error) {
|
||
console.error("[mail] Hinweis-Mail zum neuen Zugang fehlgeschlagen:", error);
|
||
}
|
||
});
|
||
}
|
||
|
||
revalidatePath("/client/passwords");
|
||
revalidatePath(`/admin/clients/${profileId}/passwords`);
|
||
|
||
return { success: true };
|
||
}
|
||
|
||
const UpdateCredentialSchema = z.object({
|
||
profileId: z.string().uuid(),
|
||
credentialId: z.string().uuid(),
|
||
service: z.string().min(1, { error: "Dienst ist erforderlich." }),
|
||
username: z.string().min(1, { error: "Benutzername ist erforderlich." }),
|
||
password: z.string().optional(),
|
||
});
|
||
|
||
export async function updateCredential(
|
||
_prevState: CredentialState,
|
||
formData: FormData
|
||
): Promise<CredentialState> {
|
||
const session = await verifySession();
|
||
const profileId = String(formData.get("profileId") ?? "");
|
||
|
||
if (!session.isAdmin && session.userId !== profileId) {
|
||
return { error: "Keine Berechtigung." };
|
||
}
|
||
|
||
const password = String(formData.get("password") ?? "").trim();
|
||
|
||
const validatedFields = UpdateCredentialSchema.safeParse({
|
||
profileId,
|
||
credentialId: formData.get("credentialId"),
|
||
service: formData.get("service"),
|
||
username: formData.get("username"),
|
||
password: password === "" ? undefined : password,
|
||
});
|
||
|
||
if (!validatedFields.success) {
|
||
const { fieldErrors } = z.flattenError(validatedFields.error);
|
||
const firstError = Object.values(fieldErrors).flat()[0];
|
||
return { error: firstError ?? "Bitte alle Felder ausfüllen." };
|
||
}
|
||
|
||
const supabase = await createClient();
|
||
const { error } = await supabase.rpc("update_credential", {
|
||
p_id: validatedFields.data.credentialId,
|
||
p_service: validatedFields.data.service,
|
||
p_username: validatedFields.data.username,
|
||
p_password: validatedFields.data.password ?? null,
|
||
});
|
||
|
||
if (error) return { error: "Eintrag konnte nicht aktualisiert werden." };
|
||
|
||
revalidatePath("/client/passwords");
|
||
revalidatePath(`/admin/clients/${profileId}/passwords`);
|
||
|
||
return { success: true };
|
||
}
|
||
|
||
export async function revealCredential(credentialId: string): Promise<{ password: string } | { error: string }> {
|
||
await verifySession();
|
||
|
||
const supabase = await createClient();
|
||
const { data, error } = await supabase.rpc("reveal_credential", { p_id: credentialId });
|
||
|
||
if (error || typeof data !== "string") {
|
||
return { error: "Passwort konnte nicht abgerufen werden." };
|
||
}
|
||
|
||
return { password: data };
|
||
}
|
||
|
||
export async function deleteCredential(profileId: string, credentialId: string): Promise<CredentialState> {
|
||
const session = await verifySession();
|
||
|
||
if (!session.isAdmin) {
|
||
return { error: "Keine Berechtigung." };
|
||
}
|
||
|
||
const supabase = createAdminClient();
|
||
const { error } = await supabase.from("credentials").delete().eq("id", credentialId);
|
||
|
||
if (error) return { error: "Eintrag konnte nicht gelöscht werden." };
|
||
|
||
revalidatePath(`/admin/clients/${profileId}/passwords`);
|
||
|
||
return { success: true };
|
||
}
|