feat(dsgvo): Art. 15 Auskunftsrecht – strukturierter Datenexport pro User

GET /users/{id}/export-data liefert alle personenbezogenen Daten
(Profil, Zeiterfassung, Abwesenheiten, Kommentare, Urlaubs-/Überstunden-
konto, Auszahlungen, AuditLog-Einträge über den User) als JSON-Download.
Eigenes Konto immer erlaubt, fremde Konten nur HR/Admin/Manager der
eigenen Firma. Ergänzt die bestehende Art.-17-Anonymisierung.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CviFgc739S89xS97pvjszj
This commit is contained in:
2026-08-07 08:19:15 +02:00
co-authored by Claude Sonnet 5
parent 59a0305a75
commit 23f8e634d1
2 changed files with 153 additions and 1 deletions
+26 -1
View File
@@ -2,7 +2,7 @@ from typing import Annotated
from uuid import UUID
from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile
from fastapi.responses import PlainTextResponse
from fastapi.responses import PlainTextResponse, Response
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
@@ -22,6 +22,7 @@ from app.schemas.user import (
UserUpdate,
)
from app.services import user_import_service
from app.services.gdpr_service import gdpr_service
from app.services.user_service import user_service
router = APIRouter(prefix="/users", tags=["Users"])
@@ -244,6 +245,30 @@ async def anonymize_user(
return UserOut.model_validate(user)
@router.get("/{user_id}/export-data")
async def export_user_data(
user_id: UUID,
current_user: CurrentUser,
db: AsyncSession = Depends(get_db),
):
"""DSGVO Art. 15 (Auskunftsrecht): strukturierter Export aller
personenbezogenen Daten des Users als JSON. Eigenes Konto immer erlaubt,
fremde Konten nur für HR/Admin/Manager der eigenen Firma."""
if user_id != current_user.id and current_user.role not in _hr_roles:
raise HTTPException(status_code=403, detail="Keine Berechtigung für dieses Konto")
try:
data = await gdpr_service.export_user_data(user_id, current_user.company_id, db)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
content = gdpr_service.to_json(data)
return Response(
content=content, media_type="application/json",
headers={"Content-Disposition": f"attachment; filename=datenauskunft_{user_id}.json"},
)
@router.post("/{user_id}/kiosk-pin", response_model=MessageResponse)
async def set_kiosk_pin(
user_id: UUID,