from fastapi import APIRouter, Depends, HTTPException, status, Response, Request from sqlalchemy import select from sqlalchemy.orm import Session from app.core import settings from app.core.security import ( hash_password, get_token_payload, get_current_user, get_cookie_token, verify_password, create_access_token, set_auth_cookie, ) from app.db import get_async_db from app.db.models import User from app.api.v1.schemas import UserCreate, UserRead router = APIRouter() # ------------------------ # User # ------------------------ @router.post( "/new", response_model=UserRead, status_code=status.HTTP_201_CREATED, tags=["Users"] ) async def register_user(user_in: UserCreate, response: Response, db: Session = Depends(get_async_db)): result = await db.execute(select(User).where(User.email == user_in.email)) if result.scalar_one_or_none(): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Email already registered" ) result = await db.execute(select(User).where(User.username == user_in.username)) if result.scalar_one_or_none(): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Username already registered" ) user = User( username=user_in.username, email=user_in.email, hashed_password=hash_password(user_in.password), stats = "{}" ) db.add(user) await db.commit() await db.refresh(user) access_token = create_access_token( data = {"sub": str(user.id)} ) set_auth_cookie( response, token=access_token, max_age=settings.ACCESS_TOKEN_EXPIRE_MINUTES * 60 ) return UserRead.model_validate(user) @router.get( "/me", response_model=UserRead, status_code=status.HTTP_200_OK, tags=["Users"] ) async def get_user(payload: dict = Depends(get_token_payload), db: Session = Depends(get_async_db)): user_id = payload.get('sub') if not user_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token payload" ) result = await db.execute(select(User).where(User.id == int(user_id))) user = result.scalar_one_or_none() if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found", ) return UserRead.model_validate(user) @router.put( "/me", response_model=UserRead, status_code=status.HTTP_200_OK, tags=["Users"] ) async def update_user(user_in: UserCreate, payload: dict = Depends(get_token_payload), db: Session = Depends(get_async_db)): user_id = payload.get('sub') if not user_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token payload" ) result = await db.execute(select(User).where(User.id == int(user_id))) user = result.scalar_one_or_none() if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found", ) if user_in.email != user.email: result = await db.execute(select(User).where( (User.email == user_in.email) )) if result.scalar_one_or_none(): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Email or username already registered" ) if user_in.username != user.username: result = await db.execute(select(User).where( (User.username == user_in.username) )) if result.scalar_one_or_none(): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Email or username already registered" ) user.username = user_in.username user.email = user_in.email user.hashed_password = hash_password(user_in.password) await db.commit() await db.refresh(user) return UserRead.model_validate(user) @router.delete( "/me", status_code=status.HTTP_200_OK, tags=['Users'] ) async def delete_user(payload: dict = Depends(get_token_payload), db: Session = Depends(get_async_db)): user_id = payload.get('sub') if not user_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token payload" ) result = await db.execute(select(User).where(User.id == int(user_id))) user = result.scalar_one_or_none() if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found", ) await db.delete(user) await db.commit() response = Response() response.delete_cookie( key="access_token", path="/", # mesmo chemin que lors du set_cookie httponly=True, samesite="lax" if settings.ENV == "dev" else "strict", secure=settings.USE_SSL, # mettre True en prod sur HTTPS ) return response