feat(dsr): Go DSR deprecated, Python Export-Endpoint, Frontend an Backend-APIs anbinden
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 34s
CI / test-python-backend-compliance (push) Successful in 30s
CI / test-python-document-crawler (push) Successful in 22s
CI / test-python-dsms-gateway (push) Successful in 17s

- Go: DEPRECATED-Kommentare an allen DSR-Handlern und Routes
- Python: GET /dsr/export?format=csv|json (Semikolon-CSV, 12 Spalten)
- API-Client: 12 neue Funktionen (verify, assign, extend, complete, reject, communications, exception-checks, history)
- Detail-Seite: Alle Actions verdrahtet (keine Coming-soon-Alerts mehr), Communications + Art.17(3)-Checks + Audit-Log live
- Haupt-Seite: CSV-Export-Button im Header
- Tests: 54/54 bestanden (4 neue Export-Tests)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-06 18:21:43 +01:00
parent 3593a4ff78
commit 095eff26d9
7 changed files with 526 additions and 102 deletions

View File

@@ -4,11 +4,14 @@ DSR (Data Subject Request) Routes — Betroffenenanfragen nach DSGVO Art. 15-21.
Native Python/FastAPI Implementierung, ersetzt Go consent-service Proxy.
"""
import io
import csv
import uuid
from datetime import datetime, timedelta
from typing import Optional, List, Dict, Any
from fastapi import APIRouter, Depends, HTTPException, Query, Header
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from sqlalchemy.orm import Session
from sqlalchemy import text, func, and_, or_, cast, String
@@ -438,6 +441,61 @@ async def get_dsr_stats(
}
# =============================================================================
# Export
# =============================================================================
@router.get("/export")
async def export_dsrs(
format: str = Query("csv", pattern="^(csv|json)$"),
tenant_id: str = Depends(_get_tenant),
db: Session = Depends(get_db),
):
"""Exportiert alle DSRs als CSV oder JSON."""
tid = uuid.UUID(tenant_id)
dsrs = db.query(DSRRequestDB).filter(
DSRRequestDB.tenant_id == tid,
).order_by(DSRRequestDB.created_at.desc()).all()
if format == "json":
return {
"exported_at": datetime.utcnow().isoformat(),
"total": len(dsrs),
"requests": [_dsr_to_dict(d) for d in dsrs],
}
# CSV export (semicolon-separated, matching Go format + extended fields)
output = io.StringIO()
writer = csv.writer(output, delimiter=';', quoting=csv.QUOTE_MINIMAL)
writer.writerow([
"ID", "Referenznummer", "Typ", "Name", "E-Mail", "Status",
"Prioritaet", "Eingegangen", "Frist", "Abgeschlossen", "Quelle", "Zugewiesen",
])
for dsr in dsrs:
writer.writerow([
str(dsr.id),
dsr.request_number or "",
dsr.request_type or "",
dsr.requester_name or "",
dsr.requester_email or "",
dsr.status or "",
dsr.priority or "",
dsr.received_at.strftime("%Y-%m-%d") if dsr.received_at else "",
dsr.deadline_at.strftime("%Y-%m-%d") if dsr.deadline_at else "",
dsr.completed_at.strftime("%Y-%m-%d") if dsr.completed_at else "",
dsr.source or "",
dsr.assigned_to or "",
])
output.seek(0)
return StreamingResponse(
output,
media_type="text/csv; charset=utf-8",
headers={"Content-Disposition": "attachment; filename=dsr_export.csv"},
)
# =============================================================================
# Deadline Processing (MUST be before /{dsr_id} to avoid path conflicts)
# =============================================================================

View File

@@ -697,3 +697,49 @@ class TestDSRTemplates:
fake_id = str(uuid.uuid4())
resp = client.get(f"/api/compliance/dsr/templates/{fake_id}/versions", headers=HEADERS)
assert resp.status_code == 404
class TestDSRExport:
"""Tests for DSR export endpoint."""
def test_export_csv_empty(self):
resp = client.get("/api/compliance/dsr/export?format=csv", headers=HEADERS)
assert resp.status_code == 200
assert "text/csv" in resp.headers.get("content-type", "")
lines = resp.text.strip().split("\n")
assert len(lines) == 1 # Header only
assert "Referenznummer" in lines[0]
assert "Zugewiesen" in lines[0]
def test_export_csv_with_data(self):
# Create a DSR first
body = {
"request_type": "access",
"requester_name": "Export Test",
"requester_email": "export@example.de",
"source": "email",
}
create_resp = client.post("/api/compliance/dsr", json=body, headers=HEADERS)
assert create_resp.status_code == 200
resp = client.get("/api/compliance/dsr/export?format=csv", headers=HEADERS)
assert resp.status_code == 200
lines = resp.text.strip().split("\n")
assert len(lines) >= 2 # Header + at least 1 data row
# Check data row contains our test data
assert "Export Test" in lines[1]
assert "export@example.de" in lines[1]
assert "access" in lines[1]
def test_export_json(self):
resp = client.get("/api/compliance/dsr/export?format=json", headers=HEADERS)
assert resp.status_code == 200
data = resp.json()
assert "exported_at" in data
assert "total" in data
assert "requests" in data
assert isinstance(data["requests"], list)
def test_export_invalid_format(self):
resp = client.get("/api/compliance/dsr/export?format=xml", headers=HEADERS)
assert resp.status_code == 422 # Validation error