import secrets from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.core.database import get_db from app.core.security import hash_password, verify_password, create_access_token, get_current_user from app.models.user import User from pydantic import BaseModel, EmailStr from app.schemas.auth import RegisterRequest, LoginRequest, TokenResponse, UserResponse from app.services.spotify import get_spotify_auth_url, exchange_spotify_code, get_spotify_user router = APIRouter(prefix="/auth", tags=["auth"]) @router.post("/register", response_model=TokenResponse) async def register(data: RegisterRequest, db: AsyncSession = Depends(get_db)): result = await db.execute(select(User).where(User.email == data.email)) if result.scalar_one_or_none(): raise HTTPException(status_code=400, detail="Email already registered") user = User( email=data.email, name=data.name, hashed_password=hash_password(data.password), ) db.add(user) await db.flush() return TokenResponse(access_token=create_access_token(user.id)) class UpdateProfileRequest(BaseModel): name: str | None = None email: EmailStr | None = None class ChangePasswordRequest(BaseModel): current_password: str new_password: str @router.put("/me") async def update_profile(data: UpdateProfileRequest, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)): if data.name: user.name = data.name if data.email and data.email != user.email: existing = await db.execute(select(User).where(User.email == data.email)) if existing.scalar_one_or_none(): raise HTTPException(status_code=400, detail="Email already in use") user.email = data.email return UserResponse(id=user.id, email=user.email, name=user.name, is_pro=user.is_pro, spotify_connected=user.spotify_id is not None) @router.post("/change-password") async def change_password(data: ChangePasswordRequest, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)): if not user.hashed_password or not verify_password(data.current_password, user.hashed_password): raise HTTPException(status_code=400, detail="Current password is incorrect") user.hashed_password = hash_password(data.new_password) return {"ok": True} @router.delete("/me") async def delete_account(user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)): await db.delete(user) return {"ok": True} @router.post("/login", response_model=TokenResponse) async def login(data: LoginRequest, db: AsyncSession = Depends(get_db)): result = await db.execute(select(User).where(User.email == data.email)) user = result.scalar_one_or_none() if not user or not user.hashed_password or not verify_password(data.password, user.hashed_password): raise HTTPException(status_code=401, detail="Invalid email or password") return TokenResponse(access_token=create_access_token(user.id)) @router.get("/me", response_model=UserResponse) async def get_me(user: User = Depends(get_current_user)): return UserResponse( id=user.id, email=user.email, name=user.name, is_pro=user.is_pro, spotify_connected=user.spotify_id is not None, ) @router.get("/spotify/url") async def spotify_auth_url(): state = secrets.token_urlsafe(32) url = get_spotify_auth_url(state) return {"url": url, "state": state} @router.post("/spotify/callback", response_model=TokenResponse) async def spotify_callback(code: str, db: AsyncSession = Depends(get_db)): token_data = await exchange_spotify_code(code) access_token = token_data["access_token"] refresh_token = token_data.get("refresh_token") spotify_user = await get_spotify_user(access_token) spotify_id = spotify_user["id"] email = spotify_user.get("email", f"{spotify_id}@spotify.user") name = spotify_user.get("display_name") or spotify_id # Check if user exists by spotify_id or email result = await db.execute(select(User).where(User.spotify_id == spotify_id)) user = result.scalar_one_or_none() if not user: result = await db.execute(select(User).where(User.email == email)) user = result.scalar_one_or_none() if user: user.spotify_id = spotify_id user.spotify_access_token = access_token user.spotify_refresh_token = refresh_token or user.spotify_refresh_token else: user = User( email=email, name=name, spotify_id=spotify_id, spotify_access_token=access_token, spotify_refresh_token=refresh_token, ) db.add(user) await db.flush() return TokenResponse(access_token=create_access_token(user.id))