import { z } from "zod"; export const deviceSourceValues = [ "fritzbox", "dns", "http", "https", "portscan", "manual", "plugin", ] as const; export const DeviceSourceSchema = z.enum(deviceSourceValues); export const DeviceCreateSchema = z.object({ hostname: z.string().min(1, "hostname darf nicht leer sein"), ip: z.string().min(1, "ip darf nicht leer sein"), mac: z.string().optional(), manufacturer: z.string().optional(), model: z.string().optional(), online: z.boolean().optional(), source: DeviceSourceSchema.optional(), }); export type DeviceCreateInput = z.infer; export const DeviceUpdateSchema = DeviceCreateSchema.partial(); export type DeviceUpdateInput = z.infer; export const ServiceCreateSchema = z.object({ deviceId: z.string().min(1, "deviceId darf nicht leer sein"), displayName: z.string().min(1, "displayName darf nicht leer sein"), hostname: z.string().min(1, "hostname darf nicht leer sein"), url: z.string().url("url muss eine gültige URL sein"), https: z.boolean().optional(), port: z.number().int().min(1).max(65535), category: z.string().optional(), icon: z.string().optional(), favicon: z.string().optional(), description: z.string().optional(), favorite: z.boolean().optional(), alias: z.array(z.string()).optional(), order: z.number().optional(), visible: z.boolean().optional(), }); export type ServiceCreateInput = z.infer; export const ServiceUpdateSchema = ServiceCreateSchema.omit({ deviceId: true, }) .partial() .extend({ // Frontend schickt beim Leeren eines Felds bewusst null (nicht nur // "Feld weglassen") - .partial() allein akzeptiert dafür nur // string|undefined, ein PATCH mit category:null schlug daher bisher mit // 400 fehl, ohne dass das Formular das sichtbar gemacht hätte. category: z.string().nullable().optional(), description: z.string().nullable().optional(), icon: z.string().nullable().optional(), favicon: z.string().nullable().optional(), // Standardmäßig markiert ein PATCH mit displayName/category den jeweiligen // Wert als "manuell bearbeitet" (siehe updateService-Doku in // repositories/services.ts). Wird explizit false mitgeschickt - z. B. beim // Übernehmen eines Scan-Vorschlags in der Änderungsübersicht -, bleibt der // Wert stattdessen als "vom Scan" markiert. displayNameEditedManually: z.boolean().optional(), categoryEditedManually: z.boolean().optional(), }); export type ServiceUpdateInput = z.infer; /** Für Drag & Drop: neue Reihenfolge mehrerer Dienste auf einmal setzen. */ export const ServiceReorderSchema = z .array( z.object({ id: z.string().min(1), order: z.number(), }) ) .min(1, "mindestens ein Eintrag erforderlich"); export type ServiceReorderInput = z.infer; export const CategoryCreateSchema = z.object({ name: z.string().min(1, "name darf nicht leer sein"), color: z .string() .regex(/^#[0-9a-fA-F]{6}$/, "Farbe muss ein Hex-Code sein, z. B. #3b82f6") .optional(), }); export type CategoryCreateInput = z.infer; export const CategoryUpdateSchema = CategoryCreateSchema.partial(); export type CategoryUpdateInput = z.infer; /** Für Drag & Drop: neue Reihenfolge mehrerer Kategorien auf einmal setzen. */ export const CategoryReorderSchema = z .array( z.object({ id: z.string().min(1), order: z.number(), }) ) .min(1, "mindestens ein Eintrag erforderlich"); export type CategoryReorderInput = z.infer; export const BookmarkCreateSchema = z.object({ url: z.string().url("url muss eine gültige URL sein"), // Optional: wird nicht angegeben, versucht das Backend automatisch den // Seitentitel zu lesen (siehe apps/backend/src/routes/bookmarks.ts). displayName: z.string().min(1).optional(), description: z.string().optional(), category: z.string().optional(), icon: z.string().optional(), favicon: z.string().optional(), favorite: z.boolean().optional(), alias: z.array(z.string()).optional(), order: z.number().optional(), }); export type BookmarkCreateInput = z.infer; export const BookmarkUpdateSchema = BookmarkCreateSchema.partial().extend({ // Siehe Kommentar bei ServiceUpdateSchema: null muss erlaubt sein, damit // sich Kategorie/Beschreibung/Icons im Bearbeiten-Formular leeren lassen. category: z.string().nullable().optional(), description: z.string().nullable().optional(), icon: z.string().nullable().optional(), favicon: z.string().nullable().optional(), }); export type BookmarkUpdateInput = z.infer; /** Für Drag & Drop: neue Reihenfolge mehrerer Lesezeichen auf einmal setzen. */ export const BookmarkReorderSchema = z .array( z.object({ id: z.string().min(1), order: z.number(), }) ) .min(1, "mindestens ein Eintrag erforderlich"); export type BookmarkReorderInput = z.infer;