Expondo saldo do usuário

Na listagem de usuário está faltando exibir o saldo total do usuário, esta é uma informação sensivel e portanto estará disponível apenas em alguns casos.

  • ?show_balance=true for passado na URL das rotas GET de /user/
  • O usuário logado é superuser ou
  • O usuário logado está acessando sua própria conta

EDITE o arquivo dundie/auth.py e vamos adicionar mais uma dependencia baseada em autenticação.


async def show_balance_field(
    *,
    request: Request,
    show_balance: Optional[bool] = False,  # from /user/?show_balance=true
) -> bool:
    """Returns True if one of the conditions is met.
    1. show_balance is True AND
    2. authenticated_user.superuser OR
    3. authenticated_user.username == username
    """
    if not show_balance:
        return False

    username = request.path_params.get("username")

    try:
        authenticated_user = get_current_user(token="", request=request)
    except HTTPException:
        authenticated_user = None

    if any(
        [
            authenticated_user and authenticated_user.superuser,
            authenticated_user and authenticated_user.username == username,
        ]
    ):
        return True

    return False


ShowBalanceField = Depends(show_balance_field)

Agora precisamos de um serializer contendo o campo balance e posteriormente no endpoint usaremos este serializer como retorno apenas quando a dependência acima for satisfeita, usando uma abordagem chamada conditional response model

EDITE dundie/models/user.py

# Logo abaixo da classe UserResponse

class UserResponseWithBalance(UserResponse):
    balance: Optional[int] = None

    @root_validator(pre=True)
    def set_balance(cls, values: dict):
        """Sets the balance of the user"""
        instance = values["_sa_instance_state"].object
        values["balance"] = instance.balance
        return values

Agora EDITE o dundie/routes/user.py e vamos usar a dependencia nos endpoints list_users e get_user_by_username e além de adicionar a dependencia vamos alterar o responde_model tornando o condicional.

# IMPORTS 
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from pydantic import parse_obj_as
from dundie.auth import ShowBalanceField
from dundie.models.user import UserResponseWithBalance

# list_users 

@router.get(
    "/",
    response_model=List[UserResponse] | List[UserResponseWithBalance],
    response_model_exclude_unset=True,
)
async def list_users(
    *, session: Session = ActiveSession, show_balance_field: bool = ShowBalanceField
):
    """List all users.

    NOTES:
    - This endpoint can be accessed with a token authentication
    - show_balance query parameter takes effect only for authenticated superuser.
    """
    users = session.exec(select(User)).all()
    if show_balance_field:
        users_with_balance = parse_obj_as(List[UserResponseWithBalance], users)
        return JSONResponse(jsonable_encoder(users_with_balance))
    return users

# get user by username 
@router.get(
    "/{username}/",
    response_model=UserResponse | UserResponseWithBalance,
    response_model_exclude_unset=True,
)
async def get_user_by_username(
    *, session: Session = ActiveSession, username: str, show_balance_field: bool = ShowBalanceField
):
    """Get user by username"""
    query = select(User).where(User.username == username)
    user = session.exec(query).first()
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    if show_balance_field:
        user_with_balance = parse_obj_as(UserResponseWithBalance, user)
        return JSONResponse(jsonable_encoder(user_with_balance))
    return user

Você pode testar essa funcionalidade fazendo chamadas a URL /user/ e /user/{username} e verificar que quando o argumento ?show_balance=true for passado na URL, o serializer de retorno irá conter o campo do saldo, mas isso só será feito se o usuário for o superuser, ou o próprio usuário autenticado.

A API está pronta!

Não é bem assim... nada está pronto enquanto não tiver cobertura de testes -->