"""Authentication endpoints for TapTrack Hub.""" from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException, status, Request from pydantic import BaseModel, EmailStr from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from app.auth.password import verify_password, hash_password from app.auth.jwt import create_access_token from app.auth.dependencies import get_current_user from app.database import get_db from app.models.user import HubUser, UserRole router = APIRouter(prefix="/api/auth", tags=["auth"]) class LoginRequest(BaseModel): email: EmailStr password: str class TokenResponse(BaseModel): access_token: str token_type: str = "bearer" role: str user_id: str full_name: str school_id: str | None class ChangePasswordRequest(BaseModel): current_password: str new_password: str @router.post("/login", response_model=TokenResponse) async def login(body: LoginRequest, request: Request, db: AsyncSession = Depends(get_db)): result = await db.execute(select(HubUser).where(HubUser.email == body.email)) user = result.scalar_one_or_none() if not user or not verify_password(body.password, user.hashed_password): raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials") if not user.is_active: raise HTTPException(status_code=401, detail="Account is inactive") user.last_login_at = datetime.now(timezone.utc) await db.commit() token = create_access_token({"sub": user.id, "role": user.role.value}) return TokenResponse( access_token=token, role=user.role.value, user_id=user.id, full_name=user.full_name, school_id=user.school_id, ) @router.get("/me") async def get_me(current_user: HubUser = Depends(get_current_user)): return { "id": current_user.id, "email": current_user.email, "full_name": current_user.full_name, "role": current_user.role.value, "school_id": current_user.school_id, "is_active": current_user.is_active, } @router.put("/me/password", status_code=204) async def change_password( body: ChangePasswordRequest, current_user: HubUser = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): if len(body.new_password) < 8: raise HTTPException(status_code=422, detail="Password must be at least 8 characters") if not verify_password(body.current_password, current_user.hashed_password): raise HTTPException(status_code=400, detail="Current password is incorrect") current_user.hashed_password = hash_password(body.new_password) await db.commit()