feat: add auth system, boss pokemon details, moves/abilities API, and run ownership
Add user authentication with login/signup/protected routes, boss pokemon detail fields and result team tracking, moves and abilities selector components and API, run ownership and visibility controls, and various UI improvements across encounters, run list, and journal pages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
106
backend/src/app/api/users.py
Normal file
106
backend/src/app/api/users.py
Normal file
@@ -0,0 +1,106 @@
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.auth import AuthUser, require_auth
|
||||
from app.core.database import get_session
|
||||
from app.models.user import User
|
||||
from app.schemas.base import CamelModel
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class UserResponse(CamelModel):
|
||||
id: UUID
|
||||
email: str
|
||||
display_name: str | None = None
|
||||
|
||||
|
||||
@router.post("/me", response_model=UserResponse)
|
||||
async def sync_current_user(
|
||||
session: AsyncSession = Depends(get_session),
|
||||
auth_user: AuthUser = Depends(require_auth),
|
||||
):
|
||||
"""
|
||||
Sync the current authenticated user from Supabase to local DB.
|
||||
Creates user on first login, updates email if changed.
|
||||
"""
|
||||
user_id = UUID(auth_user.id)
|
||||
|
||||
result = await session.execute(select(User).where(User.id == user_id))
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
if user is None:
|
||||
# First login - create user record
|
||||
user = User(
|
||||
id=user_id,
|
||||
email=auth_user.email or "",
|
||||
display_name=None,
|
||||
)
|
||||
session.add(user)
|
||||
elif auth_user.email and user.email != auth_user.email:
|
||||
# Email changed in Supabase - update local record
|
||||
user.email = auth_user.email
|
||||
|
||||
await session.commit()
|
||||
await session.refresh(user)
|
||||
return user
|
||||
|
||||
|
||||
@router.get("/me", response_model=UserResponse)
|
||||
async def get_current_user(
|
||||
session: AsyncSession = Depends(get_session),
|
||||
auth_user: AuthUser = Depends(require_auth),
|
||||
):
|
||||
"""Get the current authenticated user's profile."""
|
||||
user_id = UUID(auth_user.id)
|
||||
|
||||
result = await session.execute(select(User).where(User.id == user_id))
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
if user is None:
|
||||
# Auto-create if not exists (shouldn't happen if /me POST is called on login)
|
||||
user = User(
|
||||
id=user_id,
|
||||
email=auth_user.email or "",
|
||||
display_name=None,
|
||||
)
|
||||
session.add(user)
|
||||
await session.commit()
|
||||
await session.refresh(user)
|
||||
|
||||
return user
|
||||
|
||||
|
||||
class UserUpdateRequest(CamelModel):
|
||||
display_name: str | None = None
|
||||
|
||||
|
||||
@router.patch("/me", response_model=UserResponse)
|
||||
async def update_current_user(
|
||||
data: UserUpdateRequest,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
auth_user: AuthUser = Depends(require_auth),
|
||||
):
|
||||
"""Update the current user's profile (display name)."""
|
||||
user_id = UUID(auth_user.id)
|
||||
|
||||
result = await session.execute(select(User).where(User.id == user_id))
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
if user is None:
|
||||
user = User(
|
||||
id=user_id,
|
||||
email=auth_user.email or "",
|
||||
display_name=data.display_name,
|
||||
)
|
||||
session.add(user)
|
||||
else:
|
||||
if data.display_name is not None:
|
||||
user.display_name = data.display_name
|
||||
|
||||
await session.commit()
|
||||
await session.refresh(user)
|
||||
return user
|
||||
Reference in New Issue
Block a user