A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
112 lines
4.0 KiB
Python
112 lines
4.0 KiB
Python
"""
|
|
Data Subject Request (DSR) API - Betroffenenanfragen nach DSGVO
|
|
Benutzer-Endpunkte zum Erstellen und Verwalten eigener Betroffenenanfragen
|
|
"""
|
|
|
|
from fastapi import APIRouter, HTTPException, Header, Query
|
|
from typing import Optional, List, Dict, Any
|
|
from pydantic import BaseModel, EmailStr
|
|
import httpx
|
|
import os
|
|
|
|
from consent_client import generate_jwt_token, JWT_SECRET
|
|
|
|
# Consent Service URL
|
|
CONSENT_SERVICE_URL = os.getenv("CONSENT_SERVICE_URL", "http://localhost:8081")
|
|
|
|
router = APIRouter(prefix="/v1/dsr", tags=["dsr"])
|
|
|
|
|
|
# Request Models
|
|
class CreateDSRRequest(BaseModel):
|
|
"""Anfrage zum Erstellen einer Betroffenenanfrage"""
|
|
request_type: str # access, rectification, erasure, restriction, portability
|
|
requester_email: Optional[str] = None
|
|
requester_name: Optional[str] = None
|
|
requester_phone: Optional[str] = None
|
|
request_details: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
# Helper to extract token
|
|
def get_token(authorization: Optional[str]) -> str:
|
|
if authorization:
|
|
parts = authorization.split(" ")
|
|
if len(parts) == 2 and parts[0] == "Bearer":
|
|
return parts[1]
|
|
raise HTTPException(status_code=401, detail="Authorization required")
|
|
|
|
|
|
async def proxy_request(method: str, path: str, token: str, json_data=None, query_params=None):
|
|
"""Proxied Anfragen an den Go Consent Service"""
|
|
url = f"{CONSENT_SERVICE_URL}/api/v1{path}"
|
|
headers = {
|
|
"Authorization": f"Bearer {token}",
|
|
"Content-Type": "application/json"
|
|
}
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
try:
|
|
if method == "GET":
|
|
response = await client.get(url, headers=headers, params=query_params, timeout=10.0)
|
|
elif method == "POST":
|
|
response = await client.post(url, headers=headers, json=json_data, timeout=10.0)
|
|
elif method == "PUT":
|
|
response = await client.put(url, headers=headers, json=json_data, timeout=10.0)
|
|
elif method == "DELETE":
|
|
response = await client.delete(url, headers=headers, timeout=10.0)
|
|
else:
|
|
raise HTTPException(status_code=400, detail="Invalid method")
|
|
|
|
if response.status_code >= 400:
|
|
error_detail = response.json() if response.content else {"error": "Unknown error"}
|
|
raise HTTPException(status_code=response.status_code, detail=error_detail)
|
|
|
|
return response.json() if response.content else {"success": True}
|
|
|
|
except httpx.RequestError as e:
|
|
raise HTTPException(status_code=503, detail=f"Consent Service unavailable: {str(e)}")
|
|
|
|
|
|
# ==========================================
|
|
# User DSR Endpoints
|
|
# ==========================================
|
|
|
|
@router.post("")
|
|
async def create_dsr(
|
|
request: CreateDSRRequest,
|
|
authorization: str = Header(...)
|
|
):
|
|
"""
|
|
Erstellt eine neue Betroffenenanfrage.
|
|
|
|
request_type muss einer der folgenden Werte sein:
|
|
- access: Auskunftsrecht (Art. 15 DSGVO)
|
|
- rectification: Recht auf Berichtigung (Art. 16 DSGVO)
|
|
- erasure: Recht auf Löschung (Art. 17 DSGVO)
|
|
- restriction: Recht auf Einschränkung (Art. 18 DSGVO)
|
|
- portability: Recht auf Datenübertragbarkeit (Art. 20 DSGVO)
|
|
"""
|
|
token = get_token(authorization)
|
|
return await proxy_request("POST", "/dsr", token, request.dict(exclude_none=True))
|
|
|
|
|
|
@router.get("")
|
|
async def get_my_dsrs(authorization: str = Header(...)):
|
|
"""Gibt alle eigenen Betroffenenanfragen zurück"""
|
|
token = get_token(authorization)
|
|
return await proxy_request("GET", "/dsr", token)
|
|
|
|
|
|
@router.get("/{dsr_id}")
|
|
async def get_my_dsr(dsr_id: str, authorization: str = Header(...)):
|
|
"""Gibt Details einer eigenen Betroffenenanfrage zurück"""
|
|
token = get_token(authorization)
|
|
return await proxy_request("GET", f"/dsr/{dsr_id}", token)
|
|
|
|
|
|
@router.post("/{dsr_id}/cancel")
|
|
async def cancel_my_dsr(dsr_id: str, authorization: str = Header(...)):
|
|
"""Storniert eine eigene Betroffenenanfrage"""
|
|
token = get_token(authorization)
|
|
return await proxy_request("POST", f"/dsr/{dsr_id}/cancel", token)
|