diff --git a/workout_api/atleta/controller.py b/workout_api/atleta/controller.py index b3a28d4c..e4ecdd5a 100644 --- a/workout_api/atleta/controller.py +++ b/workout_api/atleta/controller.py @@ -1,18 +1,27 @@ -from datetime import datetime +from datetime import datetime from uuid import uuid4 -from fastapi import APIRouter, Body, HTTPException, status +from fastapi import APIRouter, Body, HTTPException, Query, status from pydantic import UUID4 +from sqlalchemy.future import select +from sqlalchemy.exc import IntegrityError -from workout_api.atleta.schemas import AtletaIn, AtletaOut, AtletaUpdate +from workout_api.atleta.schemas import ( + AtletaIn, + AtletaOut, + AtletaUpdate, + AtletaListOut +) from workout_api.atleta.models import AtletaModel from workout_api.categorias.models import CategoriaModel from workout_api.centro_treinamento.models import CentroTreinamentoModel from workout_api.contrib.dependencies import DatabaseDependency -from sqlalchemy.future import select + +from fastapi_pagination import Page, paginate, add_pagination router = APIRouter() + @router.post( '/', summary='Criar um novo atleta', @@ -45,6 +54,7 @@ async def post( status_code=status.HTTP_400_BAD_REQUEST, detail=f'O centro de treinamento {centro_treinamento_nome} não foi encontrado.' ) + try: atleta_out = AtletaOut(id=uuid4(), created_at=datetime.utcnow(), **atleta_in.model_dump()) atleta_model = AtletaModel(**atleta_out.model_dump(exclude={'categoria', 'centro_treinamento'})) @@ -54,7 +64,14 @@ async def post( db_session.add(atleta_model) await db_session.commit() + except IntegrityError: + await db_session.rollback() + raise HTTPException( + status_code=status.HTTP_303_SEE_OTHER, + detail=f"Já existe um atleta cadastrado com o cpf: {atleta_in.cpf}" + ) except Exception: + await db_session.rollback() raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail='Ocorreu um erro ao inserir os dados no banco' @@ -64,15 +81,25 @@ async def post( @router.get( - '/', - summary='Consultar todos os Atletas', + '/', + summary='Consultar todos os Atletas com filtros e paginação', status_code=status.HTTP_200_OK, - response_model=list[AtletaOut], + response_model=Page[AtletaListOut], ) -async def query(db_session: DatabaseDependency) -> list[AtletaOut]: - atletas: list[AtletaOut] = (await db_session.execute(select(AtletaModel))).scalars().all() - - return [AtletaOut.model_validate(atleta) for atleta in atletas] +async def query( + db_session: DatabaseDependency, + nome: str | None = Query(default=None, description="Filtrar por nome do atleta"), + cpf: str | None = Query(default=None, description="Filtrar por CPF do atleta"), +): + stmt = select(AtletaModel) + + if nome: + stmt = stmt.filter(AtletaModel.nome.ilike(f"%{nome}%")) + if cpf: + stmt = stmt.filter(AtletaModel.cpf == cpf) + + result = (await db_session.execute(stmt)).scalars().all() + return paginate(result) @router.get( @@ -82,7 +109,7 @@ async def query(db_session: DatabaseDependency) -> list[AtletaOut]: response_model=AtletaOut, ) async def get(id: UUID4, db_session: DatabaseDependency) -> AtletaOut: - atleta: AtletaOut = ( + atleta = ( await db_session.execute(select(AtletaModel).filter_by(id=id)) ).scalars().first() @@ -102,7 +129,7 @@ async def get(id: UUID4, db_session: DatabaseDependency) -> AtletaOut: response_model=AtletaOut, ) async def patch(id: UUID4, db_session: DatabaseDependency, atleta_up: AtletaUpdate = Body(...)) -> AtletaOut: - atleta: AtletaOut = ( + atleta = ( await db_session.execute(select(AtletaModel).filter_by(id=id)) ).scalars().first() @@ -128,7 +155,7 @@ async def patch(id: UUID4, db_session: DatabaseDependency, atleta_up: AtletaUpda status_code=status.HTTP_204_NO_CONTENT ) async def delete(id: UUID4, db_session: DatabaseDependency) -> None: - atleta: AtletaOut = ( + atleta = ( await db_session.execute(select(AtletaModel).filter_by(id=id)) ).scalars().first() @@ -140,3 +167,6 @@ async def delete(id: UUID4, db_session: DatabaseDependency) -> None: await db_session.delete(atleta) await db_session.commit() + + +add_pagination(router) diff --git a/workout_api/atleta/schemas.py b/workout_api/atleta/schemas.py index f3e33d36..a76305b3 100644 --- a/workout_api/atleta/schemas.py +++ b/workout_api/atleta/schemas.py @@ -15,7 +15,14 @@ class Atleta(BaseSchema): sexo: Annotated[str, Field(description='Sexo do atleta', example='M', max_length=1)] categoria: Annotated[CategoriaIn, Field(description='Categoria do atleta')] centro_treinamento: Annotated[CentroTreinamentoAtleta, Field(description='Centro de treinamento do atleta')] + +class AtletaListOut(BaseSchema): + nome: Annotated[str, Field(description="Nome do atleta", example="Joao")] + centro_treinamento: CentroTreinamentoAtleta + categoria: CategoriaIn + class Config: + orm_mode = True class AtletaIn(Atleta): pass @@ -26,4 +33,4 @@ class AtletaOut(Atleta, OutMixin): class AtletaUpdate(BaseSchema): nome: Annotated[Optional[str], Field(None, description='Nome do atleta', example='Joao', max_length=50)] - idade: Annotated[Optional[int], Field(None, description='Idade do atleta', example=25)] \ No newline at end of file + idade: Annotated[Optional[int], Field(None, description='Idade do atleta', example=25)] diff --git a/workout_api/main.py b/workout_api/main.py index 07532ee5..07992f7b 100644 --- a/workout_api/main.py +++ b/workout_api/main.py @@ -1,5 +1,8 @@ from fastapi import FastAPI from workout_api.routers import api_router +from fastapi_pagination import add_pagination # ⬅️ Import necessário app = FastAPI(title='WorkoutApi') app.include_router(api_router) + +add_pagination(app) # ⬅️ Ativa a paginação global