feat: Phase 2 – Benutzerverwaltung (CRUD-API, Profil, UI)

This commit is contained in:
MPM Dev
2026-10-06 14:58:36 +02:00
parent a7e1c421f2
commit e87dc1f836
25 changed files with 2056 additions and 75 deletions

View File

@@ -34,4 +34,76 @@ export const healthSchema = z.object({
}),
}),
});
export type Health = z.infer<typeof healthSchema>;
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>;