107 lines
3.6 KiB
Python
107 lines
3.6 KiB
Python
from typing import List, Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
|
|
from app.api.deps import get_current_user, get_user_repository, require_superuser
|
|
from app.core.database.user_repository import (
|
|
EmailTakenError,
|
|
UserInUseError,
|
|
UserRepository,
|
|
)
|
|
from app.core.dto.user import TrainerDTO, UserDTO, UserRole
|
|
from app.core.security import hash_password
|
|
from app.schemas.users import UserCreate, UserUpdate
|
|
|
|
router = APIRouter()
|
|
|
|
EMAIL_TAKEN = HTTPException(status_code=409, detail="Email already registered")
|
|
|
|
|
|
@router.post("/", response_model=UserDTO, status_code=status.HTTP_201_CREATED, dependencies=[Depends(require_superuser)])
|
|
async def create_user(
|
|
payload: UserCreate,
|
|
repository: UserRepository = Depends(get_user_repository),
|
|
):
|
|
try:
|
|
return await repository.create(
|
|
email=payload.email,
|
|
password_hash=hash_password(payload.password),
|
|
first_name=payload.first_name,
|
|
last_name=payload.last_name,
|
|
role=payload.role,
|
|
)
|
|
except EmailTakenError:
|
|
raise EMAIL_TAKEN
|
|
|
|
|
|
@router.get("/", response_model=List[UserDTO], dependencies=[Depends(require_superuser)])
|
|
async def list_users(
|
|
role: Optional[UserRole] = None,
|
|
repository: UserRepository = Depends(get_user_repository),
|
|
):
|
|
return await repository.list(role)
|
|
|
|
|
|
@router.get("/trainers/", response_model=List[TrainerDTO])
|
|
async def list_trainers(
|
|
_: UserDTO = Depends(get_current_user),
|
|
repository: UserRepository = Depends(get_user_repository),
|
|
):
|
|
"""Trainers to pick from in a training form: any signed-in user may ask for them."""
|
|
return [
|
|
TrainerDTO(
|
|
id=user.id,
|
|
name=f"{user.first_name} {user.last_name}".strip() or user.email,
|
|
)
|
|
for user in await repository.list(UserRole.trainer)
|
|
if user.is_active
|
|
]
|
|
|
|
|
|
@router.get("/{user_id}/", response_model=UserDTO, dependencies=[Depends(require_superuser)])
|
|
async def get_user(
|
|
user_id: int,
|
|
repository: UserRepository = Depends(get_user_repository),
|
|
):
|
|
user = await repository.get(user_id)
|
|
if user is None:
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
return user
|
|
|
|
|
|
@router.patch("/{user_id}/", response_model=UserDTO, dependencies=[Depends(require_superuser)])
|
|
async def update_user(
|
|
user_id: int,
|
|
payload: UserUpdate,
|
|
repository: UserRepository = Depends(get_user_repository),
|
|
):
|
|
values = payload.model_dump(exclude_unset=True)
|
|
if "password" in values:
|
|
values["password_hash"] = hash_password(values.pop("password"))
|
|
# null makes no sense for these columns
|
|
if any(values.get(name, "") is None for name in ("email", "role", "is_active", "password_hash")):
|
|
raise HTTPException(status_code=422, detail="Field must not be null")
|
|
try:
|
|
user = await repository.update(user_id, values)
|
|
except EmailTakenError:
|
|
raise EMAIL_TAKEN
|
|
if user is None:
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
return user
|
|
|
|
|
|
@router.delete("/{user_id}/", dependencies=[Depends(require_superuser)])
|
|
async def delete_user(
|
|
user_id: int,
|
|
current: UserDTO = Depends(require_superuser),
|
|
repository: UserRepository = Depends(get_user_repository),
|
|
):
|
|
if user_id == current.id:
|
|
raise HTTPException(status_code=400, detail="You can't delete yourself")
|
|
try:
|
|
deleted = await repository.delete(user_id)
|
|
except UserInUseError:
|
|
raise HTTPException(status_code=409, detail="User has trainings")
|
|
if not deleted:
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
return {"status": "ok"}
|