|
| 1 | +import json |
1 | 2 | import logging |
| 3 | +from datetime import datetime, timezone |
2 | 4 | from typing import Annotated |
3 | 5 |
|
4 | 6 | from fastapi import APIRouter, Depends, HTTPException, status |
| 7 | +from fastapi.responses import Response |
5 | 8 |
|
6 | 9 | from database import get_supabase |
7 | 10 | from middleware.auth import get_current_user |
@@ -83,6 +86,59 @@ async def update_settings( |
83 | 86 | ) |
84 | 87 |
|
85 | 88 |
|
| 89 | +@router.get("/export-data") |
| 90 | +async def export_user_data(current_user: Annotated[dict, Depends(get_current_user)]): |
| 91 | + """GDPR: Export all user data as a JSON file.""" |
| 92 | + try: |
| 93 | + supabase = get_supabase() |
| 94 | + uid = _user_id(current_user) |
| 95 | + |
| 96 | + profile = supabase.table("user_profiles").select("*").eq("id", uid).single().execute() |
| 97 | + emails = supabase.table("emails").select( |
| 98 | + "id, subject, sender, body, category, priority, ai_summary, received_at, is_read, created_at" |
| 99 | + ).eq("user_id", uid).order("received_at", desc=True).limit(1000).execute() |
| 100 | + actions = supabase.table("actions").select("*").eq("user_id", uid).limit(500).execute() |
| 101 | + replies = supabase.table("reply_drafts").select( |
| 102 | + "id, email_id, draft_text, is_sent, created_at" |
| 103 | + ).eq("user_id", uid).limit(500).execute() |
| 104 | + |
| 105 | + export = { |
| 106 | + "exported_at": datetime.now(timezone.utc).isoformat(), |
| 107 | + "user_id": uid, |
| 108 | + "profile": profile.data or {}, |
| 109 | + "emails": emails.data or [], |
| 110 | + "actions": actions.data or [], |
| 111 | + "reply_drafts": replies.data or [], |
| 112 | + } |
| 113 | + |
| 114 | + filename = f"inboxiq-data-{datetime.now(timezone.utc).strftime('%Y%m%d')}.json" |
| 115 | + return Response( |
| 116 | + content=json.dumps(export, indent=2, default=str), |
| 117 | + media_type="application/json", |
| 118 | + headers={"Content-Disposition": f'attachment; filename="{filename}"'}, |
| 119 | + ) |
| 120 | + except Exception as exc: |
| 121 | + logger.error("export_user_data error: %s", exc) |
| 122 | + raise HTTPException(status_code=500, detail="Failed to export data.") |
| 123 | + |
| 124 | + |
| 125 | +@router.delete("/delete-account", status_code=status.HTTP_200_OK) |
| 126 | +async def delete_account(current_user: Annotated[dict, Depends(get_current_user)]): |
| 127 | + """GDPR: Delete all user data. Irreversible.""" |
| 128 | + try: |
| 129 | + supabase = get_supabase() |
| 130 | + uid = _user_id(current_user) |
| 131 | + # Delete in dependency order |
| 132 | + supabase.table("reply_drafts").delete().eq("user_id", uid).execute() |
| 133 | + supabase.table("actions").delete().eq("user_id", uid).execute() |
| 134 | + supabase.table("emails").delete().eq("user_id", uid).execute() |
| 135 | + supabase.table("user_profiles").delete().eq("id", uid).execute() |
| 136 | + return {"message": "Account and all data deleted successfully."} |
| 137 | + except Exception as exc: |
| 138 | + logger.error("delete_account error: %s", exc) |
| 139 | + raise HTTPException(status_code=500, detail="Failed to delete account.") |
| 140 | + |
| 141 | + |
86 | 142 | @router.get("/profile", response_model=dict) |
87 | 143 | async def get_profile(current_user: Annotated[dict, Depends(get_current_user)]): |
88 | 144 | """ |
|
0 commit comments