Commit inicial

This commit is contained in:
Ricardo 2026-09-11 17:22:41 -03:00
commit 5385af660d
10 changed files with 173 additions and 0 deletions

33
app/core/configs.py Normal file
View file

@ -0,0 +1,33 @@
from pathlib import Path
from pydantic import SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict
# Raiz do projeto (api-podium/), pra achar o .env independente de onde a API for iniciada
RAIZ_PROJETO = Path(__file__).resolve().parents[2]
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=RAIZ_PROJETO / '.env',
env_file_encoding='utf-8',
env_ignore_empty=True, # variável vazia no .env conta como não configurada
extra='ignore', # outras variáveis no .env não geram erro
)
# SQL Server
db_host: str
db_porta: int = 1433
db_database: str
db_login: str
db_senha: SecretStr # SecretStr evita que a senha apareça em print/log
db_driver: str = 'ODBC Driver 17 for SQL Server'
# Pool de conexões
db_pool_size: int = 5
db_pool_max_overflow: int = 10
# Configs da api
port: int
settings = Settings()

47
app/core/database.py Normal file
View file

@ -0,0 +1,47 @@
from collections.abc import Iterator
from typing import Annotated
import pyodbc
from fastapi import Depends
from sqlalchemy import create_engine
from sqlalchemy.engine import URL
from sqlalchemy.pool import PoolProxiedConnection
from app.core.configs import settings
# O pool do SQLAlchemy substitui o pool interno do ODBC. Com os dois ligados,
# conexões que o SQLAlchemy descarta continuam abertas no driver.
pyodbc.pooling = False
def _escapar(valor: str) -> str:
# Envolve o valor em chaves pra que ; ou } na senha não quebrem a connection string
return '{' + valor.replace('}', '}}') + '}'
connection_string = (
f"DRIVER={_escapar(settings.db_driver)};"
f"SERVER={settings.db_host},{settings.db_porta};"
f"DATABASE={_escapar(settings.db_database)};"
f"UID={_escapar(settings.db_login)};"
f"PWD={_escapar(settings.db_senha.get_secret_value())};"
)
engine = create_engine(
URL.create('mssql+pyodbc', query={'odbc_connect': connection_string}),
pool_size=settings.db_pool_size, # conexões mantidas abertas
max_overflow=settings.db_pool_max_overflow, # extras permitidas em pico de acesso
pool_pre_ping=True, # testa a conexão antes de entregar (o servidor pode ter derrubado)
pool_recycle=1800, # renova conexões com mais de 30 min
)
def get_conexao() -> Iterator[PoolProxiedConnection]:
"""Pega uma conexão do pool e devolve ao final da requisição."""
conexao = engine.raw_connection()
try:
yield conexao
finally:
conexao.close() # devolve ao pool, não fecha de verdade
Conexao = Annotated[PoolProxiedConnection, Depends(get_conexao)]

7
app/v1/api.py Normal file
View file

@ -0,0 +1,7 @@
from fastapi import APIRouter
from app.v1.modules.veiculo.router import veiculo_router
api_router = APIRouter()
api_router.include_router(veiculo_router)

View file

View file

@ -0,0 +1,5 @@
from pydantic import BaseModel
class CancelamentoPropostaInput(BaseModel):
id: str

View file

@ -0,0 +1,30 @@
from fastapi import APIRouter, HTTPException, status
from app.core.database import Conexao
from app.v1.modules.veiculo.model import CancelamentoPropostaInput
from app.v1.modules.veiculo.services import CancelamentoProposta
veiculo_router = APIRouter(
prefix='/veiculos'
)
# Rota de cancelamento (logica pra informar que o processo foi cancelado, esses vendedores que ficam crinando proposta pra dps cancelar é foda)
@veiculo_router.post(
path='/cancelar_proposta'
)
def cancelar_proposta(
dados_proposta_holmes: CancelamentoPropostaInput,
conexao: Conexao,
):
repo = CancelamentoProposta(conexao)
cancelou = repo.cancelar_no_banco(dados_proposta_holmes.id)
if not cancelou:
raise HTTPException(
status_code= status.HTTP_404_NOT_FOUND,
detail= "Proposta não encontrada no banco de dados"
)
return {
'mensagem': 'Proposta cancelada'
}

View file

@ -0,0 +1,17 @@
from sqlalchemy.pool import PoolProxiedConnection
class CancelamentoProposta:
def __init__(self, conexao: PoolProxiedConnection) -> None:
self.conexao = conexao
def cancelar_no_banco(self, id_processo: str) -> bool:
cursor = self.conexao.cursor()
cursor.execute("""
UPDATE HOLMES_FAT_ESTOQUE
SET STATUS = 'CANCELADO', DTA_ALTERACAO_STATUS = GETDATE()
WHERE ID_PROCESSO = ?
""", id_processo)
self.conexao.commit()
return cursor.rowcount > 0