Merge current features with security hardening and automatic safe initialization

This commit is contained in:
t.indorf
2026-10-09 23:29:19 +02:00
85 changed files with 13522 additions and 1326 deletions
+14 -20
View File
@@ -7,13 +7,17 @@ import { serializableTransaction } from "@/lib/transaction";
import { rateLimit } from "@/lib/rate-limit";
import { createElement } from "react";
import { revalidatePath } from "next/cache";
import { Prisma, UserRole } from "@prisma/client";
import { AnnouncementAudience, UserRole, type Prisma } from "@prisma/client";
import { z } from "zod";
import { NewsEmail } from "@/emails/NewsEmail";
import { requireKitaSession, requireRole } from "@/lib/auth-utils";
import { getAppEmailConfigError, sendAppEmail } from "@/lib/mail";
import { prisma } from "@/lib/prisma";
import {
announcementRecipientWhere,
visibleAnnouncementAudiences,
} from "@/lib/announcement-access";
const audienceSchema = z.enum(["ELTERN", "ERZIEHER", "ALLE"]);
@@ -29,24 +33,7 @@ function getSafeFileName(name: string) {
}
async function getRecipients(kitaId: string, audience: z.infer<typeof audienceSchema>) {
const where: Prisma.UserWhereInput =
audience === "ELTERN"
? {
kitaId,
familyId: { not: null },
emailVerifiedAt: { not: null },
}
: audience === "ERZIEHER"
? {
kitaId,
role: UserRole.ERZIEHER,
emailVerifiedAt: { not: null },
}
: {
kitaId,
emailVerifiedAt: { not: null },
OR: [{ familyId: { not: null } }, { role: UserRole.ERZIEHER }],
};
const where: Prisma.UserWhereInput = { kitaId, emailVerifiedAt: { not: null }, ...announcementRecipientWhere(audience as AnnouncementAudience) };
const users = await prisma.user.findMany({
where: { ...where, disabledAt: null, privacyPolicyAcceptedAt: { not: null } },
@@ -118,6 +105,7 @@ export async function createAnnouncement(formData: FormData) {
kitaId,
title: parsed.data.title,
content: parsed.data.content,
audience: parsed.data.audience,
authorId: session.user.id,
attachments: {
create: attachmentInputs.map((attachment) => ({
@@ -158,7 +146,10 @@ export async function createAnnouncement(formData: FormData) {
rm(attachmentPath(attachment.id), { force: true }),
),
);
console.error("Ankündigungs-Anhang konnte nicht gespeichert werden:", error);
console.error(
"Ankündigungs-Anhang konnte nicht gespeichert werden:",
error,
);
return { error: "Anhang konnte nicht gespeichert werden." };
}
}
@@ -190,6 +181,7 @@ export async function createAnnouncement(formData: FormData) {
revalidatePath("/dashboard");
revalidatePath("/dashboard/admin/news");
revalidatePath("/dashboard/mitteilungen");
if (emailError) {
return {
@@ -208,6 +200,7 @@ export async function markAnnouncementRead(announcementId: string) {
where: {
id: announcementId,
kitaId: session.user.kitaId,
audience: { in: visibleAnnouncementAudiences(session.user) },
},
select: { id: true },
});
@@ -234,5 +227,6 @@ export async function markAnnouncementRead(announcementId: string) {
});
revalidatePath("/dashboard");
revalidatePath("/dashboard/mitteilungen");
return { success: true };
}