Feature+Security: DSGVO-Datenexport, auth-geschützte Media, Datenschutzerklärung v2 (SW by-v880)
This commit is contained in:
parent
465dc2e4d3
commit
bf1087c5e1
7 changed files with 264 additions and 27 deletions
|
|
@ -167,3 +167,177 @@ async def delete_account(user=Depends(get_current_user)):
|
|||
conn.execute("DELETE FROM forum_posts WHERE user_id=?", (uid,))
|
||||
conn.execute("DELETE FROM users WHERE id=?", (uid,))
|
||||
return {"status": "deleted"}
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# GET /profile/export — DSGVO Datenexport (Art. 20)
|
||||
# ----------------------------------------------------------
|
||||
@router.get('/export')
|
||||
async def export_user_data(user=Depends(get_current_user)):
|
||||
"""Gibt alle personenbezogenen Daten des Users als JSON zurück."""
|
||||
import json as _json
|
||||
from datetime import datetime as _dt
|
||||
from fastapi.responses import Response as _Response
|
||||
uid = user['id']
|
||||
|
||||
with db() as conn:
|
||||
# --- Nutzerprofil ---
|
||||
u = dict(conn.execute(
|
||||
"SELECT id, name, email, bio, wohnort, erfahrung, social_link, "
|
||||
"email_verified, is_premium, subscription_tier, created_at "
|
||||
"FROM users WHERE id=?", (uid,)
|
||||
).fetchone() or {})
|
||||
|
||||
# --- Hunde ---
|
||||
dogs_raw = conn.execute(
|
||||
"SELECT * FROM dogs WHERE user_id=?", (uid,)
|
||||
).fetchall()
|
||||
dogs_out = []
|
||||
|
||||
for dog in dogs_raw:
|
||||
did = dog['id']
|
||||
d = dict(dog)
|
||||
|
||||
# Tagebuch
|
||||
diary_rows = conn.execute(
|
||||
"SELECT id, datum, typ, titel, text, gps_lat, gps_lon, "
|
||||
"location_name, is_milestone, created_at FROM diary WHERE dog_id=?",
|
||||
(did,)
|
||||
).fetchall()
|
||||
diary_out = []
|
||||
for de in diary_rows:
|
||||
de_dict = dict(de)
|
||||
media = conn.execute(
|
||||
"SELECT url, preview_url, media_type FROM diary_media WHERE diary_id=?",
|
||||
(de['id'],)
|
||||
).fetchall()
|
||||
de_dict['media'] = [dict(m) for m in media]
|
||||
diary_out.append(de_dict)
|
||||
|
||||
# Gesundheit
|
||||
health_rows = conn.execute(
|
||||
"SELECT id, typ, bezeichnung, datum, naechstes, notiz, "
|
||||
"schweregrad, reaktion, dosierung, haeufigkeit, "
|
||||
"tierarzt_name, charge_nr FROM health WHERE dog_id=?",
|
||||
(did,)
|
||||
).fetchall()
|
||||
health_out = []
|
||||
for he in health_rows:
|
||||
he_dict = dict(he)
|
||||
media = conn.execute(
|
||||
"SELECT url, media_type FROM health_media WHERE health_id=?",
|
||||
(he['id'],)
|
||||
).fetchall()
|
||||
he_dict['media'] = [dict(m) for m in media]
|
||||
health_out.append(he_dict)
|
||||
|
||||
# Trainingsfortschritt
|
||||
progress = conn.execute(
|
||||
"SELECT exercise_id, status, updated_at FROM exercise_progress "
|
||||
"WHERE dog_id=?", (did,)
|
||||
).fetchall()
|
||||
|
||||
# Ausgaben
|
||||
expenses = conn.execute(
|
||||
"SELECT datum, betrag, kategorie, notiz, is_recurring "
|
||||
"FROM expenses WHERE dog_id=?", (did,)
|
||||
).fetchall()
|
||||
|
||||
# Verhalten
|
||||
behavior = conn.execute(
|
||||
"SELECT datum, uhrzeit, kategorie, intensitaet, trigger, notiz "
|
||||
"FROM behavior_log WHERE dog_id=?", (did,)
|
||||
).fetchall()
|
||||
|
||||
# Versicherung
|
||||
insurance = conn.execute(
|
||||
"SELECT anbieter, police_nr, jahresbeitrag, kontakt, ablaufdatum, notizen "
|
||||
"FROM dog_insurance WHERE dog_id=?", (did,)
|
||||
).fetchall()
|
||||
|
||||
# Ernährungs-Profil
|
||||
ern = conn.execute(
|
||||
"SELECT futter_typ, marke, kcal_tag, portionen, notizen "
|
||||
"FROM futter_profil WHERE dog_id=?", (did,)
|
||||
).fetchone()
|
||||
|
||||
# Futter-Einträge
|
||||
futter = conn.execute(
|
||||
"SELECT datum, uhrzeit, futter_name, futter_typ, menge_g, notiz "
|
||||
"FROM futter_eintraege WHERE dog_id=?", (did,)
|
||||
).fetchall()
|
||||
|
||||
# Futter-Reaktionen
|
||||
reaktionen = conn.execute(
|
||||
"SELECT datum, uhrzeit, reaktion_typ, intensitaet, notiz "
|
||||
"FROM futter_reaktionen WHERE dog_id=?", (did,)
|
||||
).fetchall()
|
||||
|
||||
# Routen (via route_dogs)
|
||||
routes = conn.execute(
|
||||
"SELECT r.name, r.distanz_km, r.gps_track IS NOT NULL AS hat_track, "
|
||||
"date(r.created_at) AS datum "
|
||||
"FROM routes r JOIN route_dogs rd ON rd.route_id=r.id "
|
||||
"WHERE rd.dog_id=?", (did,)
|
||||
).fetchall()
|
||||
|
||||
d['tagebuch'] = diary_out
|
||||
d['gesundheit'] = [dict(h) for h in health_out]
|
||||
d['trainingsfortschritt'] = [dict(p) for p in progress]
|
||||
d['ausgaben'] = [dict(e) for e in expenses]
|
||||
d['verhaltensprotokoll'] = [dict(b) for b in behavior]
|
||||
d['versicherung'] = [dict(i) for i in insurance]
|
||||
d['ernaehrungsprofil'] = dict(ern) if ern else None
|
||||
d['futter_eintraege'] = [dict(f) for f in futter]
|
||||
d['futter_reaktionen'] = [dict(r) for r in reaktionen]
|
||||
d['routen'] = [dict(r) for r in routes]
|
||||
dogs_out.append(d)
|
||||
|
||||
# --- Forum-Beiträge ---
|
||||
forum = conn.execute(
|
||||
"SELECT ft.title, fp.content, fp.created_at, "
|
||||
"CASE WHEN fp.parent_id IS NULL THEN 'Thread' ELSE 'Antwort' END AS art "
|
||||
"FROM forum_posts fp "
|
||||
"LEFT JOIN forum_threads ft ON ft.id = fp.thread_id "
|
||||
"WHERE fp.user_id=? ORDER BY fp.created_at DESC",
|
||||
(uid,)
|
||||
).fetchall()
|
||||
|
||||
# --- Gassi-Teilnahmen ---
|
||||
walk_participations = conn.execute(
|
||||
"SELECT w.titel, w.datum, w.uhrzeit, w.ort_name "
|
||||
"FROM walk_participants wp JOIN walks w ON w.id=wp.walk_id "
|
||||
"WHERE wp.user_id=?", (uid,)
|
||||
).fetchall()
|
||||
|
||||
# --- Gassi-Fotos ---
|
||||
walk_photos = conn.execute(
|
||||
"SELECT wp.url, w.datum AS walk_datum, w.titel AS walk_titel, wp.created_at "
|
||||
"FROM walk_photos wp JOIN walks w ON w.id=wp.walk_id "
|
||||
"WHERE wp.user_id=?", (uid,)
|
||||
).fetchall()
|
||||
|
||||
# --- Push-Subscriptions (Anzahl, kein raw endpoint) ---
|
||||
push_count = conn.execute(
|
||||
"SELECT COUNT(*) FROM push_subscriptions WHERE user_id=?", (uid,)
|
||||
).fetchone()[0]
|
||||
|
||||
export = {
|
||||
"export_erstellt": _dt.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||
"hinweis": "Dieser Export enthält alle personenbezogenen Daten deines Ban-Yaro-Kontos gemäß Art. 20 DSGVO.",
|
||||
"profil": u,
|
||||
"hunde": dogs_out,
|
||||
"forum_beitraege": [dict(f) for f in forum],
|
||||
"gassi_teilnahmen": [dict(w) for w in walk_participations],
|
||||
"gassi_fotos": [dict(p) for p in walk_photos],
|
||||
"push_subscriptions": push_count,
|
||||
}
|
||||
|
||||
content = _json.dumps(export, ensure_ascii=False, indent=2, default=str)
|
||||
today = _dt.utcnow().strftime("%Y-%m-%d")
|
||||
filename = f"banyaro-export-{today}.json"
|
||||
return _Response(
|
||||
content = content,
|
||||
media_type = "application/json",
|
||||
headers = {"Content-Disposition": f'attachment; filename="{filename}"'},
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue