import { z } from 'zod'; import { UserRole, AuthMode, WidgetType, IconType, PermissionLevel, EntityType, TargetType, HealthcheckMethod, CardSize, NotificationType, ApiTokenScope, AuditAction } from './constants.js'; // --- Auth --- export const loginSchema = z.object({ email: z.string().email('Invalid email address'), password: z.string().min(1, 'Password is required') }); export const registerSchema = z.object({ email: z.string().email('Invalid email address'), password: z.string().min(6, 'Password must be at least 6 characters'), displayName: z.string().min(1, 'Display name is required').max(100) }); // --- User --- export const createUserSchema = z.object({ email: z.string().email('Invalid email address'), password: z.string().min(6).optional(), displayName: z.string().min(1).max(100), avatarUrl: z.string().url().optional(), authProvider: z.enum([AuthMode.LOCAL, AuthMode.OAUTH]).optional(), role: z.enum([UserRole.ADMIN, UserRole.USER]).optional() }); export const updateUserSchema = z.object({ displayName: z.string().min(1).max(100).optional(), avatarUrl: z.string().url().nullable().optional(), role: z.enum([UserRole.ADMIN, UserRole.USER]).optional(), onboardingComplete: z.boolean().optional(), trackRecentApps: z.boolean().optional() }); // --- Group --- export const createGroupSchema = z.object({ name: z.string().min(1, 'Group name is required').max(100), description: z.string().max(500).optional(), isDefault: z.boolean().optional() }); export const updateGroupSchema = z.object({ name: z.string().min(1).max(100).optional(), description: z.string().max(500).nullable().optional(), isDefault: z.boolean().optional() }); // --- App --- export const createAppSchema = z.object({ name: z.string().min(1, 'App name is required').max(200), url: z.string().url('Invalid URL'), icon: z.string().max(500).optional(), iconType: z.enum([IconType.LUCIDE, IconType.SIMPLE, IconType.URL, IconType.EMOJI]).optional(), description: z.string().max(1000).optional(), category: z.string().max(100).optional(), tags: z.string().max(500).optional(), healthcheckEnabled: z.boolean().optional(), healthcheckInterval: z.number().int().min(30).max(86400).optional(), healthcheckMethod: z.enum([HealthcheckMethod.GET, HealthcheckMethod.HEAD]).optional(), healthcheckExpectedStatus: z.number().int().min(100).max(599).optional(), healthcheckTimeout: z.number().int().min(1000).max(30000).optional(), integrationType: z.string().max(50).nullable().optional(), integrationConfig: z.string().max(10000).nullable().optional(), integrationEnabled: z.boolean().optional() }); export const updateAppSchema = z.object({ name: z.string().min(1).max(200).optional(), url: z.string().url().optional(), icon: z.string().max(500).nullable().optional(), iconType: z.enum([IconType.LUCIDE, IconType.SIMPLE, IconType.URL, IconType.EMOJI]).optional(), description: z.string().max(1000).nullable().optional(), category: z.string().max(100).nullable().optional(), tags: z.string().max(500).optional(), healthcheckEnabled: z.boolean().optional(), healthcheckInterval: z.number().int().min(30).max(86400).optional(), healthcheckMethod: z.enum([HealthcheckMethod.GET, HealthcheckMethod.HEAD]).optional(), healthcheckExpectedStatus: z.number().int().min(100).max(599).optional(), healthcheckTimeout: z.number().int().min(1000).max(30000).optional(), integrationType: z.string().max(50).nullable().optional(), integrationConfig: z.string().max(10000).nullable().optional(), integrationEnabled: z.boolean().optional() }); // --- Board --- export const createBoardSchema = z.object({ name: z.string().min(1, 'Board name is required').max(200), icon: z.string().max(500).optional(), description: z.string().max(1000).optional(), isDefault: z.boolean().optional(), isGuestAccessible: z.boolean().optional(), backgroundConfig: z.string().optional() }); export const updateBoardSchema = z.object({ name: z.string().min(1).max(200).optional(), icon: z.string().max(500).nullable().optional(), description: z.string().max(1000).nullable().optional(), isDefault: z.boolean().optional(), isGuestAccessible: z.boolean().optional(), backgroundConfig: z.string().nullable().optional(), themeHue: z.number().int().min(0).max(360).nullable().optional(), themeSaturation: z.number().int().min(0).max(100).nullable().optional(), backgroundType: z .enum(['mesh', 'particles', 'aurora', 'wallpaper', 'none']) .nullable() .optional(), cardSize: z.enum([CardSize.COMPACT, CardSize.MEDIUM, CardSize.LARGE]).nullable().optional(), wallpaperUrl: z.string().url().max(2000).nullable().optional(), wallpaperBlur: z.number().int().min(0).max(50).nullable().optional(), wallpaperOverlay: z.number().min(0).max(1).nullable().optional(), customCss: z.string().max(10000).nullable().optional() }); // --- Section --- export const createSectionSchema = z.object({ boardId: z.string().cuid(), title: z.string().min(1, 'Section title is required').max(200), icon: z.string().max(500).optional(), order: z.number().int().min(0).optional(), isExpandedByDefault: z.boolean().optional() }); export const updateSectionSchema = z.object({ title: z.string().min(1).max(200).optional(), icon: z.string().max(500).nullable().optional(), order: z.number().int().min(0).optional(), isExpandedByDefault: z.boolean().optional(), cardSize: z.enum([CardSize.COMPACT, CardSize.MEDIUM, CardSize.LARGE]).nullable().optional() }); // --- Widget Config Schemas --- export const appWidgetConfigSchema = z.object({ appId: z.string().min(1, 'App ID is required') }); export const bookmarkWidgetConfigSchema = z.object({ url: z.string().url('Invalid URL'), label: z.string().min(1, 'Label is required').max(200), icon: z.string().max(100).optional(), description: z.string().max(500).optional() }); export const noteWidgetConfigSchema = z.object({ content: z.string().max(10000, 'Content too long'), format: z.enum(['markdown', 'text', 'html']).default('markdown') }); export const embedWidgetConfigSchema = z.object({ url: z.string().url('Invalid URL'), height: z.number().int().min(100).max(2000).default(300), sandbox: z.string().max(200).optional() }); export const statusWidgetConfigSchema = z.object({ appIds: z.array(z.string().min(1)).min(1, 'At least one app is required'), label: z.string().max(200).optional() }); // --- Widget --- const allWidgetTypes = [ WidgetType.APP, WidgetType.BOOKMARK, WidgetType.NOTE, WidgetType.EMBED, WidgetType.STATUS, WidgetType.CLOCK, WidgetType.SYSTEM_STATS, WidgetType.RSS, WidgetType.CALENDAR, WidgetType.MARKDOWN, WidgetType.METRIC, WidgetType.LINK_GROUP, WidgetType.CAMERA, WidgetType.INTEGRATION ] as const; export const createWidgetSchema = z.object({ sectionId: z.string().cuid(), type: z.enum(allWidgetTypes), order: z.number().int().min(0).optional(), config: z.string().optional(), appId: z.string().cuid().optional() }); export const updateWidgetSchema = z.object({ type: z.enum(allWidgetTypes).optional(), order: z.number().int().min(0).optional(), config: z.string().optional(), appId: z.string().cuid().nullable().optional() }); // --- Permission --- export const createPermissionSchema = z.object({ entityType: z.enum([EntityType.BOARD, EntityType.APP]), entityId: z.string().cuid(), targetType: z.enum([TargetType.USER, TargetType.GROUP]), targetId: z.string().cuid(), level: z.enum([PermissionLevel.VIEW, PermissionLevel.EDIT, PermissionLevel.ADMIN]) }); // --- Backup Schedule --- export const updateBackupScheduleSchema = z.object({ backupEnabled: z.boolean().optional(), backupCronExpression: z.string().min(1).max(100).optional(), backupMaxCount: z.number().int().min(1).max(100).optional() }); // --- System Settings --- export const updateSystemSettingsSchema = z.object({ authMode: z.enum([AuthMode.LOCAL, AuthMode.OAUTH, AuthMode.BOTH]).optional(), registrationEnabled: z.boolean().optional(), oauthClientId: z.string().nullable().optional(), oauthClientSecret: z.string().nullable().optional(), oauthDiscoveryUrl: z.string().url().nullable().optional(), defaultTheme: z.enum(['dark', 'light']).optional(), defaultPrimaryColor: z .string() .regex(/^#[0-9a-fA-F]{6}$/, 'Invalid hex color') .optional(), healthcheckDefaults: z.string().optional(), customCss: z.string().max(10000).nullable().optional(), onboardingComplete: z.boolean().optional() }); // --- New widget config schemas for Phases 4-7 --- export const clockWeatherWidgetConfigSchema = z.object({ timezone: z.string().max(100).optional(), showWeather: z.boolean().optional(), latitude: z.number().min(-90).max(90).optional(), longitude: z.number().min(-180).max(180).optional(), clockStyle: z.enum(['analog', 'digital', '24h']).optional() }); export const systemStatsWidgetConfigSchema = z.object({ sourceUrl: z.string().url('Invalid source URL'), sourceType: z.enum(['glances', 'prometheus', 'custom']), metrics: z.array(z.string().min(1)).min(1, 'At least one metric is required'), refreshInterval: z.number().int().min(5).max(3600).optional() }); export const rssWidgetConfigSchema = z.object({ feedUrl: z.string().url('Invalid feed URL'), maxItems: z.number().int().min(1).max(50).optional(), showSummary: z.boolean().optional() }); export const calendarWidgetConfigSchema = z.object({ icalUrls: z .array( z.object({ url: z.string().url('Invalid iCal URL'), color: z .string() .regex(/^#[0-9a-fA-F]{6}$/, 'Invalid hex color') .optional(), label: z.string().max(100).optional() }) ) .min(1, 'At least one calendar URL is required'), daysAhead: z.number().int().min(1).max(90).optional() }); export const markdownWidgetConfigSchema = z.object({ content: z.string().max(50000, 'Content too long'), syntaxTheme: z.string().max(50).optional() }); export const metricWidgetConfigSchema = z.object({ label: z.string().min(1, 'Label is required').max(200), source: z.enum(['static', 'json', 'prometheus']), value: z.string().max(200).optional(), url: z.string().url().optional(), jsonPath: z.string().max(200).optional(), query: z.string().max(500).optional(), unit: z.string().max(50).optional(), refreshInterval: z.number().int().min(5).max(3600).optional() }); export const linkGroupWidgetConfigSchema = z.object({ links: z .array( z.object({ label: z.string().min(1).max(200), url: z.string().url('Invalid URL'), icon: z.string().max(100).optional() }) ) .min(1, 'At least one link is required'), collapsible: z.boolean().optional() }); export const cameraWidgetConfigSchema = z.object({ streamUrl: z.string().url('Invalid stream URL'), type: z.enum(['mjpeg', 'hls', 'image']), refreshInterval: z.number().int().min(1).max(3600).optional(), aspectRatio: z.string().max(20).optional() }); export const integrationWidgetConfigSchema = z.object({ appId: z.string().min(1, 'App ID is required'), endpointId: z.string().min(1, 'Endpoint ID is required'), refreshInterval: z.number().int().min(5).max(3600).optional() }); // --- New entity schemas for Phases 4-7 --- export const createTagSchema = z.object({ name: z.string().min(1, 'Tag name is required').max(50), color: z .string() .regex(/^#[0-9a-fA-F]{6}$/, 'Invalid hex color') .optional() }); export const updateTagSchema = z.object({ name: z.string().min(1).max(50).optional(), color: z .string() .regex(/^#[0-9a-fA-F]{6}$/, 'Invalid hex color') .nullable() .optional() }); export const createAppLinkSchema = z.object({ appId: z.string().cuid(), label: z.string().min(1, 'Label is required').max(200), url: z.string().url('Invalid URL'), icon: z.string().max(100).optional(), order: z.number().int().min(0).optional() }); export const updateAppLinkSchema = z.object({ label: z.string().min(1).max(200).optional(), url: z.string().url().optional(), icon: z.string().max(100).nullable().optional(), order: z.number().int().min(0).optional() }); export const createNotificationChannelSchema = z.object({ type: z.enum([ NotificationType.DISCORD, NotificationType.SLACK, NotificationType.TELEGRAM, NotificationType.HTTP ]), config: z.string().min(1, 'Config is required'), enabled: z.boolean().optional() }); export const updateNotificationChannelSchema = z.object({ type: z .enum([ NotificationType.DISCORD, NotificationType.SLACK, NotificationType.TELEGRAM, NotificationType.HTTP ]) .optional(), config: z.string().min(1).optional(), enabled: z.boolean().optional() }); export const createApiTokenSchema = z.object({ name: z.string().min(1, 'Token name is required').max(100), scope: z.enum([ApiTokenScope.READ, ApiTokenScope.WRITE, ApiTokenScope.ADMIN]), expiresAt: z.string().datetime().optional() }); export const createBoardTemplateSchema = z.object({ name: z.string().min(1, 'Template name is required').max(200), description: z.string().max(1000).optional(), icon: z.string().max(500).optional(), config: z.string().min(1, 'Config is required'), isBuiltin: z.boolean().optional() }); export const auditLogQuerySchema = z.object({ action: z .enum([ AuditAction.USER_CREATED, AuditAction.USER_DELETED, AuditAction.USER_UPDATED, AuditAction.BOARD_CREATED, AuditAction.BOARD_DELETED, AuditAction.APP_CREATED, AuditAction.APP_DELETED, AuditAction.SETTINGS_UPDATED, AuditAction.IMPORT, AuditAction.EXPORT, AuditAction.BACKUP_CREATED, AuditAction.BACKUP_RESTORED, AuditAction.BACKUP_DELETED ]) .optional(), entityType: z.string().max(50).optional(), dateFrom: z.string().datetime().optional(), dateTo: z.string().datetime().optional(), page: z.number().int().min(1).optional(), limit: z.number().int().min(1).max(100).optional() });