CC2 R4 #4: /api/users/me/gdpr-export alias
- New auth.gdpr.me_router prefix /api/users/me with: - GET/POST /gdpr-export → Art.20 JSON download with Content-Disposition - POST /gdpr-erase → Art.17 erasure request - GET /gdpr-consent → consent history for caller - jsonable_encoder fixes datetime serialisation in JSONResponse - admin_users.html: 'Izvezi moje podatke' now POSTs to alias and uses filename from Content-Disposition header - 401 enforced on no-auth, 200 on valid Bearer (verified live)
This commit is contained in:
@@ -16,6 +16,7 @@ from typing import Optional, Dict, List
|
||||
from fastapi import APIRouter, HTTPException, Depends, Request, Body
|
||||
from pydantic import BaseModel
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
|
||||
from .auth_v2 import (
|
||||
db_query, db_one, db_exec,
|
||||
@@ -25,6 +26,7 @@ from .admin_users import _is_pgz_admin
|
||||
|
||||
router = APIRouter(prefix="/api/gdpr", tags=["gdpr"])
|
||||
admin_router = APIRouter(prefix="/api/admin/gdpr", tags=["gdpr_admin"])
|
||||
me_router = APIRouter(prefix="/api/users/me", tags=["users_me_gdpr"])
|
||||
|
||||
# Ensure GDPR tables exist (idempotent)
|
||||
def _ensure_tables():
|
||||
@@ -174,6 +176,31 @@ def request_erasure(req: EraseReq, request: Request, user = Depends(require_user
|
||||
return {"status": "ok", "request_id": new_id,
|
||||
"message": "Vaš zahtjev je zaprimljen i bit će obrađen unutar 30 dana."}
|
||||
|
||||
# ─────────────────────────── Admin: erasure queue ───────────────────────────
|
||||
# ─────────────────────────── /api/users/me alias (R4 #4) ───────────────────────────
|
||||
@me_router.get("/gdpr-export")
|
||||
@me_router.post("/gdpr-export")
|
||||
def me_gdpr_export(user = Depends(require_user)):
|
||||
"""GDPR Art. 20 — JSON export of all data we hold about the caller.
|
||||
Same payload as GET /api/gdpr/export, exposed at user-friendly path.
|
||||
Returns Content-Disposition: attachment so browsers offer a download."""
|
||||
payload = export_my_data(user=user)
|
||||
fn = f"pgz_data_export_{user['id']}_{int(datetime.utcnow().timestamp())}.json"
|
||||
return JSONResponse(jsonable_encoder(payload),
|
||||
headers={"Content-Disposition": f'attachment; filename="{fn}"'})
|
||||
|
||||
@me_router.post("/gdpr-erase")
|
||||
def me_gdpr_erase(req: 'EraseReq', request: Request, user = Depends(require_user)):
|
||||
return request_erasure(req=req, request=request, user=user)
|
||||
|
||||
@me_router.get("/gdpr-consent")
|
||||
def me_gdpr_consent(user = Depends(require_user)):
|
||||
rows = db_query("""SELECT necessary, analytics, marketing, consent_at,
|
||||
policy_version, ip, session_id
|
||||
FROM pgz_sport.gdpr_consent WHERE user_id=%s
|
||||
ORDER BY consent_at DESC LIMIT 50""", (user["id"],))
|
||||
return {"current": rows[0] if rows else None, "history": rows}
|
||||
|
||||
# ─────────────────────────── Admin: erasure queue ───────────────────────────
|
||||
@admin_router.get("/erasure-requests")
|
||||
def list_erasure_requests(status: Optional[str] = None,
|
||||
|
||||
Reference in New Issue
Block a user