Coverage for app / api / v1 / users.py: 100%

38 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-05 17:54 +0000

1import logging 

2from fastapi import APIRouter, Depends, HTTPException 

3from sqlalchemy import select 

4from sqlalchemy.ext.asyncio import AsyncSession 

5 

6from app.api.deps import get_db, get_current_user 

7from app.models.user import User 

8from app.schemas.user import UserRead 

9from app.core.security import get_supabase_admin 

10 

11router = APIRouter() 

12logger = logging.getLogger(__name__) 

13 

14async def _get_or_create_user( 

15 current_user: dict, 

16 db: AsyncSession, 

17) -> User: 

18 """Ensure the Supabase-authenticated user exists in our database.""" 

19 result = await db.execute( 

20 select(User).where(User.supabase_uid == current_user["supabase_uid"]) 

21 ) 

22 user = result.scalar_one_or_none() 

23 

24 if not user: 

25 user = User( 

26 id=current_user["id"], 

27 email=current_user["email"], 

28 supabase_uid=current_user["supabase_uid"], 

29 ) 

30 db.add(user) 

31 await db.commit() 

32 await db.refresh(user) 

33 

34 return user 

35 

36@router.get("/me", response_model=UserRead) 

37async def get_current_user_profile( 

38 current_user: dict = Depends(get_current_user), 

39 db: AsyncSession = Depends(get_db), 

40): 

41 """Get current user's profile (auto-creates if missing).""" 

42 user = await _get_or_create_user(current_user, db) 

43 return user 

44 

45@router.post("/sync", response_model=UserRead, status_code=200) 

46async def sync_user( 

47 current_user: dict = Depends(get_current_user), 

48 db: AsyncSession = Depends(get_db), 

49): 

50 """Explicitly sync Supabase user to local DB.""" 

51 user = await _get_or_create_user(current_user, db) 

52 return user 

53 

54@router.delete("/me", status_code=204) 

55async def delete_current_user( 

56 current_user: dict = Depends(get_current_user), 

57 db: AsyncSession = Depends(get_db), 

58): 

59 """Delete user from local database and Supabase Auth.""" 

60 user = await _get_or_create_user(current_user, db) 

61 

62 # 1. Delete local user first (SQLAlchemy cascades delete to monitors & ping logs) 

63 await db.delete(user) 

64 await db.commit() 

65 

66 # 2. Best-effort Supabase Auth cleanup 

67 try: 

68 supabase = get_supabase_admin() 

69 supabase.auth.admin.delete_user(current_user["supabase_uid"]) 

70 except Exception as e: 

71 logger.error(f"Failed to delete Supabase auth user: {e}", exc_info=True) 

72 

73 return None