Files
mpm/apps/platform-frontend/src/lib/schemas.ts
2026-10-08 21:13:57 +02:00

175 lines
5.8 KiB
TypeScript

import { z } from 'zod';
/** Globale Plattform-Rollen (muss zum Backend passen). */
export const ROLE_NAMES = ['ADMIN', 'USER'] as const;
export type RoleName = (typeof ROLE_NAMES)[number];
/** Öffentliche Benutzerdaten (API-Vertrag /api/v1/auth/me). */
export const authUserSchema = z.object({
id: z.string(),
username: z.string(),
email: z.string(),
displayName: z.string(),
role: z.enum(ROLE_NAMES),
});
export type AuthUser = z.infer<typeof authUserSchema>;
/** Login-Anfrage (Client-Validierung ist UX; Server validiert erneut). */
export const loginSchema = z.object({
username: z.string().trim().min(1, 'Benutzername ist erforderlich'),
password: z.string().min(1, 'Passwort ist erforderlich'),
});
export type LoginDto = z.infer<typeof loginSchema>;
/** Health-Antwort (API-Vertrag /api/v1/health). */
export const healthSchema = z.object({
status: z.enum(['healthy', 'unhealthy']),
version: z.string(),
uptimeSeconds: z.number(),
components: z.object({
backend: z.enum(['healthy']),
database: z.object({
status: z.enum(['healthy', 'unhealthy']),
latencyMs: z.number(),
}),
}),
});
export type Health = z.infer<typeof healthSchema>;
/** Benutzer-Datensatz der Admin-API (/api/v1/users). */
export const userSchema = z.object({
id: z.string(),
username: z.string(),
email: z.string(),
displayName: z.string(),
role: z.enum(ROLE_NAMES),
isActive: z.boolean(),
lastLoginAt: z.string().nullable(),
createdAt: z.string(),
});
export type User = z.infer<typeof userSchema>;
/** Profil des angemeldeten Benutzers (/api/v1/profile). */
export const profileSchema = z.object({
id: z.string(),
username: z.string(),
email: z.string(),
displayName: z.string(),
role: z.enum(ROLE_NAMES),
lastLoginAt: z.string().nullable(),
createdAt: z.string(),
});
export type Profile = z.infer<typeof profileSchema>;
/** Benutzer anlegen (Client-Validierung ist UX; Server validiert erneut). */
export const createUserSchema = z.object({
username: z
.string()
.trim()
.min(3, 'Benutzername muss mindestens 3 Zeichen lang sein')
.max(100)
.regex(/^[A-Za-z0-9._-]+$/, 'Nur Buchstaben, Zahlen sowie . _ - erlaubt'),
email: z.string().trim().email('Ungültige E-Mail-Adresse'),
displayName: z.string().trim().min(1, 'Anzeigename ist erforderlich'),
password: z.string().min(10, 'Passwort muss mindestens 10 Zeichen lang sein'),
role: z.enum(ROLE_NAMES),
});
export type CreateUserDto = z.infer<typeof createUserSchema>;
/** Benutzer bearbeiten. */
export const updateUserSchema = z
.object({
email: z.string().trim().email('Ungültige E-Mail-Adresse').optional(),
displayName: z.string().trim().min(1, 'Anzeigename ist erforderlich').optional(),
role: z.enum(ROLE_NAMES).optional(),
isActive: z.boolean().optional(),
})
.refine((data) => Object.values(data).some((value) => value !== undefined), {
message: 'Mindestens ein Feld ist erforderlich',
});
export type UpdateUserDto = z.infer<typeof updateUserSchema>;
/** Passwort zurücksetzen (Admin). */
export const resetPasswordSchema = z.object({
newPassword: z.string().min(10, 'Passwort muss mindestens 10 Zeichen lang sein'),
});
export type ResetPasswordDto = z.infer<typeof resetPasswordSchema>;
/** Eigenes Passwort ändern. */
export const changePasswordSchema = z
.object({
currentPassword: z.string().min(1, 'Aktuelles Passwort ist erforderlich'),
newPassword: z.string().min(10, 'Passwort muss mindestens 10 Zeichen lang sein'),
confirmPassword: z.string().min(1, 'Passwortbestätigung ist erforderlich'),
})
.refine((data) => data.newPassword === data.confirmPassword, {
message: 'Passwörter stimmen nicht überein',
path: ['confirmPassword'],
});
export type ChangePasswordDto = z.infer<typeof changePasswordSchema>;
/** Modul-Lifecycle-Zustände (muss zum Backend passen). */
export const MODULE_STATUSES = [
'INSTALLED',
'STARTING',
'RUNNING',
'STOPPING',
'STOPPED',
'ERROR',
'DISABLED',
] as const;
export type ModuleStatus = (typeof MODULE_STATUSES)[number];
/** Modul-Datensatz der Admin-API (/api/v1/modules). */
export const moduleSchema = z.object({
id: z.string(),
moduleId: z.string(),
name: z.string(),
slug: z.string(),
version: z.string(),
description: z.string(),
author: z.string(),
status: z.enum(MODULE_STATUSES),
internalPort: z.number(),
healthcheckUrl: z.string(),
enabled: z.boolean(),
createdAt: z.string(),
configuration: z.array(z.object({
key: z.string(), label: z.string(), description: z.string(),
type: z.enum(['text', 'url', 'boolean']), secret: z.boolean(), required: z.boolean(),
defaultValue: z.string().optional(), services: z.array(z.string()),
})),
configurationReady: z.boolean(),
});
export type Module = z.infer<typeof moduleSchema>;
export const moduleConfigurationSchema = z.object({
ready: z.boolean(),
fields: z.array(z.object({
key: z.string(), label: z.string(), description: z.string(),
type: z.enum(['text', 'url', 'boolean']), secret: z.boolean(), required: z.boolean(),
services: z.array(z.string()), isSet: z.boolean(), value: z.string().optional(),
})),
});
export type ModuleConfiguration = z.infer<typeof moduleConfigurationSchema>;
/** Modul-Kachel für das Dashboard (/api/v1/profile/modules). */
export const accessibleModuleSchema = z.object({
id: z.string(),
moduleId: z.string(),
name: z.string(),
slug: z.string(),
description: z.string(),
status: z.enum(MODULE_STATUSES),
});
export type AccessibleModule = z.infer<typeof accessibleModuleSchema>;
/** Modul-Berechtigung eines Benutzers. */
export const modulePermissionSchema = z.object({
moduleId: z.string(),
moduleSlug: z.string(),
moduleName: z.string(),
permission: z.enum(['GRANTED', 'DENIED']),
});
export type ModulePermission = z.infer<typeof modulePermissionSchema>;