[MIR-4] feat(Initial): Cria o projeto inicial da aplicação
This commit is contained in:
commit
a41e383dad
42 changed files with 1378 additions and 0 deletions
9
.gitattributes
vendored
Normal file
9
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Normaliza finais de linha
|
||||
* text=auto
|
||||
|
||||
# Força Python e arquivos de configuração a usarem LF
|
||||
*.py text eol=lf
|
||||
*.sh text eol=lf
|
||||
*.yml text eol=lf
|
||||
*.yaml text eol=lf
|
||||
*.env text eol=lf
|
||||
46
.gitignore
vendored
Normal file
46
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Ambiente virtual
|
||||
venv/
|
||||
.env
|
||||
.env.*
|
||||
|
||||
# Bytecode compilado
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# Arquivos temporários do sistema
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Logs e databases locais
|
||||
*.log
|
||||
*.sqlite3
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
||||
|
||||
# PyCharm
|
||||
.idea/
|
||||
|
||||
# Arquivos de testes ou builds
|
||||
*.coverage
|
||||
htmlcov/
|
||||
coverage.xml
|
||||
dist/
|
||||
build/
|
||||
.eggs/
|
||||
*.egg-info/
|
||||
|
||||
# Cache do pip
|
||||
pip-wheel-metadata/
|
||||
*.egg
|
||||
.cache/
|
||||
.tox/
|
||||
|
||||
# Arquivo s de conexão
|
||||
config/database/firebird.json
|
||||
storage/temp
|
||||
storage/temp.json
|
||||
|
||||
# Ignorar arquivos storage
|
||||
storage/
|
||||
26
Dockerfile
Normal file
26
Dockerfile
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Usa a imagem oficial do Python
|
||||
FROM python:3.12-slim
|
||||
|
||||
# Define diretório de trabalho no container
|
||||
WORKDIR /app
|
||||
|
||||
# Copia o arquivo de dependências
|
||||
COPY requirements.txt .
|
||||
|
||||
# Instala dependências no sistema e no Python
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc libffi-dev libssl-dev python3-dev firebird-dev \
|
||||
&& pip install --upgrade pip \
|
||||
&& pip install --no-cache-dir -r requirements.txt \
|
||||
&& apt-get remove -y gcc \
|
||||
&& apt-get autoremove -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copia o restante do projeto para o container
|
||||
COPY . .
|
||||
|
||||
# Expõe a porta padrão do Uvicorn/FastAPI
|
||||
EXPOSE 8000
|
||||
|
||||
# Comando para iniciar o servidor
|
||||
CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port 8000"]
|
||||
142
README.md
Normal file
142
README.md
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
# Configuração do Projeto Python
|
||||
|
||||
Este guia descreve o passo a passo para configurar o ambiente de desenvolvimento de um projeto Python, incluindo a preparação do ambiente virtual, instalação de dependências e configuração do banco de dados.
|
||||
|
||||
---
|
||||
|
||||
## 1. Clonar o Projeto
|
||||
|
||||
Primeiro, clone o repositório do projeto a partir do Git:
|
||||
|
||||
```bash
|
||||
git clone https://git.oriustecnologia.com/OriusTecnologia/Mirror.git
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Criar o Ambiente Virtual
|
||||
|
||||
O uso de um **ambiente virtual** garante que as bibliotecas instaladas para este projeto não afetem o Python global da sua máquina.
|
||||
|
||||
```bash
|
||||
python -m venv venv
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Ativar o Ambiente Virtual
|
||||
|
||||
Ative o ambiente virtual antes de instalar as dependências ou executar a aplicação.
|
||||
|
||||
```bash
|
||||
venv\Scripts\activate
|
||||
```
|
||||
|
||||
> **Observação:**
|
||||
> Em sistemas Unix (Linux/Mac), o comando pode ser:
|
||||
>
|
||||
> ```bash
|
||||
> source venv/bin/activate
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## 4. Instalar Dependências do Sistema
|
||||
|
||||
A biblioteca de criptografia utilizada no projeto requer uma extensão da Microsoft para ser instalada.
|
||||
Baixe e instale o **Microsoft C++ Build Tools** através do link abaixo:
|
||||
|
||||
[https://visualstudio.microsoft.com/pt-br/visual-cpp-build-tools/](https://visualstudio.microsoft.com/pt-br/visual-cpp-build-tools/)
|
||||
|
||||
Durante a instalação, selecione o pacote:
|
||||
|
||||
```
|
||||
Desktop Development With C++
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Instalar as Bibliotecas do Projeto
|
||||
|
||||
Com o ambiente virtual **ativado**, instale as dependências listadas no arquivo `requirements.txt`:
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Configurar o Banco de Dados
|
||||
|
||||
O projeto utiliza um banco **Firebird**.
|
||||
Edite o arquivo de configuração localizado em:
|
||||
|
||||
```
|
||||
api/config/database/firebird.json
|
||||
```
|
||||
|
||||
Exemplo do conteúdo padrão:
|
||||
|
||||
```json
|
||||
{
|
||||
"host": "localhost",
|
||||
"name": "D:/Orius/Base/CAIAPONIA.FDB",
|
||||
"port": 3050,
|
||||
"user": "SYSDBA",
|
||||
"password": "",
|
||||
"charset": "UTF8",
|
||||
"pool": {
|
||||
"pre_ping": true,
|
||||
"size": 5,
|
||||
"max_overflow": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Ajustes Necessários
|
||||
|
||||
* **host**: Endereço do servidor do banco de dados.
|
||||
* **name**: Caminho completo do arquivo `.FDB`.
|
||||
* **port**: Porta do Firebird (padrão: `3050`).
|
||||
* **user**: Usuário do banco de dados.
|
||||
* **password**: Senha do usuário configurado.
|
||||
|
||||
---
|
||||
|
||||
## 7. Iniciar a Aplicação
|
||||
|
||||
Com o ambiente virtual **ativado**, execute o comando abaixo para iniciar a aplicação:
|
||||
|
||||
```bash
|
||||
uvicorn main:app --reload
|
||||
```
|
||||
|
||||
> **Dica:**
|
||||
> O parâmetro `--reload` reinicia automaticamente a aplicação sempre que houver alterações no código.
|
||||
|
||||
---
|
||||
|
||||
## 8. Testando a Aplicação
|
||||
|
||||
Após iniciar a aplicação, abra o navegador e acesse o seguinte endereço:
|
||||
|
||||
```http
|
||||
http://localhost:8000/docs
|
||||
```
|
||||
|
||||
Você deverá visualizar a interface do **Swagger**, onde estarão listados todos os endpoints disponíveis da API.
|
||||
|
||||
> **Observação:**
|
||||
> O Swagger permite testar os endpoints diretamente pelo navegador, sem necessidade de ferramentas externas como Postman ou Insomnia.
|
||||
|
||||
---
|
||||
|
||||
## Resumo dos Comandos
|
||||
|
||||
| Etapa | Comando |
|
||||
| ----------------------- | ------------------------------------------------------------------------------- |
|
||||
| Clonar o projeto | `git clone https://git.oriustecnologia.com/OriusTecnologia/saas_api.git` |
|
||||
| Criar ambiente virtual | `python -m venv venv` |
|
||||
| Ativar ambiente virtual | `venv\Scripts\activate` *(Windows)*<br>`source venv/bin/activate` *(Linux/Mac)* |
|
||||
| Instalar dependências | `pip install -r requirements.txt` |
|
||||
| Iniciar a aplicação | `uvicorn main:app --reload` |
|
||||
17
abstracts/action.py
Normal file
17
abstracts/action.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class BaseAction:
|
||||
|
||||
"""
|
||||
Classe abstrata base para todos as actions do sistema.
|
||||
Obriga implementação de um método execute().
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def execute(self, *args, **kwargs):
|
||||
"""
|
||||
Método abstrato obrigatório a ser implementado pelas subclasses.
|
||||
Deve conter a lógica principal do repositório.
|
||||
"""
|
||||
pass
|
||||
41
abstracts/repository.py
Normal file
41
abstracts/repository.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# abstracts/repository.py
|
||||
from sqlalchemy.orm import Session
|
||||
from database.postgres import SessionLocal
|
||||
|
||||
|
||||
class BaseRepository:
|
||||
"""Classe base para repositórios ORM."""
|
||||
|
||||
def __init__(self):
|
||||
self.session: Session = SessionLocal()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
if exc_type:
|
||||
self.session.rollback()
|
||||
else:
|
||||
self.session.commit()
|
||||
self.session.close()
|
||||
|
||||
# 🔹 Métodos utilitários ORM
|
||||
def add(self, instance):
|
||||
"""Adiciona um registro."""
|
||||
self.session.add(instance)
|
||||
self.session.commit()
|
||||
self.session.refresh(instance)
|
||||
return instance
|
||||
|
||||
def delete(self, instance):
|
||||
"""Remove um registro."""
|
||||
self.session.delete(instance)
|
||||
self.session.commit()
|
||||
|
||||
def all(self, model, limit: int = 100):
|
||||
"""Retorna todos os registros de um modelo."""
|
||||
return self.session.query(model).limit(limit).all()
|
||||
|
||||
def get(self, model, pk: int):
|
||||
"""Busca um registro por ID."""
|
||||
return self.session.get(model, pk)
|
||||
20
actions/config/config.py
Normal file
20
actions/config/config.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import json
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
|
||||
class Config:
|
||||
|
||||
@staticmethod
|
||||
def get(name: str):
|
||||
# Caminho absoluto do arquivo atual
|
||||
base_dir = Path(__file__).resolve().parent
|
||||
|
||||
# Caminho absoluto para o config.json (subindo dois níveis e entrando em config/)
|
||||
config_path = base_dir.parent.parent / 'config' / name
|
||||
|
||||
# Carrega o JSON como objeto acessível por ponto
|
||||
with open(config_path, 'r') as f:
|
||||
config = json.load(f, object_hook=lambda d: SimpleNamespace(**d))
|
||||
|
||||
return config
|
||||
32
actions/dynamic_import/dynamic_import.py
Normal file
32
actions/dynamic_import/dynamic_import.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import importlib
|
||||
from actions.config.config import Config
|
||||
from typing import Optional, Any, Type
|
||||
|
||||
|
||||
|
||||
class DynamicImport:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.config: dict[str, Any] = Config.get("app.json")
|
||||
self.base: str = "packages.v1"
|
||||
self.package: Optional[str] = None
|
||||
self.table: Optional[str] = None
|
||||
|
||||
def set_package(self, name: str) -> None:
|
||||
self.package = name
|
||||
|
||||
def set_table(self, table: str):
|
||||
self.table = table
|
||||
|
||||
def service(self, name: str, class_name : str) -> Type[Any]:
|
||||
try:
|
||||
# Define o nome do Módulo
|
||||
module_file = f"{name}"
|
||||
# Define o caminho do arquivo
|
||||
path = f"{self.base}.{self.package}.services.{self.table}.{self.config.state}.{module_file}"
|
||||
# Realiza a importação do arquivo
|
||||
module = importlib.import_module(path)
|
||||
clazz = getattr(module, class_name)
|
||||
return clazz
|
||||
except (ImportError, AttributeError) as e:
|
||||
raise ImportError(f"Erro ao importar '{class_name}' de '{path}': {e}")
|
||||
32
actions/file/file.py
Normal file
32
actions/file/file.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
|
||||
class File:
|
||||
|
||||
def create(self, data, caminho_arquivo='storage/temp.json'):
|
||||
try:
|
||||
# Garante que a pasta existe
|
||||
os.makedirs(os.path.dirname(caminho_arquivo), exist_ok=True)
|
||||
|
||||
# Lê dados existentes (ou cria nova lista)
|
||||
if os.path.exists(caminho_arquivo):
|
||||
with open(caminho_arquivo, 'r', encoding='utf-8') as arquivo:
|
||||
try:
|
||||
dados_existentes = json.load(arquivo)
|
||||
if not isinstance(dados_existentes, list):
|
||||
dados_existentes = []
|
||||
except json.JSONDecodeError:
|
||||
dados_existentes = []
|
||||
else:
|
||||
dados_existentes = []
|
||||
|
||||
# Adiciona novo dado
|
||||
dados_existentes.append(data)
|
||||
|
||||
# Salva novamente no arquivo com indentação
|
||||
with open(caminho_arquivo, 'w', encoding='utf-8') as arquivo:
|
||||
json.dump(dados_existentes, arquivo, indent=4, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Erro ao salvar o dado: {e}")
|
||||
36
actions/jwt/create_token.py
Normal file
36
actions/jwt/create_token.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
from datetime import datetime, timedelta
|
||||
from jose import jwt
|
||||
from pytz import timezone
|
||||
|
||||
from abstracts.action import BaseAction
|
||||
from actions.config.config import Config
|
||||
|
||||
class CreateToken(BaseAction):
|
||||
def __init__(self):
|
||||
# Busca as configurações da aplicação
|
||||
self.config = Config.get('app.json')
|
||||
|
||||
# Cria o timedelta com base na config
|
||||
self.access_token_expire = timedelta(
|
||||
minutes=self.config.jwt.expire.minute,
|
||||
hours=self.config.jwt.expire.hours,
|
||||
days=self.config.jwt.expire.days
|
||||
)
|
||||
|
||||
def execute(self, tipo_token: str, data : str) -> str:
|
||||
|
||||
sp = timezone('America/Sao_Paulo')
|
||||
agora = datetime.now(tz=sp)
|
||||
expira = agora + self.access_token_expire
|
||||
|
||||
# Define os dados do token
|
||||
payload = {
|
||||
'type' : tipo_token,
|
||||
'exp' : expira,
|
||||
'iat' : agora,
|
||||
'data' : str(data)
|
||||
}
|
||||
|
||||
# Retorna os dados codificados
|
||||
return jwt.encode(payload, self.config.jwt.token, algorithm=self.config.jwt.algorithm)
|
||||
|
||||
24
actions/jwt/get_current_user.py
Normal file
24
actions/jwt/get_current_user.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
from fastapi import Depends, HTTPException, status, Request
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
|
||||
from actions.jwt.verify_token import VerifyToken # A classe que criamos anteriormente
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") # Apenas requerido pelo FastAPI
|
||||
|
||||
def get_current_user(token: str = Depends(oauth2_scheme)):
|
||||
|
||||
# Ação que válida o tokne
|
||||
verify_token = VerifyToken()
|
||||
# Obtem o resultado da validação
|
||||
result = verify_token.execute(token)
|
||||
|
||||
# Verifica se a resposta é diferente de inválida
|
||||
if result['status'] != 'valid':
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=result.get('message', 'Token inválido ou expirado'),
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
# Retorna apenas os dados do token
|
||||
return result['payload']
|
||||
57
actions/jwt/verify_token.py
Normal file
57
actions/jwt/verify_token.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
from datetime import datetime
|
||||
from jose import jwt, JWTError, ExpiredSignatureError
|
||||
from pytz import timezone
|
||||
|
||||
from actions.config.config import Config
|
||||
|
||||
|
||||
class VerifyToken:
|
||||
def __init__(self):
|
||||
# Carrega configurações
|
||||
self.config = Config.get('app.json')
|
||||
|
||||
def execute(self, token: str, expected_type: str = 'access-token') -> dict:
|
||||
try:
|
||||
# Decodifica o token
|
||||
payload = jwt.decode(
|
||||
token,
|
||||
self.config.jwt.token,
|
||||
algorithms=[self.config.jwt.algorithm]
|
||||
)
|
||||
|
||||
# Valida expiração
|
||||
exp_timestamp = payload.get("exp")
|
||||
if exp_timestamp is None:
|
||||
raise ValueError("O token não possui data de expiração.")
|
||||
|
||||
# Verifica o tipo de token
|
||||
token_type = payload.get("type")
|
||||
if token_type != expected_type:
|
||||
raise ValueError("Tipo de token inválido.")
|
||||
|
||||
# Verificação opcional: validar campo "data"
|
||||
if "data" not in payload:
|
||||
raise ValueError("Token malformado: campo 'data' ausente.")
|
||||
|
||||
return {
|
||||
"status": "valid",
|
||||
"payload": payload
|
||||
}
|
||||
|
||||
except ExpiredSignatureError:
|
||||
return {
|
||||
"status": "expired",
|
||||
"message": "O token expirou."
|
||||
}
|
||||
|
||||
except JWTError as e:
|
||||
return {
|
||||
"status": "invalid",
|
||||
"message": f"Token inválido: {str(e)}"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": f"Erro na validação do token: {str(e)}"
|
||||
}
|
||||
32
actions/log/log.py
Normal file
32
actions/log/log.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
|
||||
class Log:
|
||||
|
||||
def register(self, data, caminho_arquivo='storage/temp.json'):
|
||||
try:
|
||||
# Garante que a pasta existe
|
||||
os.makedirs(os.path.dirname(caminho_arquivo), exist_ok=True)
|
||||
|
||||
# Lê dados existentes (ou cria nova lista)
|
||||
if os.path.exists(caminho_arquivo):
|
||||
with open(caminho_arquivo, 'r', encoding='utf-8') as arquivo:
|
||||
try:
|
||||
dados_existentes = json.load(arquivo)
|
||||
if not isinstance(dados_existentes, list):
|
||||
dados_existentes = []
|
||||
except json.JSONDecodeError:
|
||||
dados_existentes = []
|
||||
else:
|
||||
dados_existentes = []
|
||||
|
||||
# Adiciona novo dado
|
||||
dados_existentes.append(data)
|
||||
|
||||
# Salva novamente no arquivo com indentação
|
||||
with open(caminho_arquivo, 'w', encoding='utf-8') as arquivo:
|
||||
json.dump(dados_existentes, arquivo, indent=4, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Erro ao salvar o dado: {e}")
|
||||
43
actions/security/security.py
Normal file
43
actions/security/security.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# core/security.py
|
||||
|
||||
# Importa CryptContext da biblioteca passlib para operações de hash de senha
|
||||
from passlib.context import CryptContext
|
||||
|
||||
# Cria uma instância do contexto de criptografia
|
||||
# O esquema usado é 'bcrypt', que é seguro e amplamente aceito
|
||||
# O parâmetro 'deprecated="auto"' marca versões antigas como inseguras, se aplicável
|
||||
CRYPTO = CryptContext(schemes=['bcrypt'], deprecated='auto')
|
||||
|
||||
|
||||
class Security:
|
||||
|
||||
# Verifica se a senha tem um hash válido
|
||||
@staticmethod
|
||||
def is_hash(senha: str) -> bool:
|
||||
"""
|
||||
Verifica se a string fornecida é um hash reconhecido pelo CryptContext.
|
||||
"""
|
||||
return CRYPTO.identify(senha)
|
||||
|
||||
|
||||
# Verifica se uma senha fornecida corresponde ao hash armazenado
|
||||
def verify_senha_api(plain_senha_api: str, hashed_senha_api: str) -> bool:
|
||||
"""
|
||||
Compara a senha fornecida em texto puro com o hash armazenado.
|
||||
|
||||
:param plain_senha_api: Senha digitada pelo usuário
|
||||
:param hashed_senha_api: Hash da senha armazenado no banco de dados
|
||||
:return: True se corresponder, False se não
|
||||
"""
|
||||
return CRYPTO.verify(plain_senha_api, hashed_senha_api)
|
||||
|
||||
|
||||
# Gera o hash de uma senha fornecida
|
||||
def hash_senha_api(plain_senha_api: str) -> str:
|
||||
"""
|
||||
Gera e retorna o hash da senha fornecida.
|
||||
|
||||
:param plain_senha_api: Senha em texto puro fornecida pelo usuário
|
||||
:return: Hash da senha
|
||||
"""
|
||||
return CRYPTO.hash(plain_senha_api)
|
||||
4
actions/system/exceptions.py
Normal file
4
actions/system/exceptions.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# exceptions.py
|
||||
class BusinessRuleException(Exception):
|
||||
def __init__(self, message: str):
|
||||
self.message = message
|
||||
86
actions/system/handlers.py
Normal file
86
actions/system/handlers.py
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# handlers.py
|
||||
import json
|
||||
import traceback
|
||||
|
||||
from fastapi import Request
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import JSONResponse
|
||||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||
|
||||
from actions.system.exceptions import BusinessRuleException
|
||||
from actions.log.log import Log
|
||||
|
||||
|
||||
def register_exception_handlers(app):
|
||||
|
||||
def __init__ (self):
|
||||
log = Log()
|
||||
|
||||
@app.exception_handler(BusinessRuleException)
|
||||
async def business_rule_exception_handler(request: Request, exc: BusinessRuleException):
|
||||
|
||||
response = {
|
||||
"status": "422",
|
||||
"error": "Regra de negócio",
|
||||
"detail": exc.message
|
||||
}
|
||||
|
||||
# Salva o log em disco
|
||||
Log.register(response, 'storage/temp/business_rule_exception_handler.json')
|
||||
|
||||
return JSONResponse(
|
||||
status_code=422,
|
||||
content=response
|
||||
)
|
||||
|
||||
@app.exception_handler(StarletteHTTPException)
|
||||
async def http_exception_handler(request: Request, exc: StarletteHTTPException):
|
||||
response = {
|
||||
"status": exc.status_code,
|
||||
"error": "HTTP Error",
|
||||
"detail": exc.detail
|
||||
}
|
||||
|
||||
# Salva o log em disco
|
||||
Log.register(response, 'storage/temp/http_exception_handler.json')
|
||||
|
||||
return JSONResponse(
|
||||
status_code=exc.status_code,
|
||||
content=response
|
||||
)
|
||||
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
||||
|
||||
response = {
|
||||
"status": 400,
|
||||
"error": "Erro de validação",
|
||||
"detail": exc.errors()
|
||||
}
|
||||
|
||||
# Salva o log em disco
|
||||
Log.register(response, 'storage/temp/validation_exception_handler.json')
|
||||
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content=response
|
||||
)
|
||||
|
||||
@app.exception_handler(Exception)
|
||||
async def global_exception_handler(request: Request, exc: Exception):
|
||||
|
||||
response = {
|
||||
"status": 500,
|
||||
"error": "Erro Interno do Servidor",
|
||||
"type": type(exc).__name__,
|
||||
"message": str(exc),
|
||||
"trace": traceback.format_exc()
|
||||
}
|
||||
|
||||
# Salva o log em disco
|
||||
Log.register(response, 'storage/temp/validation_exception_handler.json')
|
||||
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content=response
|
||||
)
|
||||
8
actions/validations/cep.py
Normal file
8
actions/validations/cep.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
class CEP:
|
||||
|
||||
@staticmethod
|
||||
def validate(data: str) -> bool:
|
||||
|
||||
# Valida e retorna a informação
|
||||
return len(data) == 8
|
||||
35
actions/validations/cnpj.py
Normal file
35
actions/validations/cnpj.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import re
|
||||
|
||||
|
||||
class CNPJ:
|
||||
|
||||
@staticmethod
|
||||
def validate(data: str) -> bool:
|
||||
|
||||
# Remove caracteres não numéricos
|
||||
data = re.sub(r'\D', '', data)
|
||||
|
||||
# Verifica se tem 14 dígitos
|
||||
if len(data) != 14:
|
||||
return False
|
||||
|
||||
# CNPJs com todos os dígitos iguais são inválidos
|
||||
if data == data[0] * 14:
|
||||
return False
|
||||
|
||||
# Calcula os dois dígitos verificadores
|
||||
def calcular_digito(data, peso):
|
||||
soma = sum(int(a) * b for a, b in zip(data, peso))
|
||||
resto = soma % 11
|
||||
return '0' if resto < 2 else str(11 - resto)
|
||||
|
||||
# Primeiro dígito verificador
|
||||
peso1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
|
||||
digito1 = calcular_digito(data[:12], peso1)
|
||||
|
||||
# Segundo dígito verificador
|
||||
peso2 = [6] + peso1
|
||||
digito2 = calcular_digito(data[:12] + digito1, peso2)
|
||||
|
||||
# Verifica se os dígitos batem
|
||||
return data[-2:] == digito1 + digito2
|
||||
34
actions/validations/cpf.py
Normal file
34
actions/validations/cpf.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import re
|
||||
|
||||
|
||||
class CPF:
|
||||
|
||||
@staticmethod
|
||||
def is_valid_cpf(data: str) -> bool:
|
||||
# Remove caracteres não numéricos
|
||||
data = re.sub(r'\D', '', data)
|
||||
|
||||
# Verifica se tem 11 dígitos
|
||||
if len(data) != 11:
|
||||
return False
|
||||
|
||||
# CPFs com todos os dígitos iguais são inválidos
|
||||
if data == data[0] * 11:
|
||||
return False
|
||||
|
||||
# Calcula o primeiro e segundo dígitos verificadores
|
||||
def calcular_digito(digitos, peso):
|
||||
soma = sum(int(a) * b for a, b in zip(digitos, peso))
|
||||
resto = soma % 11
|
||||
return '0' if resto < 2 else str(11 - resto)
|
||||
|
||||
# Primeiro dígito verificador
|
||||
peso1 = range(10, 1, -1)
|
||||
digito1 = calcular_digito(data[:9], peso1)
|
||||
|
||||
# Segundo dígito verificador
|
||||
peso2 = range(11, 1, -1)
|
||||
digito2 = calcular_digito(data[:10], peso2)
|
||||
|
||||
# Verifica se os dígitos batem
|
||||
return data[-2:] == digito1 + digito2
|
||||
9
actions/validations/email.py
Normal file
9
actions/validations/email.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import re
|
||||
|
||||
|
||||
class Email:
|
||||
|
||||
@staticmethod
|
||||
def is_valid_email(email: str) -> bool:
|
||||
"""Check if email has a valid structure"""
|
||||
return bool(re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", email))
|
||||
12
actions/validations/phone.py
Normal file
12
actions/validations/phone.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
class Phone:
|
||||
|
||||
@staticmethod
|
||||
def validate_cellphone(data: str) -> bool:
|
||||
# Verifica e retorna se o numero de celular é igual a 11
|
||||
return len(data) == 11
|
||||
|
||||
@staticmethod
|
||||
def validate_telephone(data: str) -> bool:
|
||||
# Verifica e retorna se o numero de telefone é igual a 11
|
||||
return len(data) == 10
|
||||
63
actions/validations/text.py
Normal file
63
actions/validations/text.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import html
|
||||
import re
|
||||
|
||||
|
||||
class Text:
|
||||
|
||||
# Remove as mascaras de números
|
||||
@staticmethod
|
||||
def just_numbers(data: str) -> str:
|
||||
""" Mantêm apenas os numeros """
|
||||
data = re.sub(r"[^\d]", "", data)
|
||||
return data
|
||||
|
||||
# Verifica se um e-mail é válido
|
||||
@staticmethod
|
||||
def is_valid_email(email: str) -> bool:
|
||||
"""Check if email has a valid structure"""
|
||||
return bool(re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", email))
|
||||
|
||||
|
||||
"""
|
||||
Sanitiza entradas de texto contra XSS e SQL Injection básicos.
|
||||
- Remove espaços extras
|
||||
- Escapa entidades HTML
|
||||
- Remove padrões suspeitos de XSS e SQL Injection
|
||||
- Normaliza múltiplos espaços em um só
|
||||
"""
|
||||
@staticmethod
|
||||
def sanitize_input(data: str) -> str:
|
||||
|
||||
if not data:
|
||||
return data
|
||||
|
||||
# 1) Remove espaços no início e no fim
|
||||
data = data.strip()
|
||||
|
||||
# 2) Escapa entidades HTML (< > & ")
|
||||
data = html.escape(data)
|
||||
|
||||
# 3) Remove múltiplos espaços seguidos
|
||||
data = re.sub(r"\s+", " ", data)
|
||||
|
||||
# 4) Remove tags <script> (com atributos)
|
||||
data = re.sub(r'<\s*script[^>]*>', '', data, flags=re.IGNORECASE)
|
||||
data = re.sub(r'<\s*/\s*script\s*>', '', data, flags=re.IGNORECASE)
|
||||
|
||||
# 5) Remove javascript: de links
|
||||
data = re.sub(r'javascript\s*:', '', data, flags=re.IGNORECASE)
|
||||
|
||||
# 6) Remove palavras-chave SQL Injection comuns
|
||||
blacklist = [
|
||||
"--", ";", "/*", "*/", "@@",
|
||||
"char(", "nchar(", "varchar(",
|
||||
"alter", "drop", "exec", "insert",
|
||||
"delete", "update", "union", "select",
|
||||
"from", "where"
|
||||
]
|
||||
for word in blacklist:
|
||||
# Verificar se 'word' é uma string não vazia e válida para a regex
|
||||
if word:
|
||||
data = re.sub(re.escape(word), "", data, flags=re.IGNORECASE)
|
||||
|
||||
return data
|
||||
24
config/app.json
Normal file
24
config/app.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"state" : "go",
|
||||
"url": "/api/v1",
|
||||
"log": {
|
||||
"request": {
|
||||
"name": "request.json",
|
||||
"path": "storage/temp"
|
||||
}
|
||||
},
|
||||
"StartupCheck": {
|
||||
"database": true,
|
||||
"disk": true
|
||||
},
|
||||
"jwt" : {
|
||||
"token" : "WYe1zwtlDkh39_X3X3qTSICFDxts4VQrMyGLxnEpGUg",
|
||||
"algorithm" : "HS256",
|
||||
"type" : "",
|
||||
"expire" : {
|
||||
"minute" : 60,
|
||||
"hours" : 24,
|
||||
"days" : 7
|
||||
}
|
||||
}
|
||||
}
|
||||
13
config/database/firebird_exemple.json
Normal file
13
config/database/firebird_exemple.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"host": "localhost",
|
||||
"name": "D:/Orius/Base/CAIAPONIA.FDB",
|
||||
"port": 3050,
|
||||
"user": "SYSDBA",
|
||||
"password": "master!orius",
|
||||
"charset": "UTF8",
|
||||
"pool" : {
|
||||
"pre_ping" : true,
|
||||
"size" : 5,
|
||||
"max_overflow" :10
|
||||
}
|
||||
}
|
||||
14
config/database/postgres.json
Normal file
14
config/database/postgres.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"host": "localhost",
|
||||
"port": 5432,
|
||||
"name": "mirror",
|
||||
"user": "postgres",
|
||||
"password": "root",
|
||||
"charset": "UTF8",
|
||||
"pool": {
|
||||
"pre_ping": true,
|
||||
"size": 5,
|
||||
"max_overflow": 10
|
||||
},
|
||||
"debug": false
|
||||
}
|
||||
43
database/firebird.py
Normal file
43
database/firebird.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
from typing import Optional
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.engine import Engine
|
||||
|
||||
from actions.config.config import Config
|
||||
|
||||
|
||||
class Firebird:
|
||||
_engine: Optional[Engine] = None
|
||||
|
||||
@classmethod
|
||||
def get_engine(cls) -> Engine:
|
||||
# Obtem as configurações do arquivo JSON
|
||||
database = Config.get('database/firebird.json')
|
||||
|
||||
# Cria a engine apenas uma vez
|
||||
if cls._engine is None:
|
||||
|
||||
# DSN para o SQLAlchemy usando firebird-driver
|
||||
dsn = (
|
||||
f"firebird+firebird://{database.user}:"
|
||||
f"{database.password}@"
|
||||
f"{database.host}:"
|
||||
f"{database.port}/"
|
||||
f"{database.name}"
|
||||
)
|
||||
|
||||
# Criação da engine SQLAlchemy
|
||||
cls._engine = create_engine(
|
||||
dsn,
|
||||
connect_args={"charset": database.charset},
|
||||
pool_pre_ping=bool(database.pool.pre_ping),
|
||||
pool_size=database.pool.size,
|
||||
max_overflow=database.pool.max_overflow,
|
||||
)
|
||||
|
||||
return cls._engine
|
||||
|
||||
@classmethod
|
||||
def dispose(cls):
|
||||
if cls._engine:
|
||||
cls._engine.dispose()
|
||||
cls._engine = None
|
||||
40
database/postgres.py
Normal file
40
database/postgres.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker, declarative_base
|
||||
from actions.config.config import Config
|
||||
|
||||
|
||||
# Base para os modelos ORM
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
def get_database_settings():
|
||||
"""Lê as configurações do arquivo database/postgres.json"""
|
||||
return Config.get("database/postgres.json")
|
||||
|
||||
|
||||
def get_postgres_engine():
|
||||
"""Cria e retorna a engine PostgreSQL."""
|
||||
db = get_database_settings()
|
||||
|
||||
dsn = (
|
||||
f"postgresql+psycopg2://{db.user}:{db.password}@"
|
||||
f"{db.host}:{db.port}/{db.name}"
|
||||
)
|
||||
|
||||
engine = create_engine(
|
||||
dsn,
|
||||
echo=bool(getattr(db, "debug", False)),
|
||||
pool_pre_ping=bool(db.pool.pre_ping),
|
||||
pool_size=int(db.pool.size),
|
||||
max_overflow=int(db.pool.max_overflow),
|
||||
connect_args={"connect_timeout": 10},
|
||||
)
|
||||
|
||||
return engine
|
||||
|
||||
|
||||
# Criação da engine global
|
||||
engine = get_postgres_engine()
|
||||
|
||||
# Criação da sessão ORM (SessionLocal)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
89
main.py
Normal file
89
main.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# Ajuste para garantir que o diretório base do projeto seja incluído no PYTHONPATH
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Adiciona o diretório atual (onde está o main.py) ao sys.path
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
# Importa a classe principal do FastAPI
|
||||
from fastapi import FastAPI, Request
|
||||
from pathlib import Path
|
||||
# Importa o middleware de CORS
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import Response
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
|
||||
# Importa o roteador principal da API versão 1
|
||||
from packages.v1.api import api_router
|
||||
from packages.v1.system.service.startup_check_service import \
|
||||
StartupCheckService
|
||||
|
||||
# Importa as configurações globais da aplicação
|
||||
from actions.log.log import Log
|
||||
from actions.config.config import Config
|
||||
from actions.system.handlers import register_exception_handlers
|
||||
|
||||
config = Config.get('app.json')
|
||||
|
||||
# Instancia o app FastAPI com um título personalizado
|
||||
app = FastAPI(title='Mirror | Orius')
|
||||
|
||||
# Controle de erros personalizados
|
||||
register_exception_handlers(app)
|
||||
|
||||
# Adiciona o middleware de CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"], # Domínio do frontend
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
@app.on_event("startup")
|
||||
async def on_startup():
|
||||
|
||||
# Realiza as verificações do servidor
|
||||
startupCheckService = StartupCheckService()
|
||||
|
||||
# Exibe o amarzenamento do servidor
|
||||
print(startupCheckService.execute())
|
||||
|
||||
@app.middleware("http")
|
||||
async def log_tempo_requisicao(request: Request, call_next):
|
||||
|
||||
# Ação responsavel por registrar o log de requisição
|
||||
log = Log()
|
||||
config = Config.get('app.json')
|
||||
|
||||
# Obtem os dados da requisição
|
||||
log_data = {
|
||||
"method": request.method,
|
||||
"url": str(request.url),
|
||||
"headers": dict(request.headers)
|
||||
}
|
||||
|
||||
# Gera o nome do arquivo
|
||||
file = Path(config.log.request.path) / config.log.request.name
|
||||
|
||||
# Registra as requisições
|
||||
log.register(log_data, file)
|
||||
|
||||
# Passa adiante
|
||||
response = await call_next(request)
|
||||
|
||||
return response
|
||||
|
||||
# Inclui as rotas da versão 1 da API com prefixo definido em settings (ex: /api/v1)
|
||||
app.include_router(api_router, prefix=config.url)
|
||||
|
||||
# Executa o servidor com Uvicorn se este arquivo for executado diretamente
|
||||
if __name__ == '__main__':
|
||||
import uvicorn
|
||||
uvicorn.run(
|
||||
"main:app", # Caminho do app para execução
|
||||
host="0.0.0.0", # Disponibiliza a aplicação externamente
|
||||
port=8000, # Porta padrão
|
||||
log_level='info', # Define o nível de log para desenvolvimento
|
||||
reload=True # Ativa auto-reload durante desenvolvimento
|
||||
)
|
||||
0
packages/__init__.py
Normal file
0
packages/__init__.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
from abstracts.action import BaseAction
|
||||
from packages.v1.administrativo.repositories.ato_principal.ato_principal_index_repository import AtoPrincipalIndexRepository
|
||||
|
||||
|
||||
class AtoPrincipalIndexAction(BaseAction):
|
||||
|
||||
def execute(self):
|
||||
|
||||
# Instânciamento de repositório
|
||||
ato_principal_index_repository = AtoPrincipalIndexRepository()
|
||||
|
||||
# Retorna todos produtos
|
||||
return ato_principal_index_repository.execute()
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# Importação de bibliotecas
|
||||
from actions.dynamic_import.dynamic_import import DynamicImport
|
||||
from packages.v1.administrativo.services.ato_principal.go.ato_principal_index_service import AtoPrincipalIndexService
|
||||
|
||||
|
||||
class AtoPrincipalController:
|
||||
|
||||
def index(self):
|
||||
|
||||
# Importação da classe desejad
|
||||
ato_principal_index_service = AtoPrincipalIndexService()
|
||||
|
||||
# Intânciamento da classe service
|
||||
self.index_service = ato_principal_index_service
|
||||
|
||||
# Lista todos os produtos
|
||||
return {
|
||||
'message' : 'Registros localizados com sucesso',
|
||||
'data': self.index_service.execute()
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
# Importação de bibliotecas
|
||||
from fastapi import APIRouter, Body, Depends, status
|
||||
from actions.jwt.get_current_user import get_current_user
|
||||
from packages.v1.administrativo.controllers.ato_principal_controller import AtoPrincipalController
|
||||
|
||||
# Inicializar o roteaodr para as rotas de produtos
|
||||
router = APIRouter()
|
||||
|
||||
# Instãnciamento do controller desejado
|
||||
ato_principal_controller = AtoPrincipalController()
|
||||
|
||||
@router.get("/",
|
||||
status_code=status.HTTP_200_OK,
|
||||
summary="Busca itens com filtros opcionais",
|
||||
response_description="Lista de itens encontrados com base nos critérios de busca.")
|
||||
async def index():
|
||||
|
||||
# Busca todos os produtos cadastrados
|
||||
response = ato_principal_controller.index()
|
||||
|
||||
# Retornar os dados localizados
|
||||
return response
|
||||
65
packages/v1/administrativo/models/ato_principal_model.py
Normal file
65
packages/v1/administrativo/models/ato_principal_model.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# packages/v1/administrativo/models/ato_principal_model.py
|
||||
|
||||
from sqlalchemy import (
|
||||
Column,
|
||||
BigInteger,
|
||||
Integer,
|
||||
String,
|
||||
DateTime,
|
||||
Numeric,
|
||||
Text,
|
||||
ForeignKey,
|
||||
CheckConstraint,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import INET
|
||||
from sqlalchemy.sql import func
|
||||
from database.postgres import Base
|
||||
|
||||
|
||||
class AtoPrincipal(Base):
|
||||
"""
|
||||
Representa o modelo da tabela 'ato_principal' no banco de dados PostgreSQL.
|
||||
"""
|
||||
|
||||
__tablename__ = "ato_principal"
|
||||
|
||||
ato_principal_id = Column(BigInteger, primary_key=True, autoincrement=True, index=True)
|
||||
|
||||
origem_ato_principal_id = Column(
|
||||
BigInteger,
|
||||
ForeignKey("ato_principal.ato_principal_id", ondelete="SET NULL", onupdate="CASCADE"),
|
||||
nullable=True,
|
||||
)
|
||||
|
||||
identificacao_pedido_cgj = Column(BigInteger, nullable=False)
|
||||
tipo_ato = Column(Integer, nullable=False)
|
||||
codigo_selo = Column(String(50), nullable=False, unique=True)
|
||||
codigo_ato = Column(String(50), nullable=False, unique=True)
|
||||
nome_civil_ato = Column(String(255), nullable=False)
|
||||
nome_serventuario_praticou_ato = Column(String(255), nullable=False)
|
||||
data_solicitacao = Column(DateTime(timezone=True), nullable=False)
|
||||
ip_maquina = Column(INET, nullable=True)
|
||||
inteiro_teor = Column(Text, nullable=False)
|
||||
valor_entrada = Column(Numeric(12, 2), nullable=True, default=0)
|
||||
emolumento = Column(Numeric(12, 2), nullable=False)
|
||||
taxa_judiciaria = Column(Numeric(12, 2), nullable=False)
|
||||
fundos_estaduais = Column(Numeric(12, 2), nullable=False)
|
||||
protocolo_protesto = Column(String(50), nullable=True)
|
||||
protocolo_imovel = Column(String(50), nullable=True)
|
||||
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
||||
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
|
||||
|
||||
# Constraints e índices adicionais
|
||||
__table_args__ = (
|
||||
CheckConstraint(
|
||||
"(COALESCE(valor_entrada, 0) >= 0) AND (emolumento >= 0) AND (taxa_judiciaria >= 0) AND (fundos_estaduais >= 0)",
|
||||
name="chk_valores_positivos",
|
||||
),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f"<AtoPrincipal(id={self.ato_principal_id}, "
|
||||
f"codigo_ato='{self.codigo_ato}', selo='{self.codigo_selo}', tipo={self.tipo_ato})>"
|
||||
)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
from database.postgres import SessionLocal
|
||||
from packages.v1.administrativo.models.ato_principal_model import AtoPrincipal
|
||||
from packages.v1.administrativo.schemas.ato_principal_schema import AtoPrincipalResponseSchema
|
||||
|
||||
|
||||
class AtoPrincipalIndexRepository:
|
||||
def execute(self):
|
||||
# Cria a sessão dentro do repositório
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# Executa a query
|
||||
result = db.query(AtoPrincipal).all()
|
||||
|
||||
# Converte os models SQLAlchemy em schemas Pydantic
|
||||
data = [AtoPrincipalResponseSchema.model_validate(obj) for obj in result]
|
||||
|
||||
return data
|
||||
|
||||
finally:
|
||||
# Fecha a sessão após o uso (evita vazamento de conexão)
|
||||
db.close()
|
||||
0
packages/v1/administrativo/schemas/__init__.py
Normal file
0
packages/v1/administrativo/schemas/__init__.py
Normal file
56
packages/v1/administrativo/schemas/ato_principal_schema.py
Normal file
56
packages/v1/administrativo/schemas/ato_principal_schema.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from ipaddress import IPv4Address, IPv6Address
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Schema base - campos principais e comuns
|
||||
# ----------------------------------------------------
|
||||
class AtoPrincipalBaseSchema(BaseModel):
|
||||
origem_ato_principal_id: Optional[int] = None
|
||||
identificacao_pedido_cgj: int
|
||||
tipo_ato: int
|
||||
codigo_selo: str
|
||||
codigo_ato: str
|
||||
nome_civil_ato: str
|
||||
nome_serventuario_praticou_ato: str
|
||||
data_solicitacao: datetime
|
||||
ip_maquina: Optional[IPv4Address | IPv6Address] = None
|
||||
inteiro_teor: str
|
||||
valor_entrada: Optional[Decimal] = None
|
||||
emolumento: Decimal
|
||||
taxa_judiciaria: Decimal
|
||||
fundos_estaduais: Decimal
|
||||
protocolo_protesto: Optional[str] = None
|
||||
protocolo_imovel: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Schema de resposta (GET) — inclui metadata
|
||||
# ----------------------------------------------------
|
||||
class AtoPrincipalResponseSchema(AtoPrincipalBaseSchema):
|
||||
ato_principal_id: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Schema de criação (POST)
|
||||
# ----------------------------------------------------
|
||||
class AtoPrincipalCreateSchema(AtoPrincipalBaseSchema):
|
||||
pass
|
||||
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Schema de atualização (PUT)
|
||||
# ----------------------------------------------------
|
||||
class AtoPrincipalUpdateSchema(AtoPrincipalBaseSchema):
|
||||
ato_principal_id: int
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
from fastapi import HTTPException, status
|
||||
|
||||
from packages.v1.administrativo.actions.ato_principal.ato_principal_index_action import AtoPrincipalIndexAction
|
||||
|
||||
|
||||
class AtoPrincipalIndexService:
|
||||
"""
|
||||
Serviço responsável por encapsular a lógica de negócio para a operação
|
||||
de listagem de registros na tabela G_GRAMATICA.
|
||||
"""
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
Executa a operação de busca de todos os registros no banco de dados.
|
||||
|
||||
Args:
|
||||
g_cartorio_index_schema (GCartorioIndexSchema):
|
||||
Esquema que pode conter filtros ou parâmetros de busca.
|
||||
|
||||
Returns:
|
||||
A lista de registros encontrados.
|
||||
"""
|
||||
# ----------------------------------------------------
|
||||
# Instanciamento da ação
|
||||
# ----------------------------------------------------
|
||||
ato_principal_index_action = AtoPrincipalIndexAction()
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Execução da ação
|
||||
# ----------------------------------------------------
|
||||
data = ato_principal_index_action.execute()
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Verificação de retorno
|
||||
# ----------------------------------------------------
|
||||
if not data:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Não foi possível localizar registros de G_GRAMATICA."
|
||||
)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Retorno da informação
|
||||
# ----------------------------------------------------
|
||||
return data
|
||||
13
packages/v1/api.py
Normal file
13
packages/v1/api.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Importa o gerenciador de rotas do FastAPI
|
||||
from fastapi import APIRouter
|
||||
|
||||
# Importa os módulos de rotas específicos
|
||||
from packages.v1.administrativo.endpoints import ato_principal_endpoint
|
||||
|
||||
# Cria uma instância do APIRouter que vai agregar todas as rotas da API
|
||||
api_router = APIRouter()
|
||||
|
||||
# Inclui as rotas de g_cartorio
|
||||
api_router.include_router(
|
||||
ato_principal_endpoint.router, prefix="/ato", tags=["Dados do Ato"]
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
from abstracts.repository import BaseRepository
|
||||
|
||||
|
||||
class FirebirdCheckAction(BaseRepository):
|
||||
|
||||
def execute(self):
|
||||
|
||||
# Montagem do SQL
|
||||
sql = """ SELECT 1 FROM RDB$DATABASE """
|
||||
|
||||
# Execução do sql
|
||||
response = self.fetch_one(sql)
|
||||
|
||||
if response:
|
||||
|
||||
# Dados
|
||||
response = {
|
||||
"status" : "Banco de dados acessível"
|
||||
}
|
||||
|
||||
# Retorna os dados localizados
|
||||
return response
|
||||
24
packages/v1/system/actions/disk/get_size_action.py
Normal file
24
packages/v1/system/actions/disk/get_size_action.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import shutil
|
||||
|
||||
from abstracts.action import BaseAction
|
||||
|
||||
|
||||
class GetSizeAction:
|
||||
|
||||
def execute(self):
|
||||
|
||||
# Verificar espaço em disco
|
||||
total, used, free = shutil.disk_usage("/")
|
||||
|
||||
# Converter de bytes para gigabytes
|
||||
total_gb = total / (1024 ** 3)
|
||||
used_gb = used / (1024 ** 3)
|
||||
free_gb = free / (1024 ** 3)
|
||||
|
||||
return {
|
||||
"total" : round(total_gb, 2),
|
||||
"used" : round(used_gb, 2),
|
||||
"free" : round(free_gb, 2)
|
||||
}
|
||||
|
||||
|
||||
12
packages/v1/system/service/startup_check_service.py
Normal file
12
packages/v1/system/service/startup_check_service.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from packages.v1.system.actions.disk.get_size_action import GetSizeAction
|
||||
|
||||
|
||||
class StartupCheckService:
|
||||
|
||||
def execute(self):
|
||||
|
||||
get_size_action = GetSizeAction()
|
||||
get_size_action_result = get_size_action.execute()
|
||||
|
||||
return get_size_action_result
|
||||
|
||||
34
requirements.txt
Normal file
34
requirements.txt
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
annotated-types==0.7.0
|
||||
anyio==4.10.0
|
||||
bcrypt==3.1.7
|
||||
cffi==1.17.1
|
||||
click==8.2.1
|
||||
colorama==0.4.6
|
||||
dnspython==2.7.0
|
||||
ecdsa==0.19.1
|
||||
email_validator==2.2.0
|
||||
fastapi==0.116.1
|
||||
firebird-base==2.0.2
|
||||
firebird-driver==2.0.2
|
||||
greenlet==3.2.4
|
||||
h11==0.16.0
|
||||
idna==3.10
|
||||
packaging==25.0
|
||||
passlib==1.7.4
|
||||
protobuf==5.29.5
|
||||
pyasn1==0.6.1
|
||||
pycparser==2.22
|
||||
pydantic==2.11.7
|
||||
pydantic_core==2.33.2
|
||||
python-dateutil==2.9.0.post0
|
||||
python-jose==3.5.0
|
||||
pytz==2025.2
|
||||
rsa==4.9.1
|
||||
six==1.17.0
|
||||
sniffio==1.3.1
|
||||
SQLAlchemy==2.0.42
|
||||
sqlalchemy-firebird==2.1
|
||||
starlette==0.47.2
|
||||
typing-inspection==0.4.1
|
||||
typing_extensions==4.14.1
|
||||
uvicorn==0.35.0
|
||||
Loading…
Add table
Reference in a new issue