From f061657ad3cbe20413e424ddd7f62a2b490bdb65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vitor=20Hugo=20Belorio=20Sim=C3=A3o?= Date: Mon, 2 Mar 2026 17:58:08 -0300 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(api-routes):=20re?= =?UTF-8?q?organizar=20rotas=20em=20pacote=20app/api/routes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/routes.py | 161 -------------------- app/api/routes/__init__.py | 11 ++ app/api/routes/chat.py | 31 ++++ app/api/routes/dependencies.py | 33 ++++ app/api/routes/mock.py | 111 ++++++++++++++ app/api/{tool_routes.py => routes/tools.py} | 23 +-- app/main.py | 3 +- 7 files changed, 194 insertions(+), 179 deletions(-) delete mode 100644 app/api/routes.py create mode 100644 app/api/routes/__init__.py create mode 100644 app/api/routes/chat.py create mode 100644 app/api/routes/dependencies.py create mode 100644 app/api/routes/mock.py rename app/api/{tool_routes.py => routes/tools.py} (69%) diff --git a/app/api/routes.py b/app/api/routes.py deleted file mode 100644 index b5b66d4..0000000 --- a/app/api/routes.py +++ /dev/null @@ -1,161 +0,0 @@ -from typing import List, Dict, Any - -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy.exc import SQLAlchemyError -from sqlalchemy.orm import Session - -from app.api.schemas import ( - ChatRequest, - ChatResponse, - ConsultarEstoqueRequest, - ValidarClienteVendaRequest, - AvaliarVeiculoTrocaRequest, - AgendarRevisaoRequest, - CancelarPedidoRequest, -) -from app.db.database import SessionLocal -from app.services.orquestrador_service import OrquestradorService -from app.services.handlers import ( - consultar_estoque, - validar_cliente_venda, - avaliar_veiculo_troca, - agendar_revisao, - cancelar_pedido, -) - -router = APIRouter() - - -def get_db(): - """Fornece uma sessao de banco para a request e garante o fechamento.""" - db = SessionLocal() - try: - yield db - finally: - db.close() - - -def _db_error_detail(exc: SQLAlchemyError) -> str: - """Converte erros de banco em mensagens amigaveis para a API.""" - text = str(exc).lower() - - # Heuristica para identificar falhas no MySQL de tools. - tools_markers = ("tools", "tool") - if any(marker in text for marker in tools_markers): - return "Servico temporariamente indisponivel: banco MySQL (tools) inacessivel." - - # Heuristica para identificar falhas no MySQL (base ficticia). - mysql_mock_markers = ("mock", "vehicles", "customers", "orders", "review_schedules") - if any(marker in text for marker in mysql_mock_markers): - return "Servico temporariamente indisponivel: banco MySQL (dados ficticios) inacessivel." - - mysql_generic_markers = ("mysql", "pymysql", "(2003", "3306") - if any(marker in text for marker in mysql_generic_markers): - return "Servico temporariamente indisponivel: banco MySQL inacessivel." - - return "Servico temporariamente indisponivel: erro de acesso ao banco de dados." - -''' -# Removido momentaniamente para teste do Vertex IA -@router.post("/chat", response_model=ChatResponse) -async def chat(request: ChatRequest, db: Session = Depends(get_db)): - service = OrquestradorService(db) - result = await service.handle_message( - message=request.message, - user_id=request.user_id, - ) - return ChatResponse(response=result) -''' -@router.post("/chat", response_model=ChatResponse) -async def chat(request: ChatRequest, db: Session = Depends(get_db)): - """Processa mensagem do usuario via orquestrador e retorna resposta do chat.""" - try: - service = OrquestradorService(db) - result = await service.handle_message( - message=request.message, - user_id=request.user_id, - ) - return ChatResponse(response=result) - except SQLAlchemyError as exc: - raise HTTPException( - status_code=503, - detail=_db_error_detail(exc), - ) - except ValueError as exc: - # Erros de configuracao de Vertex (regiao/projeto/modelo) - raise HTTPException(status_code=500, detail=f"Configuracao invalida do Vertex AI: {exc}") - except RuntimeError as exc: - raise HTTPException(status_code=503, detail=f"Falha temporaria no LLM/Vertex AI: {exc}") - - -@router.post("/mock/consultar-estoque") -async def consultar_estoque_endpoint( - body: ConsultarEstoqueRequest, -) -> List[Dict[str, Any]]: - """Consulta estoque de veiculos mock com filtros opcionais.""" - try: - return await consultar_estoque( - preco_max=body.preco_max, - categoria=body.categoria, - ordenar_preco=body.ordenar_preco, - limite=body.limite, - ) - except SQLAlchemyError as exc: - raise HTTPException(status_code=503, detail=_db_error_detail(exc)) - - -@router.post("/mock/validar-cliente-venda") -async def validar_cliente_venda_endpoint( - body: ValidarClienteVendaRequest, -) -> Dict[str, Any]: - """Valida elegibilidade de cliente para uma venda.""" - try: - return await validar_cliente_venda( - cpf=body.cpf, - valor_veiculo=body.valor_veiculo, - ) - except SQLAlchemyError as exc: - raise HTTPException(status_code=503, detail=_db_error_detail(exc)) - - -@router.post("/mock/avaliar-veiculo-troca") -async def avaliar_veiculo_troca_endpoint( - body: AvaliarVeiculoTrocaRequest, -) -> Dict[str, Any]: - """Avalia valor de troca de um veiculo com base em modelo, ano e quilometragem.""" - return await avaliar_veiculo_troca( - modelo=body.modelo, - ano=body.ano, - km=body.km, - ) - - -@router.post("/mock/agendar-revisao") -async def agendar_revisao_endpoint( - body: AgendarRevisaoRequest, -) -> Dict[str, Any]: - """Agenda revisao para uma placa em data/hora informada.""" - try: - return await agendar_revisao( - placa=body.placa, - data_hora=body.data_hora, - user_id=body.user_id, - ) - except SQLAlchemyError as exc: - raise HTTPException(status_code=503, detail=_db_error_detail(exc)) - - -@router.post("/mock/cancelar-pedido") -async def cancelar_pedido_endpoint( - body: CancelarPedidoRequest, - -) -> Dict[str, Any]: - """Cancela pedido de venda existente registrando o motivo.""" - try: - return await cancelar_pedido( - numero_pedido=body.numero_pedido, - motivo=body.motivo, - user_id=body.user_id, - ) - except SQLAlchemyError as exc: - raise HTTPException(status_code=503, detail=_db_error_detail(exc)) diff --git a/app/api/routes/__init__.py b/app/api/routes/__init__.py new file mode 100644 index 0000000..7e05fc6 --- /dev/null +++ b/app/api/routes/__init__.py @@ -0,0 +1,11 @@ +from fastapi import APIRouter + +from app.api.routes.chat import router as chat_router +from app.api.routes.mock import router as mock_router +from app.api.routes.tools import router as tool_router + +router = APIRouter() +router.include_router(chat_router) +router.include_router(mock_router) + +__all__ = ["router", "tool_router"] diff --git a/app/api/routes/chat.py b/app/api/routes/chat.py new file mode 100644 index 0000000..bbb23db --- /dev/null +++ b/app/api/routes/chat.py @@ -0,0 +1,31 @@ +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.orm import Session + +from app.api.routes.dependencies import db_error_detail, get_db +from app.api.schemas import ChatRequest, ChatResponse +from app.services.orquestrador_service import OrquestradorService + +router = APIRouter(tags=["Chat"]) + + +@router.post("/chat", response_model=ChatResponse) +async def chat(request: ChatRequest, db: Session = Depends(get_db)): + """Processa mensagem do usuario via orquestrador e retorna resposta do chat.""" + try: + service = OrquestradorService(db) + result = await service.handle_message( + message=request.message, + user_id=request.user_id, + ) + return ChatResponse(response=result) + except SQLAlchemyError as exc: + raise HTTPException( + status_code=503, + detail=db_error_detail(exc), + ) + except ValueError as exc: + # Erros de configuracao de Vertex (regiao/projeto/modelo) + raise HTTPException(status_code=500, detail=f"Configuracao invalida do Vertex AI: {exc}") + except RuntimeError as exc: + raise HTTPException(status_code=503, detail=f"Falha temporaria no LLM/Vertex AI: {exc}") diff --git a/app/api/routes/dependencies.py b/app/api/routes/dependencies.py new file mode 100644 index 0000000..f63e24c --- /dev/null +++ b/app/api/routes/dependencies.py @@ -0,0 +1,33 @@ +from sqlalchemy.exc import SQLAlchemyError + +from app.db.database import SessionLocal + + +def get_db(): + """Fornece uma sessao de banco para a request e garante o fechamento.""" + db = SessionLocal() + try: + yield db + finally: + db.close() + + +def db_error_detail(exc: SQLAlchemyError) -> str: + """Converte erros de banco em mensagens amigaveis para a API.""" + text = str(exc).lower() + + # Heuristica para identificar falhas no MySQL de tools. + tools_markers = ("tools", "tool") + if any(marker in text for marker in tools_markers): + return "Servico temporariamente indisponivel: banco MySQL (tools) inacessivel." + + # Heuristica para identificar falhas no MySQL (base ficticia). + mysql_mock_markers = ("mock", "vehicles", "customers", "orders", "review_schedules") + if any(marker in text for marker in mysql_mock_markers): + return "Servico temporariamente indisponivel: banco MySQL (dados ficticios) inacessivel." + + mysql_generic_markers = ("mysql", "pymysql", "(2003", "3306") + if any(marker in text for marker in mysql_generic_markers): + return "Servico temporariamente indisponivel: banco MySQL inacessivel." + + return "Servico temporariamente indisponivel: erro de acesso ao banco de dados." diff --git a/app/api/routes/mock.py b/app/api/routes/mock.py new file mode 100644 index 0000000..ada6a22 --- /dev/null +++ b/app/api/routes/mock.py @@ -0,0 +1,111 @@ +from typing import Any, Dict, List + +from fastapi import APIRouter, HTTPException +from sqlalchemy.exc import SQLAlchemyError + +from app.api.routes.dependencies import db_error_detail +from app.api.schemas import ( + AgendarRevisaoRequest, + AvaliarVeiculoTrocaRequest, + CancelarPedidoRequest, + ConsultarEstoqueRequest, + RealizarPedidoRequest, + ValidarClienteVendaRequest, +) +from app.services.handlers import ( + agendar_revisao, + avaliar_veiculo_troca, + cancelar_pedido, + consultar_estoque, + realizar_pedido, + validar_cliente_venda, +) + +router = APIRouter(prefix="/mock", tags=["Mock"]) + + +@router.post("/consultar-estoque") +async def consultar_estoque_endpoint( + body: ConsultarEstoqueRequest, +) -> List[Dict[str, Any]]: + """Consulta estoque de veiculos mock com filtros opcionais.""" + try: + return await consultar_estoque( + preco_max=body.preco_max, + categoria=body.categoria, + ordenar_preco=body.ordenar_preco, + limite=body.limite, + ) + except SQLAlchemyError as exc: + raise HTTPException(status_code=503, detail=db_error_detail(exc)) + + +@router.post("/validar-cliente-venda") +async def validar_cliente_venda_endpoint( + body: ValidarClienteVendaRequest, +) -> Dict[str, Any]: + """Valida elegibilidade de cliente para uma venda.""" + try: + return await validar_cliente_venda( + cpf=body.cpf, + valor_veiculo=body.valor_veiculo, + ) + except SQLAlchemyError as exc: + raise HTTPException(status_code=503, detail=db_error_detail(exc)) + + +@router.post("/avaliar-veiculo-troca") +async def avaliar_veiculo_troca_endpoint( + body: AvaliarVeiculoTrocaRequest, +) -> Dict[str, Any]: + """Avalia valor de troca de um veiculo com base em modelo, ano e quilometragem.""" + return await avaliar_veiculo_troca( + modelo=body.modelo, + ano=body.ano, + km=body.km, + ) + + +@router.post("/agendar-revisao") +async def agendar_revisao_endpoint( + body: AgendarRevisaoRequest, +) -> Dict[str, Any]: + """Agenda revisao para uma placa em data/hora informada.""" + try: + return await agendar_revisao( + placa=body.placa, + data_hora=body.data_hora, + user_id=body.user_id, + ) + except SQLAlchemyError as exc: + raise HTTPException(status_code=503, detail=db_error_detail(exc)) + + +@router.post("/cancelar-pedido") +async def cancelar_pedido_endpoint( + body: CancelarPedidoRequest, +) -> Dict[str, Any]: + """Cancela pedido de venda existente registrando o motivo.""" + try: + return await cancelar_pedido( + numero_pedido=body.numero_pedido, + motivo=body.motivo, + user_id=body.user_id, + ) + except SQLAlchemyError as exc: + raise HTTPException(status_code=503, detail=db_error_detail(exc)) + + +@router.post("/realizar-pedido") +async def realizar_pedido_endpoint( + body: RealizarPedidoRequest, +) -> Dict[str, Any]: + """Cria um pedido de compra para cliente aprovado no valor informado.""" + try: + return await realizar_pedido( + cpf=body.cpf, + valor_veiculo=body.valor_veiculo, + user_id=body.user_id, + ) + except SQLAlchemyError as exc: + raise HTTPException(status_code=503, detail=db_error_detail(exc)) diff --git a/app/api/tool_routes.py b/app/api/routes/tools.py similarity index 69% rename from app/api/tool_routes.py rename to app/api/routes/tools.py index 4af230c..e516d34 100644 --- a/app/api/tool_routes.py +++ b/app/api/routes/tools.py @@ -1,30 +1,21 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session -from app.db.database import SessionLocal -from app.repositories.tool_repository import ToolRepository + +from app.api.routes.dependencies import get_db from app.api.schemas import ToolCreate, ToolResponse +from app.repositories.tool_repository import ToolRepository router = APIRouter(prefix="/tools", tags=["Tools"]) -# Dependency para abrir e fechar conexão automaticamente -def get_db(): - """Fornece sessao para operacoes de tools e fecha conexao ao final da request.""" - db = SessionLocal() - try: - yield db - finally: - db.close() - - -@router.post("/", response_model=ToolResponse) # Desenvolver uma tela de cadastro e atualização +@router.post("/", response_model=ToolResponse) def create_tool(tool: ToolCreate, db: Session = Depends(get_db)): """Cria uma nova tool persistida no banco.""" repo = ToolRepository(db) return repo.create( name=tool.name, description=tool.description, - parameters=tool.parameters + parameters=tool.parameters, ) @@ -42,7 +33,7 @@ def get_tool(tool_id: int, db: Session = Depends(get_db)): tool = repo.get_by_id(tool_id) if not tool: - raise HTTPException(status_code=404, detail="Tool não encontrada") + raise HTTPException(status_code=404, detail="Tool nao encontrada") return tool @@ -54,6 +45,6 @@ def delete_tool(tool_id: int, db: Session = Depends(get_db)): tool = repo.delete(tool_id) if not tool: - raise HTTPException(status_code=404, detail="Tool não encontrada") + raise HTTPException(status_code=404, detail="Tool nao encontrada") return {"message": "Tool removida com sucesso"} diff --git a/app/main.py b/app/main.py index 670f98e..0803f9f 100644 --- a/app/main.py +++ b/app/main.py @@ -1,7 +1,6 @@ from fastapi import FastAPI -from app.api.routes import router -from app.api.tool_routes import router as tool_router +from app.api.routes import router, tool_router from app.core.settings import settings from app.db.database import Base, engine from app.db.mock_database import MockBase, mock_engine