mirror of
http://172.20.10.11:3000/gitadmin/INSIGHT-MVP.git
synced 2026-06-24 21:16:40 +02:00
feat: add user deletion (backend endpoint + frontend with confirmation)
- Backend: DELETE /api/v1/users/:id endpoint (PLATFORM_ADMIN only) - Frontend: "Löschen" button with confirmation modal - Cascading deletes handle auth providers, memberships, profiles Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
85574a85aa
commit
8efaa49930
3 changed files with 101 additions and 0 deletions
|
|
@ -3,6 +3,7 @@ import {
|
|||
Get,
|
||||
Post,
|
||||
Patch,
|
||||
Delete,
|
||||
Body,
|
||||
Param,
|
||||
Query,
|
||||
|
|
@ -124,4 +125,16 @@ export class UsersController {
|
|||
) {
|
||||
return this.usersService.update(id, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /api/v1/users/:id
|
||||
* User löschen (nur PLATFORM_ADMIN).
|
||||
*/
|
||||
@Delete(':id')
|
||||
@Roles('PLATFORM_ADMIN')
|
||||
@UseGuards(RolesGuard)
|
||||
@ApiOperation({ summary: 'Benutzer löschen (Admin)' })
|
||||
async delete(@Param('id', ParseUUIDPipe) id: string) {
|
||||
return this.usersService.delete(id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -239,6 +239,21 @@ export class UsersService {
|
|||
this.logger.log(`Passwort geändert für User ${user.email}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* User löschen (inkl. Auth-Provider, Memberships, Profil via Cascade).
|
||||
*/
|
||||
async delete(id: string) {
|
||||
const user = await this.prisma.user.findUnique({ where: { id } });
|
||||
if (!user) {
|
||||
throw new NotFoundException('Benutzer nicht gefunden');
|
||||
}
|
||||
|
||||
await this.prisma.user.delete({ where: { id } });
|
||||
this.logger.log(`User gelöscht: ${user.email}`);
|
||||
|
||||
return { message: 'Benutzer wurde gelöscht' };
|
||||
}
|
||||
|
||||
/**
|
||||
* Alle User auflisten (für Admin).
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useState } from 'react';
|
||||
import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
|
||||
import api from '../api/client';
|
||||
import { Modal } from '../components/Modal';
|
||||
import { UserFormModal } from './UserFormModal';
|
||||
|
||||
interface User {
|
||||
|
|
@ -48,6 +49,7 @@ export function AdminUsersPage() {
|
|||
const queryClient = useQueryClient();
|
||||
const [isCreateModalOpen, setCreateModalOpen] = useState(false);
|
||||
const [editingUser, setEditingUser] = useState<User | null>(null);
|
||||
const [deletingUser, setDeletingUser] = useState<User | null>(null);
|
||||
|
||||
const { data, isLoading, error } = useQuery<UsersResponse>({
|
||||
queryKey: ['admin', 'users'],
|
||||
|
|
@ -65,6 +67,14 @@ export function AdminUsersPage() {
|
|||
},
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: (id: string) => api.delete(`/users/${id}`),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] });
|
||||
setDeletingUser(null);
|
||||
},
|
||||
});
|
||||
|
||||
if (isLoading) return <p>Laden...</p>;
|
||||
if (error) return <p style={{ color: 'var(--color-error)' }}>Fehler beim Laden der Benutzer</p>;
|
||||
|
||||
|
|
@ -181,6 +191,20 @@ export function AdminUsersPage() {
|
|||
>
|
||||
{user.isActive ? 'Deaktivieren' : 'Aktivieren'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setDeletingUser(user)}
|
||||
style={{
|
||||
padding: '0.25rem 0.625rem',
|
||||
fontSize: '0.8125rem',
|
||||
background: 'transparent',
|
||||
border: '1px solid #fecaca',
|
||||
borderRadius: 'var(--radius-sm)',
|
||||
cursor: 'pointer',
|
||||
color: 'var(--color-error)',
|
||||
}}
|
||||
>
|
||||
Löschen
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -204,6 +228,55 @@ export function AdminUsersPage() {
|
|||
user={editingUser}
|
||||
onSuccess={() => setEditingUser(null)}
|
||||
/>
|
||||
|
||||
{/* Modal: Benutzer löschen — Bestätigung */}
|
||||
<Modal
|
||||
isOpen={!!deletingUser}
|
||||
onClose={() => setDeletingUser(null)}
|
||||
title="Benutzer löschen"
|
||||
maxWidth="420px"
|
||||
>
|
||||
<p style={{ fontSize: '0.9375rem', color: 'var(--color-text)', marginBottom: '0.5rem' }}>
|
||||
Soll der Benutzer <strong>{deletingUser?.firstName} {deletingUser?.lastName}</strong> ({deletingUser?.email}) wirklich gelöscht werden?
|
||||
</p>
|
||||
<p style={{ fontSize: '0.8125rem', color: 'var(--color-error)', marginBottom: '1.5rem' }}>
|
||||
Diese Aktion kann nicht rückgängig gemacht werden. Alle Daten des Benutzers werden unwiderruflich gelöscht.
|
||||
</p>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '0.75rem' }}>
|
||||
<button
|
||||
onClick={() => setDeletingUser(null)}
|
||||
disabled={deleteMutation.isPending}
|
||||
style={{
|
||||
padding: '0.5rem 1rem',
|
||||
background: 'transparent',
|
||||
border: '1px solid var(--color-border)',
|
||||
borderRadius: 'var(--radius-sm)',
|
||||
fontSize: '0.875rem',
|
||||
cursor: 'pointer',
|
||||
color: 'var(--color-text-secondary)',
|
||||
}}
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
<button
|
||||
onClick={() => deletingUser && deleteMutation.mutate(deletingUser.id)}
|
||||
disabled={deleteMutation.isPending}
|
||||
style={{
|
||||
padding: '0.5rem 1rem',
|
||||
background: 'var(--color-error)',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: 'var(--radius-sm)',
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 600,
|
||||
cursor: deleteMutation.isPending ? 'wait' : 'pointer',
|
||||
opacity: deleteMutation.isPending ? 0.7 : 1,
|
||||
}}
|
||||
>
|
||||
{deleteMutation.isPending ? 'Löschen...' : 'Endgültig löschen'}
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue