from __future__ import annotations import logging from typing import Annotated from fastapi import APIRouter, Depends, HTTPException, status from fastapi.responses import JSONResponse from sqlalchemy import select, func from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.models.user import User from app.models.profile import UserProfile from app.models.points import PointRule, DailyTarget, DEFAULT_POINT_RULES, TTM_DAILY_TARGETS from app.schemas.auth import LoginRequest, RegisterRequest, TokenResponse, UserResponse from app.utils.auth import hash_password, verify_password, create_access_token, get_current_user logger = logging.getLogger("fitness-rewards") router = APIRouter(prefix="/api/auth", tags=["auth"]) @router.post("/register") async def register(req: RegisterRequest, db: Annotated[AsyncSession, Depends(get_db)]): try: existing = await db.execute(select(User).where(User.username == req.username)) if existing.scalar_one_or_none(): raise HTTPException(status_code=400, detail="Username already taken") # Grant admin to first user admin_count = await db.execute(select(func.count(User.id)).where(User.is_admin == True)) is_first_user = (admin_count.scalar() or 0) == 0 user = User( username=req.username, display_name=req.display_name, password_hash=hash_password(req.password), email=req.email, is_admin=is_first_user, ) db.add(user) await db.flush() # Create empty profile profile = UserProfile(user_id=user.id) db.add(profile) # Create default point rules for rule_data in DEFAULT_POINT_RULES: db.add(PointRule(user_id=user.id, **rule_data)) # Create default daily targets db.add(DailyTarget(user_id=user.id)) await db.flush() token = create_access_token(str(user.id)) return {"access_token": token, "token_type": "bearer"} except HTTPException: raise except Exception as e: logger.error(f"Registration failed: {e}", exc_info=True) return JSONResponse(status_code=500, content={"detail": "Internal server error"}) @router.post("/login") async def login(req: LoginRequest, db: Annotated[AsyncSession, Depends(get_db)]): try: result = await db.execute(select(User).where(User.username == req.username)) user = result.scalar_one_or_none() if not user or not verify_password(req.password, user.password_hash): raise HTTPException(status_code=401, detail="Invalid username or password") token = create_access_token(str(user.id)) return {"access_token": token, "token_type": "bearer"} except HTTPException: raise except Exception as e: logger.error(f"Login failed: {e}", exc_info=True) return JSONResponse(status_code=500, content={"detail": "Internal server error"}) @router.get("/me") async def get_me(user: Annotated[User, Depends(get_current_user)]): return { "id": str(user.id), "username": user.username, "display_name": user.display_name, "email": user.email, "timezone": user.timezone, "is_admin": getattr(user, "is_admin", False), }