bug fix for atemschutz
This commit is contained in:
143
backend/src/models/events.model.ts
Normal file
143
backend/src/models/events.model.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Core DB-mapped interfaces
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface VeranstaltungKategorie {
|
||||
id: string;
|
||||
name: string;
|
||||
beschreibung?: string | null;
|
||||
farbe?: string | null;
|
||||
icon?: string | null;
|
||||
erstellt_von?: string | null;
|
||||
erstellt_am: Date;
|
||||
aktualisiert_am: Date;
|
||||
}
|
||||
|
||||
export interface Veranstaltung {
|
||||
id: string;
|
||||
titel: string;
|
||||
beschreibung?: string | null;
|
||||
ort?: string | null;
|
||||
ort_url?: string | null;
|
||||
kategorie_id?: string | null;
|
||||
datum_von: Date;
|
||||
datum_bis: Date;
|
||||
ganztaegig: boolean;
|
||||
zielgruppen: string[];
|
||||
alle_gruppen: boolean;
|
||||
max_teilnehmer?: number | null;
|
||||
anmeldung_erforderlich: boolean;
|
||||
anmeldung_bis?: Date | null;
|
||||
erstellt_von: string;
|
||||
abgesagt: boolean;
|
||||
abgesagt_grund?: string | null;
|
||||
abgesagt_am?: Date | null;
|
||||
erstellt_am: Date;
|
||||
aktualisiert_am: Date;
|
||||
// Joined / enriched fields
|
||||
kategorie_name?: string | null;
|
||||
kategorie_farbe?: string | null;
|
||||
kategorie_icon?: string | null;
|
||||
erstellt_von_name?: string | null;
|
||||
}
|
||||
|
||||
/** Lightweight version for calendar and list views */
|
||||
export interface VeranstaltungListItem {
|
||||
id: string;
|
||||
titel: string;
|
||||
ort?: string | null;
|
||||
kategorie_id?: string | null;
|
||||
kategorie_name?: string | null;
|
||||
kategorie_farbe?: string | null;
|
||||
kategorie_icon?: string | null;
|
||||
datum_von: Date;
|
||||
datum_bis: Date;
|
||||
ganztaegig: boolean;
|
||||
alle_gruppen: boolean;
|
||||
zielgruppen: string[];
|
||||
abgesagt: boolean;
|
||||
anmeldung_erforderlich: boolean;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Zod schemas -- Kategorien
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const CreateKategorieSchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
.min(2, 'Name muss mindestens 2 Zeichen haben')
|
||||
.max(255),
|
||||
beschreibung: z.string().max(500).optional().nullable(),
|
||||
farbe: z
|
||||
.string()
|
||||
.regex(/^#[0-9a-fA-F]{6}$/, 'Farbe muss ein gültiger Hex-Farbwert sein (z.B. #1976d2)')
|
||||
.optional(),
|
||||
icon: z.string().max(100).optional(),
|
||||
});
|
||||
|
||||
export type CreateKategorieData = z.infer<typeof CreateKategorieSchema>;
|
||||
|
||||
export const UpdateKategorieSchema = CreateKategorieSchema.partial();
|
||||
|
||||
export type UpdateKategorieData = z.infer<typeof UpdateKategorieSchema>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Zod schemas -- Veranstaltungen
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const VeranstaltungBaseSchema = z.object({
|
||||
titel: z
|
||||
.string()
|
||||
.min(3, 'Titel muss mindestens 3 Zeichen haben')
|
||||
.max(500),
|
||||
beschreibung: z.string().max(5000).optional().nullable(),
|
||||
ort: z.string().max(500).optional().nullable(),
|
||||
// Plain string validator — some users use relative paths or internal URLs
|
||||
ort_url: z.string().max(1000).optional().nullable(),
|
||||
kategorie_id: z.string().uuid('kategorie_id muss eine gültige UUID sein').optional().nullable(),
|
||||
datum_von: z
|
||||
.string()
|
||||
.datetime({ offset: true, message: 'datum_von muss ein ISO-8601 Datum mit Zeitzone sein' })
|
||||
.transform((s) => new Date(s)),
|
||||
datum_bis: z
|
||||
.string()
|
||||
.datetime({ offset: true, message: 'datum_bis muss ein ISO-8601 Datum mit Zeitzone sein' })
|
||||
.transform((s) => new Date(s)),
|
||||
ganztaegig: z.boolean().default(false),
|
||||
zielgruppen: z.array(z.string()).default([]),
|
||||
alle_gruppen: z.boolean().default(false),
|
||||
max_teilnehmer: z.number().int().positive().optional().nullable(),
|
||||
anmeldung_erforderlich: z.boolean().default(false),
|
||||
anmeldung_bis: z
|
||||
.string()
|
||||
.datetime({ offset: true, message: 'anmeldung_bis muss ein ISO-8601 Datum mit Zeitzone sein' })
|
||||
.transform((s) => new Date(s))
|
||||
.optional()
|
||||
.nullable(),
|
||||
});
|
||||
|
||||
export const CreateVeranstaltungSchema = VeranstaltungBaseSchema.refine(
|
||||
(d) => d.datum_bis >= d.datum_von,
|
||||
{ message: 'datum_bis muss nach datum_von liegen', path: ['datum_bis'] }
|
||||
);
|
||||
|
||||
export type CreateVeranstaltungData = z.infer<typeof CreateVeranstaltungSchema>;
|
||||
|
||||
export const UpdateVeranstaltungSchema = VeranstaltungBaseSchema.partial().refine(
|
||||
(d) => d.datum_bis == null || d.datum_von == null || d.datum_bis >= d.datum_von,
|
||||
{ message: 'datum_bis muss nach datum_von liegen', path: ['datum_bis'] }
|
||||
);
|
||||
|
||||
export type UpdateVeranstaltungData = z.infer<typeof UpdateVeranstaltungSchema>;
|
||||
|
||||
export const CancelVeranstaltungSchema = z.object({
|
||||
abgesagt_grund: z
|
||||
.string()
|
||||
.min(5, 'Bitte gib einen Grund für die Absage an (min. 5 Zeichen)')
|
||||
.max(1000),
|
||||
});
|
||||
|
||||
export type CancelVeranstaltungData = z.infer<typeof CancelVeranstaltungSchema>;
|
||||
Reference in New Issue
Block a user