feat: add directory opt-out sync and layout/xss seed data extensions

This commit is contained in:
t.indorf
2026-05-22 15:22:12 +02:00
parent e9595fab21
commit f28956194c
4 changed files with 89 additions and 0 deletions
+53
View File
@@ -659,6 +659,24 @@ async function createAnnouncements(context: SeedContext) {
}, },
}); });
const longTitle = await prisma.announcement.create({
data: {
kitaId: kita.id,
title: "Dies ist eine extrem lange Ankuendigung mit einem superlangen Titel um das Layout des Dashboards und des Schwarzen Bretts auf korrekten Textumbruch und Mobilfreundlichkeit zu testen",
content: "Inhalt der superlangen Ankuendigung fuer Layout-Testing.",
authorId: admin.id,
},
});
const xssTitle = await prisma.announcement.create({
data: {
kitaId: kita.id,
title: "Boesartiger Titel mit <script>alert('XSS_NEWS_TITLE')</script> HTML Tags",
content: "Dieser Beitrag testet, ob die Anwendung XSS-Angriffe im Titel oder im Inhalt filtert. <script>alert('XSS_NEWS_CONTENT')</script>",
authorId: admin.id,
},
});
await prisma.attachment.create({ await prisma.attachment.create({
data: { data: {
announcementId: sommerfest.id, announcementId: sommerfest.id,
@@ -808,6 +826,41 @@ async function createTermine(
}, },
}); });
const highDensityDay = addDays(now, 7);
for (let i = 1; i <= 8; i++) {
await prisma.termin.create({
data: {
kitaId: kita.id,
title: `Parallel-Termin #${i}`,
description: `Beschreibung fuer Parallel-Termin #${i}`,
type: TerminType.SONSTIGES,
status: TerminStatus.CONFIRMED,
startDate: highDensityDay,
endDate: highDensityDay,
allDay: false,
createdById: admin.id,
approvedById: admin.id,
approvedAt: now,
},
});
}
await prisma.termin.create({
data: {
kitaId: kita.id,
title: "Boesartiger Termin <script>alert('XSS_CAL_TITLE')</script>",
description: "XSS Test im Terminkalender <script>alert('XSS_CAL_DESC')</script>",
type: TerminType.SONSTIGES,
status: TerminStatus.CONFIRMED,
startDate: addDays(now, 5),
endDate: addDays(now, 5),
allDay: true,
createdById: admin.id,
approvedById: admin.id,
approvedAt: now,
},
});
await prisma.mitbringselItem.createMany({ await prisma.mitbringselItem.createMany({
data: [ data: [
{ {
@@ -16,6 +16,7 @@ type ContactFormProps = {
street: string | null; street: string | null;
postalCode: string | null; postalCode: string | null;
city: string | null; city: string | null;
directoryOptIn: boolean;
}; };
}; };
@@ -30,6 +31,7 @@ export function ContactForm({ contact }: ContactFormProps) {
street: String(formData.get("street") ?? ""), street: String(formData.get("street") ?? ""),
postalCode: String(formData.get("postalCode") ?? ""), postalCode: String(formData.get("postalCode") ?? ""),
city: String(formData.get("city") ?? ""), city: String(formData.get("city") ?? ""),
directoryOptIn: formData.get("directoryOptIn") === "on",
}); });
if (result.error) { if (result.error) {
@@ -106,6 +108,22 @@ export function ContactForm({ contact }: ContactFormProps) {
</div> </div>
</div> </div>
<div className="flex items-center gap-2 mt-2">
<input
type="checkbox"
id="directoryOptIn"
name="directoryOptIn"
defaultChecked={contact.directoryOptIn}
className="rounded border-[var(--hairline)] accent-[var(--accent-deep)]"
/>
<Label
htmlFor="directoryOptIn"
className="font-normal cursor-pointer text-xs text-[var(--ink-soft)]"
>
Im Adressbuch anzeigen (Datenschutzerklärung)
</Label>
</div>
<div className="flex justify-end pt-1"> <div className="flex justify-end pt-1">
<Button type="submit" disabled={isPending}> <Button type="submit" disabled={isPending}>
{isPending ? ( {isPending ? (
+16
View File
@@ -80,6 +80,7 @@ const contactSchema = z.object({
"Bitte eine gültige Postleitzahl angeben.", "Bitte eine gültige Postleitzahl angeben.",
), ),
city: z.string().trim().max(100).optional(), city: z.string().trim().max(100).optional(),
directoryOptIn: z.boolean().optional(),
}); });
// Gibt die erste konkrete Field-Error-Message aus einem Zod-Fehler zurück, // Gibt die erste konkrete Field-Error-Message aus einem Zod-Fehler zurück,
@@ -183,6 +184,20 @@ export async function updateMyContact(rawPayload: unknown) {
} }
try { try {
const currentUser = await prisma.user.findUniqueOrThrow({
where: { id: session.user.id },
select: { directoryOptInAt: true },
});
let newDirectoryOptInAt: Date | null = currentUser.directoryOptInAt;
if (parsed.data.directoryOptIn) {
if (!newDirectoryOptInAt) {
newDirectoryOptInAt = new Date();
}
} else {
newDirectoryOptInAt = null;
}
await prisma.user.update({ await prisma.user.update({
where: { id: session.user.id }, where: { id: session.user.id },
data: { data: {
@@ -190,6 +205,7 @@ export async function updateMyContact(rawPayload: unknown) {
street: parsed.data.street || null, street: parsed.data.street || null,
postalCode: parsed.data.postalCode || null, postalCode: parsed.data.postalCode || null,
city: parsed.data.city || null, city: parsed.data.city || null,
directoryOptInAt: newDirectoryOptInAt,
}, },
}); });
+2
View File
@@ -51,6 +51,7 @@ export default async function ProfilPage() {
street: true, street: true,
postalCode: true, postalCode: true,
city: true, city: true,
directoryOptInAt: true,
createdAt: true, createdAt: true,
family: { family: {
select: { select: {
@@ -173,6 +174,7 @@ export default async function ProfilPage() {
street: user.street, street: user.street,
postalCode: user.postalCode, postalCode: user.postalCode,
city: user.city, city: user.city,
directoryOptIn: !!user.directoryOptInAt,
}} }}
/> />
</CardContent> </CardContent>