[BE-01] feat: Reestruturação da api: 1) Implementação do JWT. 2) Implementação de Configs

This commit is contained in:
Keven Willian 2025-07-26 15:05:26 -03:00
parent a3492cd245
commit fd4c5aecff
87 changed files with 1371 additions and 756 deletions

View file

@ -1,26 +0,0 @@
# 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 ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Binary file not shown.

View file

@ -1,26 +1,150 @@
# 🧾 MyDocs - Gerador de Documentos
# 🧩 Projeto [SAAS]
Aplicação web para gerenciamento de empresas e geração automatizada de documentos com base em **minutas personalizadas** e **informações cadastradas**.
Este projeto é uma **API monolítica modular**, onde cada módulo representa um **domínio específico** da aplicação. Internamente, cada módulo é construído sobre a **Arquitetura Hexagonal**, promovendo separação de responsabilidades, testabilidade e independência da infraestrutura.
## 🚀 Funcionalidades
---
### ✅ Concluídas:
- **Autenticação de Usuário**
- Sistema de login seguro
- **CRUD de Empresas**
- Cadastro, edição, visualização e exclusão de empresas
## ⚙️ Visão Geral da Arquitetura
### 🛠️ Em Desenvolvimento:
- **Gerador de Documentos**
- Geração dinâmica de documentos com base em minutas e dados de empresas
- **Cadastro de Minutas**
- Sistema para criação e gerenciamento de templates/minutas de documentos
- **Cadastro de Produtos**
- Cadastro de produtos vinculados a empresas ou documentos
### 🏗️ Estrutura Monolítica Modular
## 🧩 Tecnologias Utilizadas
- Modelo **monolítico**, modularizado por **domínios**
- Cada domínio organizado dentro do diretório `packages/`
- Componentes por domínio:
- **Controllers** (entrada)
- **Endpoints** (rotas)
- **Schemas** (validação e transformação)
- **Actions** (orquestração de lógica)
- **Services** (casos de uso)
- **Repositories** (acesso a dados)
- **Backend:** Python / FastApi
- **Frontend:** React / NextJs
- **Banco de Dados:** MySQL
- **Autenticação:** JWT
### 🧭 Arquitetura Hexagonal por Módulo
```text
/<domínio>
/actions # Orquestra lógica entre services, schemas e repositories
/controllers # Interface entre endpoints e actions
/endpoints # Define as rotas da API
/repositories # Acesso ao Firebird via SQLAlchemy
/schemas # Entrada e saída de dados
/services # Casos de uso
```
---
## 🛠️ Tecnologias Utilizadas
- **Linguagem:** Python 3.11+
- **ORM:** SQLAlchemy
- **Banco de Dados:** Firebird
- **Driver:** fdb
- **Arquitetura:** Hexagonal por módulo
- **Organização:** Modular por domínio
---
## 🗄️ Banco de Dados
O projeto utiliza **Firebird** como banco principal.
**Arquivo de configuração:**
```text
Api/config/database/firebird.json
```
**Exemplo:**
```json
{
"host": "localhost",
"port": 3050,
"database": "/caminho/para/database.fdb",
"user": "SYSDBA",
"password": "masterkey"
}
```
**Classe de conexão:**
```text
Api/core/connections/firebird.py
```
---
## 🧠 SQLAlchemy com Queries Manuais
Utilizamos SQLAlchemy para:
- Gerenciar conexões
- Preencher parâmetros em queries nativas
**Exemplo:**
```python
sql = "SELECT * FROM CLIENTES WHERE ID = :id"
params = {"id": 123}
result = session.execute(text(sql), params).fetchall()
```
---
## 🗂️ Estrutura de Diretórios
```text
Api/
├── api/
│ └── v1/
│ ├── packages/
│ │ └── administrative/
│ │ ├── actions/
│ │ ├── controllers/
│ │ ├── endpoints/
│ │ ├── repositories/
│ │ ├── schemas/
│ │ └── services/
│ └── api.py
├── config/
│ └── database/firebird.json
├── core/
│ ├── base/
│ ├── connections/
│ ├── system/
│ ├── utils/
│ └── auth.py
```
---
## ▶️ Executando a API
```bash
# Criar ambiente virtual
python -m venv .venv
# Ativar ambiente virtual
source .venv/bin/activate # Linux/macOS
.venv\Scripts\activate # Windows
# Instalar dependências
pip install -r requirements.txt
# Executar a API
uvicorn api.v1.api:app --reload
```
---
## 📌 Observações
- Novos domínios devem seguir a estrutura modular.
- A arquitetura hexagonal facilita manutenção e futura extração para microsserviços.
- A separação entre actions, services e repositories melhora a organização e testabilidade.
---
## 👨‍💻 Autor
Desenvolvido por **Orius Tecnologia**
GitHub / LinkedIn: [seu-link](#)

View file

@ -1,5 +1,6 @@
from abc import ABC, abstractmethod
class BaseAction:
"""

View file

@ -0,0 +1,50 @@
from typing import Literal, Optional
from sqlalchemy import text
from sqlalchemy.exc import SQLAlchemyError
from database.firebird import Firebird
class BaseRepository:
def query(self, sql: str, params: Optional[dict] = None):
"""Executa uma consulta SQL e retorna o resultado como objeto ResultProxy"""
return self._execute(sql, params, fetch="result")
def fetch_all(self, sql: str, params: Optional[dict] = None):
"""Executa uma consulta SQL e retorna todos os registros com mapeamento de colunas"""
return self._execute(sql, params, fetch="all")
def fetch_one(self, sql: str, params: Optional[dict] = None):
"""Executa uma consulta SQL e retorna o primeiro registro com mapeamento de colunas"""
return self._execute(sql, params, fetch="one")
def run(self, sql: str, params: Optional[dict] = None):
"""Executa um SQL sem retorno de dados (ex: INSERT, UPDATE, DELETE)"""
return self._execute(sql, params, fetch="none")
def run_and_return(self, sql: str, params: Optional[dict] = None):
"""Executa SQL com RETURNING e retorna o primeiro registro como dict"""
return self._execute(sql, params, fetch="one")
def _execute(
self,
sql: str,
params: Optional[dict] = None,
fetch: Literal["all", "one", "result", "none"] = "result",
):
engine = Firebird.get_engine()
try:
with engine.begin() as conn:
result = conn.execute(text(sql), params or {})
if fetch == "all":
return result.mappings().all()
elif fetch == "one":
return result.mappings().first()
elif fetch == "none":
return None
return result # Result object
except SQLAlchemyError as e:
print(f"[ERRO SQL]: {e}")
raise

View file

@ -1,17 +1,17 @@
import json
from types import SimpleNamespace
from pathlib import Path
from types import SimpleNamespace
class Config:
@staticmethod
def get():
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' / 'database.json'
config_path = base_dir.parent.parent / 'config' / name
# Carrega o JSON como objeto acessível por ponto
with open(config_path, 'r') as f:

View file

@ -0,0 +1,28 @@
import importlib
from actions.config.config import Config
class DynamicImport:
def __init__(self):
self.config = Config.get('app.json')
self.base = 'packages.v1'
def set_package(self, name):
self.package = name
def set_table(self, table):
self.table = table
def service(self, name: str, class_name : str):
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
Api/actions/file/file.py Normal file
View 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}")

View 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)

View 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']

View 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
Api/actions/log/log.py Normal file
View 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}")

View file

@ -1,42 +1,86 @@
# handlers.py
import json
import traceback
from fastapi import Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from starlette.exceptions import HTTPException as StarletteHTTPException
from core.system.exceptions import BusinessRuleException
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={"error": "Regra de negócio", "detail": exc.message}
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={"error": "HTTP Error", "detail": exc.detail}
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={"error": "Erro de validação", "detail": exc.errors()}
content=response
)
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
return JSONResponse(
status_code=500,
content={
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
)

View file

@ -1,5 +1,6 @@
import re
class CNPJ:
@staticmethod

View file

@ -1,5 +1,6 @@
import re
class Email:
@staticmethod

View file

@ -1,5 +1,6 @@
import re
import html
import re
class Text:

View file

@ -1,13 +0,0 @@
from core.base.base_action import BaseAction
from api.v1.packages.administrative.repositories.c_caixa_item.index import Index
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSearchSchema
class IndexAction(BaseAction):
def execute(self, search : CaixaItemSearchSchema):
# Instânciamento de repositório
index = Index()
# Retorna todos produtos
return index.execute(search)

View file

@ -1,48 +0,0 @@
# Importação de bibliotecas
from core.utils.dynamic_import import DynamicImport
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSearchSchema
class CCaixaItemController:
def index(self, search : CaixaItemSearchSchema):
# Importação da classe desejad
indexService = DynamicImport.service("administrative", "c_caixa_item", "index_service", "IndexService")
# Intânciamento da classe service
self.indexService = indexService()
# Lista todos os produtos
return self.indexService.execute(search)
def create(self, caixa_item_schema: CaixaItemSchema):
# Importação da classe desejada
createService = DynamicImport.service("administrative", "c_caixa_item", "save_service", "SaveService")
# Intânciamento da classe service
self.createService = createService()
# Lista todos os produtos
return self.createService.execute(caixa_item_schema)
def show(self, caixa_item_schema: CaixaItemSchema):
# Importação da classe desejad
showService = DynamicImport.service("administrative", "c_caixa_item", "show_service", "ShowService")
# Intânciamento da classe service
self.showService = showService()
# Lista todos os produtos
return self.showService.execute(caixa_item_schema)
def delete(self, caixa_item_schema: CaixaItemSchema):
# Importação da classe desejad
deleteService = DynamicImport.service("administrative", "c_caixa_item", "delete_service", "DeleteService")
# Intânciamento da classe service
self.deleteService = deleteService()
# Lista todos os produtos
return self.deleteService.execute(caixa_item_schema)

View file

@ -1,14 +0,0 @@
from core.base.base_repository import BaseRepository
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
class Delete(BaseRepository):
def execute(self, caixa_item : CaixaItemSchema):
# Realiza a remoção
self.cursor.execute(""" DELETE FROM c_caixa_item cci WHERE cci.caixa_item_id = ?""", (caixa_item.caixa_item_id,))
# Comita a transação
self.commit()
return True

View file

@ -1,57 +0,0 @@
from core.base.base_repository import BaseRepository
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSearchSchema
from pydantic import BaseModel
from typing import Optional
class Index(BaseRepository):
def execute(self, search: Optional[CaixaItemSearchSchema]):
print("### Iniciando método execute ###")
if search is None:
print("⚠️ search é None — retornando lista vazia")
return []
where_clauses = []
params = []
for campo, intervalo in search.__dict__.items():
print(f"🔍 Campo: {campo}, Valor: {intervalo}, Tipo: {type(intervalo)}")
if isinstance(intervalo, BaseModel):
date_start = getattr(intervalo, "date_start", None)
date_end = getattr(intervalo, "date_end", None)
if date_start and date_end:
where_clauses.append(f"cci.{campo} BETWEEN ? AND ?")
params.extend([date_start, date_end])
elif date_start:
where_clauses.append(f"cci.{campo} >= ?")
params.append(date_start)
elif date_end:
where_clauses.append(f"cci.{campo} <= ?")
params.append(date_end)
# Montagem da SQL
sql = """
SELECT FIRST 100 *
FROM c_caixa_item cci \
"""
if where_clauses:
sql += " WHERE " + " AND ".join(where_clauses)
sql += " ORDER BY caixa_item_id DESC"
print("✅ SQL Final:", sql)
print("📦 Params:", params)
# Executa a query
self.cursor.execute(sql, params)
columns = [col[0] for col in self.cursor.description]
results = [
{columns[i]: row[i] for i in range(len(columns))}
for row in self.cursor.fetchall()
]
self.commit()
return results

View file

@ -1,21 +0,0 @@
from core.base.base_repository import BaseRepository
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
class Show(BaseRepository):
def execute(self, caixa_item_schema: CaixaItemSchema):
# Executa a busca pelo ID usando placeholder correto (?)
self.cursor.execute(
"""SELECT * FROM c_caixa_item cci WHERE cci.caixa_item_id = ?""",
(caixa_item_schema.caixa_item_id,) # Importante: precisa ser tupla!
)
row = self.cursor.fetchone()
if not row:
return None
# Transforma em dict associativo
columns = [desc[0].lower() for desc in self.cursor.description]
return dict(zip(columns, row))

View file

@ -1,12 +0,0 @@
from api.v1.packages.administrative.actions.c_caixa_item.index_action import IndexAction
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSearchSchema
class IndexService:
def execute(self, search : CaixaItemSearchSchema):
# Instânciamento de ações
indexAction = IndexAction()
# Retorna todos produtos desejados
return indexAction.execute(search)

View file

@ -1,19 +0,0 @@
from api.v1.packages.sequencia.schemas.g_sequencia import GSequenciaSchema
from core.base.base_repository import BaseRepository
class Get(BaseRepository):
def execute(self, sequencia_schema : GSequenciaSchema):
self.cursor.execute(""" SELECT * FROM G_SEQUENCIA gs WHERE gs.TABELA LIKE ? """,
(sequencia_schema.tabela,))
row = self.cursor.fetchone()
if not row:
return None
# Transforma em dict associativo
columns = [desc[0].lower() for desc in self.cursor.description]
return dict(zip(columns, row))

View file

@ -1,16 +0,0 @@
from api.v1.packages.sequencia.schemas.g_sequencia import GSequenciaSchema
from core.base.base_repository import BaseRepository
class Save(BaseRepository):
def execute(self, sequencia_schema : GSequenciaSchema):
self.cursor.execute(""" UPDATE G_SEQUENCIA SET SEQUENCIA = ? WHERE TABELA LIKE ? """,
(sequencia_schema.sequencia, sequencia_schema.tabela))
# Comita a transação
self.commit()
# Retorna como verdadeiro se for salvo com sucesso
return sequencia_schema

24
Api/config/app.json Normal file
View 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
}
}
}

View file

@ -1,10 +0,0 @@
{
"firebird": {
"host": "localhost",
"name": "C:/Users/keven/OneDrive/Desktop/Orius/CAIAPONIA.FDB",
"port": 3050,
"user": "SYSDBA",
"password": "302b3c",
"charset": "UTF8"
}
}

View file

@ -0,0 +1,13 @@
{
"host": "localhost",
"name": "C:/Users/keven/OneDrive/Desktop/Orius/CAIAPONIA.FDB",
"port": 3050,
"user": "SYSDBA",
"password": "302b3c",
"charset": "UTF8",
"pool" : {
"pre_ping" : "True",
"size" : 5,
"max_overflow" :10
}
}

View file

@ -1,34 +0,0 @@
from pytz import timezone
from datetime import datetime, timedelta
from fastapi.security import OAuth2PasswordBearer
from jose import jwt
from core.configs import settings
# Define o esquema OAuth2 para login
oauth2_schema = OAuth2PasswordBearer(
tokenUrl=f"{settings.API_V1_STR}/usuarios/login"
)
# Função para criar um token JWT
def create_token(tipo_token: str, tempo_vida: timedelta, sub: str) -> str:
payload = {}
sp = timezone('America/Sao_Paulo')
expira = datetime.now(tz=sp) + tempo_vida
payload["type"] = tipo_token
payload["exp"] = expira
payload["iat"] = datetime.now(tz=sp)
payload["sub"] = str(sub)
return jwt.encode(payload, settings.JWT_SECRET, algorithm=settings.ALGORITHM)
# Criação do token de acesso (access_token)
def create_access_token(sub: str) -> str:
return create_token(
tipo_token='access_token',
tempo_vida=timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES),
sub=sub
)

View file

@ -1,43 +0,0 @@
from abc import ABC, abstractmethod
from core.database import get_connection
class BaseRepository(ABC):
"""
Classe abstrata base para todos os repositórios do sistema.
Fornece conexão com o banco de dados e obriga implementação de um método execute().
"""
def __init__(self):
"""
Inicializa a conexão e o cursor do banco de dados.
Essa conexão deve ser usada pelas subclasses.
"""
self.conn = get_connection()
self.cursor = self.conn.cursor()
@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
def commit(self):
"""
Realiza o commit da transação.
"""
self.conn.commit()
def rollback(self):
"""
Realiza o rollback da transação.
"""
self.conn.rollback()
def close(self):
"""
Fecha cursor e conexão.
"""
self.cursor.close()
self.conn.close()

View file

@ -1,74 +0,0 @@
from abc import ABC, abstractmethod
from core.connections.firebird_4 import Firebird4
import traceback
class BaseRepositoryAlchemy(ABC):
"""
Classe base para repositórios Firebird com SQL puro.
Suporte a bind params nomeados (ex: :id), simula uso estilo PDO.
"""
def __init__(self):
self.conn = Firebird4().connect()
self.cursor = self.conn.cursor()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, tb):
if exc_type:
print("⚠️ Erro durante execução do repositório:")
print(f"Tipo: {exc_type.__name__}")
print(f"Erro: {exc_value}")
print("Traceback:")
traceback.print_tb(tb)
self.rollback()
else:
self.commit()
self.close()
@abstractmethod
def execute(self, *args, **kwargs):
pass
def commit(self):
if self.conn:
try:
self.conn.commit()
except Exception as e:
print(f"❌ Erro ao fazer commit: {e}")
self.rollback()
def rollback(self):
if self.conn:
try:
self.conn.rollback()
except Exception as e:
print(f"❌ Erro ao fazer rollback: {e}")
def close(self):
try:
if self.cursor and not self.cursor.closed:
self.cursor.close()
except Exception as e:
print(f"❌ Erro ao fechar cursor: {e}")
try:
if self.conn and not self.conn.closed:
self.conn.close()
except Exception as e:
print(f"❌ Erro ao fechar conexão: {e}")
def fetch_one(self, query: str, params: dict = {}):
self.cursor.execute(query, params)
row = self.cursor.fetchone()
return self._row_to_dict(row) if row else None
def fetch_all(self, query: str, params: dict = {}):
self.cursor.execute(query, params)
return [self._row_to_dict(row) for row in self.cursor.fetchall()]
def execute_query(self, query: str, params: dict = {}):
self.cursor.execute(query, params)
def _row_to_dict(self, row):
return {desc[0]: value for desc, value in zip(self.cursor.description, row)}

View file

@ -1,26 +0,0 @@
from pydantic_settings import BaseSettings
# Classe de configurações globais da aplicação
class Settings(BaseSettings):
# Prefixo base das rotas da API
API_V1_STR: str = '/api/v1'
# URL de conexão com o banco MySQL
# Formato: mysql://usuario:senha@host:porta/banco
DB_URL: str = "mysql://root:root@127.0.0.1:3306/mydocs"
# Chave secreta para geração dos tokens JWT
JWT_SECRET: str = 'WYe1zwtlDkh39_X3X3qTSICFDxts4VQrMyGLxnEpGUg'
# Algoritmo usado para assinar o token
ALGORITHM: str = 'HS256'
# Tempo de expiração do token JWT (em minutos): 1 semana
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7
# Configuração interna do Pydantic
class Config:
case_sensitive = True # Respeita letras maiúsculas/minúsculas nas variáveis de ambiente
# Instância única das configurações
settings: Settings = Settings()

View file

@ -1,22 +0,0 @@
import fdb
from core.utils.config import Config
def get_connection():
"""
Constrói e retorna uma conexão com o banco MySQL
utilizando os dados da URL definida nas configurações.
"""
# Obtem as configurações de banco de dados
database = Config.get()
# Constrói o DSN no formato 'hostname/port:database_path'
# E essa string é passada como o PRIMEIRO ARGUMENTO POSICIONAL
connection_dsn = f"{database.firebird.host}/{database.firebird.port}:{database.firebird.name}"
return fdb.connect(
connection_dsn, # Este é o DSN completo que o driver espera
user=database.firebird.user,
password=database.firebird.password,
charset=database.firebird.charset
)

View file

@ -1,28 +0,0 @@
from sqlalchemy import create_engine, text
from core.utils.config import Config
class Firebird4:
def connect(self):
"""
Constrói e retorna uma conexão com o banco Firebird
utilizando os dados da URL definida nas configurações.
"""
# Obtem as configurações de banco de dados
database = Config.get()
# Caminho da conexão com o banco de dados
dsn = (
f"firebird://{database.firebird.user}:"
f"{database.firebird.password}@"
f"{database.firebird.host}:"
f"{database.firebird.port}/"
f"{database.firebird.name}"
)
# Retorna a conexão com o banco
return create_engine(
dsn,
echo=False, # Não exibe as query nos logs
)

View file

@ -1,22 +0,0 @@
import fdb
from core.utils.config import Config
def get_connection():
"""
Constrói e retorna uma conexão com o banco MySQL
utilizando os dados da URL definida nas configurações.
"""
# Obtem as configurações de banco de dados
database = Config.get()
# Constrói o DSN no formato 'hostname/port:database_path'
# E essa string é passada como o PRIMEIRO ARGUMENTO POSICIONAL
connection_dsn = f"{database.firebird.host}/{database.firebird.port}:{database.firebird.name}"
return fdb.connect(
connection_dsn, # Este é o DSN completo que o driver espera
user=database.firebird.user,
password=database.firebird.password,
charset=database.firebird.charset
)

View file

@ -1,64 +0,0 @@
# core/deps.py
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import jwt, JWTError
from core.configs import settings
from api.v1.packages.users.models.users.users_model import UserModel # <--- Importe o UserModel
# Define o esquema de segurança OAuth2 (token tipo Bearer)
oauth2_schema = OAuth2PasswordBearer(
tokenUrl=f"{settings.API_V1_STR}/usuarios/login"
)
# Função que retorna o usuário autenticado com base no token JWT
def get_current_user(token: str = Depends(oauth2_schema)) -> dict:
credential_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail='Could not validate credentials',
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(
token,
settings.JWT_SECRET,
algorithms=[settings.ALGORITHM],
options={"verify_aud": False}
)
user_id: str = payload.get("sub")
if user_id is None:
raise credential_exception
except JWTError:
raise credential_exception
# --- NOVO: Buscar os dados completos do usuário do banco de dados ---
# Convert user_id para int, se ele for um string no JWT e int no banco
try:
user_id_int = int(user_id)
except ValueError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid user ID format in token."
)
# Use o UserModel para buscar os dados completos
# Adicione um try-except para a chamada ao modelo para capturar erros de DB
try:
user = UserModel.get_by_id(user_id_int)
except Exception as e: # Captura qualquer erro ao buscar no DB
print(f"Error fetching user in get_current_user: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to retrieve user data. {str(e)}"
)
if not user:
# Se o usuário não for encontrado no DB (mas o token era válido para um ID),
# pode indicar um usuário deletado ou um ID inválido no token.
raise credential_exception # Ou HTTPException(404, "User associated with token not found")
return user # Retorna o dicionário completo do usuário

View file

@ -1,31 +0,0 @@
# core/security.py
from passlib.context import CryptContext # Contexto de criptografia de senhas
from passlib.exc import UnknownHashError # Exceção para hashes não reconhecidos
# Define contexto de criptografia com esquema bcrypt
CRYPTO = CryptContext(schemes=['bcrypt'], deprecated='auto')
def verify_senha_api(plain_senha_api: str, hashed_senha_api: str) -> bool:
"""
Compara a senha fornecida em texto puro com o hash armazenado.
Retorna False em caso de erro ou formato inválido.
"""
try:
if not plain_senha_api or not hashed_senha_api:
return False # Garante que nenhum dos valores seja nulo ou vazio
# Verifica se a senha corresponde ao hash
return CRYPTO.verify(plain_senha_api, hashed_senha_api)
except UnknownHashError:
return False # Hash inválido ou não reconhecido pelo passlib
except Exception:
return False # Falha genérica na verificação
def hash_senha_api(plain_senha_api: str) -> str:
"""
Gera o hash da senha em texto puro.
"""
return CRYPTO.hash(plain_senha_api) # Retorna a senha criptografada com bcrypt

View file

@ -1,17 +0,0 @@
import importlib
class DynamicImport:
state = "go"
base = "api.v1.packages"
@staticmethod
def service(package: str, table: str, name: str, class_name : str):
try:
module_file = f"{name}"
path = f"{DynamicImport.base}.{package}.services.{table}.{DynamicImport.state}.{module_file}"
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}")

View file

@ -1,31 +0,0 @@
# utils/validation.py
import re
import html
class InputSanitizer:
@staticmethod
def clean_text(text: str) -> str:
"""Trim spaces, escape HTML entities, collapse multiple spaces."""
text = text.strip()
text = html.escape(text)
text = re.sub(r"\s+", " ", text)
return text
@staticmethod
def is_valid_email(email: str) -> bool:
"""Check if email has a valid structure"""
return bool(re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", email))
@staticmethod
def has_script(text: str) -> bool:
"""Detect basic XSS attempts"""
return "<script" in text.lower() or "javascript:" in text.lower()
@staticmethod
def is_safe(text: str) -> bool:
"""Detect common XSS/SQL injection characters or patterns"""
blacklist = ["<script", "javascript:", "--", ";", "/*", "*/", "@@", "char(", "nchar(", "varchar(", "alter", "drop", "exec"]
text_lower = text.lower()
return not any(p in text_lower for p in blacklist)

55
Api/database/firebird.py Normal file
View file

@ -0,0 +1,55 @@
# core/database/connection_manager.py
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 de banco de dados
database = Config.get('database/firebird.json')
# Se nao existir engine, cria uma nova
if cls._engine is None:
# Caminho da conexão com o banco de dados
dsn = (
f"firebird://{database.user}:"
f"{database.password}@"
f"{database.host}:"
f"{database.port}/"
f"{database.name}"
)
# Criação da Engine
cls._engine = create_engine(
dsn,
connect_args={
"charset": database.charset,
},
pool_pre_ping=database.pool.pre_ping,
pool_size=database.pool.size,
max_overflow=database.pool.max_overflow
)
# Retorna a criação da engine
return cls._engine
@classmethod
def dispose(cls):
# Verifica se existe engine
if cls._engine:
# Se existir encerra a conexão
cls._engine.dispose()
# Anula a engine
cls._engine = None

View file

@ -1,28 +1,32 @@
# main.py
# Ajuste para garantir que o diretório base do projeto seja incluído no PYTHONPATH
import sys
import os
import sys
# Adiciona o diretório atual (onde está o main.py) ao sys.path
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
# Importa a classe principal do FastAPI
from fastapi import FastAPI
from core.system.handlers import register_exception_handlers
from fastapi import FastAPI, Request
from pathlib import Path
# Importa o middleware de CORS
from fastapi.middleware.cors import CORSMiddleware
# Importa as configurações globais da aplicação
from core.configs import settings
from fastapi.responses import Response
from starlette.middleware.base import BaseHTTPMiddleware
# Importa o roteador principal da API versão 1
from api.v1.api import api_router
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 core.configs import settings
from actions.log.log import Log
from actions.config.config import Config
from actions.system.handlers import register_exception_handlers
# Instancia o app FastAPI com um título personalizado
app = FastAPI(title='API Mydocs')
app = FastAPI(title='SAAS Orius')
# Controle de erros personalizados
register_exception_handlers(app)
# Adiciona o middleware de CORS
@ -34,6 +38,40 @@ app.add_middleware(
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=settings.API_V1_STR)

0
Api/packages/__init__.py Normal file
View file

View file

@ -1,6 +1,9 @@
from core.base.base_action import BaseAction
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
from api.v1.packages.administrative.repositories.c_caixa_item.delete import Delete
from packages.v1.administrativo.repositories.c_caixa_item.delete import \
Delete
from packages.v1.administrativo.schemas.c_caixa_item_schema import \
CaixaItemSchema
from abstracts.action import BaseAction
class DeleteAction(BaseAction):

View file

@ -0,0 +1,16 @@
from packages.v1.administrativo.repositories.c_caixa_item.index import \
Index
from packages.v1.administrativo.schemas.c_caixa_item_schema import \
CaixaItemSearchSchema
from abstracts.action import BaseAction
class IndexAction(BaseAction):
def execute(self):
# Instânciamento de repositório
index = Index()
# Retorna todos produtos
return index.execute()

View file

@ -1,6 +1,8 @@
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
from api.v1.packages.administrative.repositories.c_caixa_item.save import Save
from core.base.base_action import BaseAction
from packages.v1.administrativo.repositories.c_caixa_item.save import Save
from packages.v1.administrativo.schemas.c_caixa_item_schema import \
CaixaItemSchema
from abstracts.action import BaseAction
class SaveAction(BaseAction):

View file

@ -1,6 +1,8 @@
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
from api.v1.packages.administrative.repositories.c_caixa_item.show import Show
from core.base.base_action import BaseAction
from packages.v1.administrativo.repositories.c_caixa_item.show import Show
from packages.v1.administrativo.schemas.c_caixa_item_schema import \
CaixaItemSchema
from abstracts.action import BaseAction
class ShowAction(BaseAction):

View file

@ -0,0 +1,14 @@
from abstracts.action import BaseAction
from packages.v1.administrativo.schemas.g_usuario_schema import GUsuarioLoginSchema
from packages.v1.administrativo.repositories.g_usuario.get_by_login_repository import GetByLoginRepository
class GetByLoginAction(BaseAction):
def execute(self, g_usuario_login_schema : GUsuarioLoginSchema):
# Instânciamento do repositório de busca pelo login
get_by_login_repository = GetByLoginRepository()
# Execução do repositório
return get_by_login_repository.execute(g_usuario_login_schema)

View file

@ -0,0 +1,12 @@
from packages.v1.administrativo.schemas.g_usuario_schema import GUsuarioSchema
from packages.v1.administrativo.repositories.g_usuario.get_by_usuario_id_repository import GetByUsuarioIdRepository
class GetByUsuarioIdAction:
def execute(self, g_usuario_schema = GUsuarioSchema):
# Importação do repositório
get_by_usuario_id_repository = GetByUsuarioIdRepository()
# Execução do repositório
return get_by_usuario_id_repository.execute(g_usuario_schema)

View file

@ -0,0 +1,15 @@
from abstracts.action import BaseAction
from packages.v1.administrativo.repositories.g_usuario.index_repository import IndexRepository
class IndexAction(BaseAction):
def execute(self):
# Instânciamento do repositório sql
index_repository = IndexRepository()
# Execução do sql
response = index_repository.execute()
# Retorno da informação
return response

View file

@ -0,0 +1,69 @@
# Importação de bibliotecas
from packages.v1.administrativo.schemas.c_caixa_item_schema import (
CaixaItemSchema, CaixaItemSearchSchema)
from actions.dynamic_import.dynamic_import import DynamicImport
class CCaixaItemController:
def __init__(self):
# Classe responsável por carregar as services de acordo com o estado
self.dynamic_import = DynamicImport()
# Define o pacote que deve ser carregado
self.dynamic_import.set_package("administrativo")
# Define a tabela que o pacote pertence
self.dynamic_import.set_table("c_caixa_item")
def index(self):
# Importação da classe desejad
indexService = self.dynamic_import.service("index_service", "IndexService")
# Intânciamento da classe service
self.indexService = indexService()
# Lista todos os produtos
return {
'message' : 'Registros localizados com sucesso',
'data': self.indexService.execute()
}
def create(self, caixa_item_schema: CaixaItemSchema):
# Importação da classe desejada
createService = self.dynamic_import.service("save_service", "SaveService")
# Intânciamento da classe service
self.createService = createService()
# Lista todos os produtos
return {
'message' : 'Registros cadastrado com sucesso',
'data': self.createService.execute(caixa_item_schema)
}
def show(self, caixa_item_schema: CaixaItemSchema):
# Importação da classe desejad
showService = self.dynamic_import.service("show_service", "ShowService")
# Intânciamento da classe service
self.showService = showService()
# Lista todos os produtos
return {
'message' : 'Registro localizado com sucesso',
'data': self.showService.execute(caixa_item_schema)
}
def delete(self, caixa_item_schema: CaixaItemSchema):
# Importação da classe desejad
deleteService = self.dynamic_import.service("delete_service", "DeleteService")
# Intânciamento da classe service
self.deleteService = deleteService()
# Lista todos os produtos
return {
'message' : 'Registros removido com sucesso',
'data': self.deleteService.execute(caixa_item_schema)
}

View file

@ -0,0 +1,60 @@
from actions.dynamic_import.dynamic_import import DynamicImport
from packages.v1.administrativo.schemas.g_usuario_schema import (
GUsuarioSchema,
GUsuarioLoginSchema
)
class GUsuarioController:
def __init__(self):
# Action responsável por carregar as services de acodo com o estado
self.dynamic_import = DynamicImport()
# Define o pacote que deve ser carregado
self.dynamic_import.set_package("administrativo")
# Define a tabela que o pacote pertence
self.dynamic_import.set_table("g_usuario")
pass
def login(self, g_usuario_login_schema : GUsuarioLoginSchema):
# Importação de service de login
login_service = self.dynamic_import.service("login_service", "LoginService")
# Instânciamento da service
self.login_service = login_service()
# Retorna o usuário logado
return {
'message' : 'Usuário localizado com sucesso',
'data' : {
'token' : self.login_service.execute(g_usuario_login_schema)
}
}
def me(self, current_user):
# Importação de service de login
me_service = self.dynamic_import.service("me_service", "MeService")
# Instânciamento da service
self.me_service = me_service()
# Retorna o usuário logado
return {
'message' : 'Usuário localizado com sucesso',
'data' : self.me_service.execute(current_user)
}
def index(self):
# Importação da classe desejada
indexService = self.dynamic_import.service("index_service", "IndexService")
# Instânciamento da classe service
self.indexService = indexService()
# Lista todos os usuários
return {
'message': 'Usuários localizados com sucesso',
'data': self.indexService.execute()
}

View file

@ -1,9 +1,14 @@
# Importação de bibliotecas
from fastapi import APIRouter, status, Depends, Body
from typing import Optional
from api.v1.packages.administrative.controllers.c_caixa_item_controller import CCaixaItemController
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSearchSchema
from fastapi import APIRouter, Body, Depends, status
from actions.jwt.get_current_user import get_current_user
from packages.v1.administrativo.controllers.c_caixa_item_controller import \
CCaixaItemController
from packages.v1.administrativo.schemas.c_caixa_item_schema import (
CaixaItemSchema, CaixaItemSearchSchema)
# Inicializar o roteaodr para as rotas de produtos
router = APIRouter()
@ -15,31 +20,30 @@ cCaixaItemController = CCaixaItemController()
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(search : Optional[CaixaItemSchema] = Body(default=None)):
async def index(current_user : dict = Depends(get_current_user)):
# Busca todos os produtos cadastrados
response = cCaixaItemController.index(search)
response = cCaixaItemController.index()
# Retornar os dados localizados
return {
"data": response
}
return response
@router.post('/',
status_code=status.HTTP_201_CREATED,
summary="Cadastrar uma nova receita ou despesa no sistema",
response_description="Confirmação do cadastro da receita ou despesa, incluindo detalhes do item criado.")
async def save(caixa_item_schema: CaixaItemSchema):
async def save(caixa_item_schema: CaixaItemSchema, current_user : dict = Depends(get_current_user)):
# Salva o produto desejado
response = cCaixaItemController.create(caixa_item_schema)
# Retorna a informação desejada
return {
"data": response
}
return response
@router.get('/{caixa_item_id}', status_code=status.HTTP_200_OK)
async def show(caixa_item_id : int):
@router.get('/{caixa_item_id}',
status_code=status.HTTP_200_OK,
summary="Busca um registro em específico",
response_description="Busca um registro de acordo com o ID informado")
async def show(caixa_item_id : int, current_user : dict = Depends(get_current_user)):
# Armazena o produto id no Schema
CaixaItemSchema.caixa_item_id = caixa_item_id
@ -48,12 +52,13 @@ async def show(caixa_item_id : int):
response = cCaixaItemController.show(CaixaItemSchema)
# Retorna a informação desejada
return {
"data": response
}
return response
@router.delete('/{caixa_item_id}', status_code=status.HTTP_200_OK)
async def delete(caixa_item_id : int):
@router.delete('/{caixa_item_id}',
status_code=status.HTTP_200_OK,
summary="Remove um registro em específico",
response_description="Remove um registro de acordo com o ID informado")
async def delete(caixa_item_id : int, current_user : dict = Depends(get_current_user)):
# Armazena o produto id no Schema
CaixaItemSchema.caixa_item_id = caixa_item_id
@ -62,6 +67,4 @@ async def delete(caixa_item_id : int):
response = cCaixaItemController.delete(CaixaItemSchema)
# Retorna a informação desejada
return {
"data": response
}
return response

View file

@ -0,0 +1,52 @@
# Importação de bibliotecas
from typing import Optional
from fastapi import APIRouter, Body, Depends, status
from actions.jwt.get_current_user import get_current_user
from packages.v1.administrativo.controllers.g_usuario_controller import GUsuarioController
from packages.v1.administrativo.schemas.g_usuario_schema import (
GUsuarioSchema,
GUsuarioLoginSchema
)
# Inicializa o roteador para as rotas de usuário
router = APIRouter()
# Instãnciamento do controller desejado
g_usuario_controller = GUsuarioController()
@router.post('/login',
status_code=status.HTTP_200_OK,
summary='Cria o token de acesso do usuário',
response_description='Retorna o token de acesso do usuário')
async def index(g_usuario_login_schema : GUsuarioLoginSchema):
# Busca todos os usuários cadastrados
response = g_usuario_controller.login(g_usuario_login_schema)
# Retorna os dados localizados
return response
@router.get('/me')
async def me(current_user: dict = Depends(get_current_user)):
# Busca todos os usuários cadastrados
response = g_usuario_controller.me(current_user)
# Retorna os dados localizados
return response
@router.get('/',
status_code=status.HTTP_200_OK,
summary='Lista todos os usuário cadastrados',
response_description='Lista todos os usuário cadastrados')
async def index(current_user: dict = Depends(get_current_user)):
# Busca todos os usuários cadastrados
response = g_usuario_controller.index()
# Retorna os dados localizados
return response

View file

@ -0,0 +1,22 @@
from packages.v1.administrativo.schemas.c_caixa_item_schema import \
CaixaItemSchema
from Api.abstracts.repository import BaseRepository
class Delete(BaseRepository):
def execute(self, caixa_item : CaixaItemSchema):
# Montagem do sql
sql = """ DELETE FROM c_caixa_item cci WHERE cci.caixa_item_id = :caixaItemId """
# Preenchimento de parâmetros
params = {
"caixaItemId" : caixa_item.caixa_item_id
}
#Execução do sql
response = self.run(sql, params)
# Retorna o resultado
return response

View file

@ -0,0 +1,17 @@
from packages.v1.administrativo.schemas.c_caixa_item_schema import \
CaixaItemSearchSchema
from Api.abstracts.repository import BaseRepository
class Index(BaseRepository):
def execute(self):
# Montagem do SQL
sql = """ SELECT FIRST 10 * FROM c_caixa_item ORDER BY caixa_item_id DESC """
# Execução do sql
response = self.fetch_all(sql)
# Retorna os dados localizados
return response

View file

@ -1,15 +1,10 @@
# Importação de bibliotecas
from sqlalchemy import create_engine, text
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
from core.connections.firebird_4 import Firebird4
from packages.v1.administrativo.schemas.c_caixa_item_schema import \
CaixaItemSchema
from Api.abstracts.repository import BaseRepository
class Save():
def __init__(self):
firebird = Firebird4()
self.engine = firebird.connect()
class Save(BaseRepository):
def execute(self, caixa_item : CaixaItemSchema):
@ -71,9 +66,8 @@ class Save():
"registrado" : caixa_item.registrado
}
# Conexão e execução
with self.engine.begin() as conn: # garante commit automático
conn.execute(text(sql), params)
# Execução do sql
response = self.run(sql, params)
# Retorna os dados registrados
return caixa_item

View file

@ -0,0 +1,22 @@
from packages.v1.administrativo.schemas.c_caixa_item_schema import \
CaixaItemSchema
from Api.abstracts.repository import BaseRepository
class Show(BaseRepository):
def execute(self, caixa_item_schema: CaixaItemSchema):
# Montagem do SQL
sql = """ SELECT * FROM c_caixa_item cci WHERE cci.caixa_item_id = :caixaItemId """
# Preenchimento dos parâmetros
params = {
"caixaItemId" : caixa_item_schema.caixa_item_id
}
# Execução do sql
response = self.fetch_one(sql, params)
# Retorna a informação localizada
return response

View file

@ -0,0 +1,20 @@
from abstracts.repository import BaseRepository
from packages.v1.administrativo.schemas.g_usuario_schema import GUsuarioLoginSchema
class GetByLoginRepository(BaseRepository):
def execute(self, g_usuario_login_schema : GUsuarioLoginSchema):
# Montagem do sql
sql = """ SELECT FIRST 1 * FROM g_usuario gu WHERE gu.LOGIN LIKE :login"""
# Preenchimento dos parâmetros
params = {
"login" : g_usuario_login_schema.login
}
# Execução do sql
response = self.fetch_one(sql, params)
# Retorna os dados localizados
return response

View file

@ -0,0 +1,18 @@
from abstracts.repository import BaseRepository
from packages.v1.administrativo.schemas.g_usuario_schema import GUsuarioSchema
class GetByUsuarioIdRepository(BaseRepository):
def execute(self, g_usuario_schema = GUsuarioSchema):
# Define a consulta sql
sql = """ SELECT * FROM g_usuario gu WHERE gu.usuario_id = :usuarioId """
# Preenchimento dos parâmetros SQL
params = {
'usuarioId': g_usuario_schema.usuario_id
}
# Execução da instrução sql
return self.fetch_one(sql, params)

View file

@ -0,0 +1,14 @@
from abstracts.repository import BaseRepository
class IndexRepository(BaseRepository):
def execute(self):
# Montagem do sql
sql = """ SELECT * FROM g_usuario """
# Execução do sql
response = self.fetch_all(sql)
# Retorna os dados localizados
return response

View file

@ -1,6 +1,8 @@
from typing import Optional
from pydantic import BaseModel
from datetime import date, datetime
from typing import Optional
from pydantic import BaseModel
class CaixaItemSchema(BaseModel):
especie_pagamento: Optional[str] = None

View file

@ -0,0 +1,46 @@
from pydantic import BaseModel, EmailStr, constr
from typing import Optional
from datetime import datetime
class GUsuarioSchema(BaseModel):
usuario_id: Optional[int]
trocarsenha: Optional[bool] = False
login: Optional[str]
senha: Optional[str]
situacao: Optional[str]
nome_completo: str
funcao: Optional[str]
assina: Optional[bool] = False
sigla: Optional[str]
usuario_tab: Optional[str]
ultimo_login: Optional[datetime]
ultimo_login_regs: Optional[datetime]
data_expiracao: Optional[datetime]
senha_anterior: Optional[str]
andamento_padrao: Optional[str]
lembrete_pergunta: Optional[str]
lembrete_resposta: Optional[str]
andamento_padrao2: Optional[str]
receber_mensagem_arrolamento: Optional[bool] = False
email: Optional[EmailStr]
assina_certidao: Optional[bool] = False
receber_email_penhora: Optional[bool] = False
foto: Optional[str] # base64 ou caminho para imagem
nao_receber_chat_todos: Optional[bool] = False
pode_alterar_caixa: Optional[bool] = False
receber_chat_certidao_online: Optional[bool] = False
receber_chat_cancelamento: Optional[bool] = False
cpf: Optional[str]
somente_leitura: Optional[bool] = False
receber_chat_envio_onr: Optional[bool] = False
tipo_usuario: Optional[str]
class Config:
orm_mode = True
class GUsuarioLoginSchema(BaseModel):
login: Optional[str] = None
senha_api: Optional[str] = None
class GUsuarioMe(BaseModel):
usuario_id: int

View file

@ -1,7 +1,11 @@
from fastapi import HTTPException, status
from api.v1.packages.administrative.actions.c_caixa_item.delete_action import DeleteAction
from api.v1.packages.administrative.actions.c_caixa_item.show_action import ShowAction
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
from packages.v1.administrativo.actions.c_caixa_item.delete_action import \
DeleteAction
from packages.v1.administrativo.actions.c_caixa_item.show_action import \
ShowAction
from packages.v1.administrativo.schemas.c_caixa_item_schema import \
CaixaItemSchema
class DeleteService:

View file

@ -0,0 +1,15 @@
from packages.v1.administrativo.actions.c_caixa_item.index_action import \
IndexAction
from packages.v1.administrativo.schemas.c_caixa_item_schema import \
CaixaItemSearchSchema
class IndexService:
def execute(self):
# Instânciamento de ações
indexAction = IndexAction()
# Retorna todos produtos desejados
return indexAction.execute()

View file

@ -1,7 +1,10 @@
from api.v1.packages.administrative.actions.c_caixa_item.save_action import SaveAction
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
from api.v1.packages.sequencia.schemas.g_sequencia import GSequenciaSchema
from api.v1.packages.sequencia.services.g_sequencia.generate_service import GenerateService
from packages.v1.administrativo.actions.c_caixa_item.save_action import \
SaveAction
from packages.v1.administrativo.schemas.c_caixa_item_schema import \
CaixaItemSchema
from packages.v1.sequencia.schemas.g_sequencia import GSequenciaSchema
from packages.v1.sequencia.services.g_sequencia.generate_service import \
GenerateService
class SaveService:

View file

@ -1,6 +1,9 @@
from fastapi import HTTPException, status
from api.v1.packages.administrative.actions.c_caixa_item.show_action import ShowAction
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
from packages.v1.administrativo.actions.c_caixa_item.show_action import \
ShowAction
from packages.v1.administrativo.schemas.c_caixa_item_schema import \
CaixaItemSchema
class ShowService:

View file

@ -0,0 +1,24 @@
from fastapi import HTTPException, status
from packages.v1.administrativo.schemas.g_usuario_schema import GUsuarioSchema
from packages.v1.administrativo.actions.g_usuario.index_action import IndexAction
class IndexService:
def execute(self):
# Instânciamento de acções
index_action = IndexAction()
# Executa a busca de todas as ações
data = index_action.execute()
# Verifica se foi loalizado registros
if not data:
# Retorna uma exeção
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='Não foi possível localizar os usuários'
)
# Retorna as informações localizadas
return data

View file

@ -0,0 +1,43 @@
from fastapi import HTTPException, status
from actions.jwt.create_token import CreateToken
from packages.v1.administrativo.schemas.g_usuario_schema import GUsuarioLoginSchema
from packages.v1.administrativo.actions.g_usuario.get_by_login_action import GetByLoginAction
class LoginService:
def execute(self, g_usuario_login_schema : GUsuarioLoginSchema):
# Instânciamento da action de login
get_by_login_action = GetByLoginAction()
# Execução e retorno da action
get_by_login_result = get_by_login_action.execute(g_usuario_login_schema)
# Verifica se o usuário esta ativo
if not get_by_login_result.situacao == 'A':
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail='O usuário encontra-se desativado'
)
# Compa as senhas
if not get_by_login_result.senha_api == g_usuario_login_schema.senha_api:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='A senha informada não é válida'
)
# Gera o token de acesso
create_token = CreateToken()
# Adiciona os dados do usuário ao token
jwtUser = {
'usuario_id' : int(get_by_login_result.usuario_id),
'login' : str(get_by_login_result.login),
'nome' : str(get_by_login_result.login)
}
# Retorna o token dos dados do usuário
return create_token.execute('access-token', str(jwtUser))

View file

@ -0,0 +1,18 @@
import ast
from packages.v1.administrativo.schemas.g_usuario_schema import GUsuarioMe
from packages.v1.administrativo.actions.g_usuario.get_by_usuario_id_action import GetByUsuarioIdAction
class MeService:
def execute(self, current_user):
get_by_usuario_id_action = GetByUsuarioIdAction()
# Converte a string para dict de forma segura
usuario_data = ast.literal_eval(current_user["data"])
# Define os dados do schema
g_usuario_schema = GUsuarioMe(usuario_id=int(usuario_data["usuario_id"]))
# Executa a ação em questão
return get_by_usuario_id_action.execute(g_usuario_schema)

View file

@ -2,7 +2,8 @@
from fastapi import APIRouter
# Importa os módulos de rotas específicos
from api.v1.packages.administrative.endpoints import c_caixa_item
from packages.v1.administrativo.endpoints import c_caixa_item
from packages.v1.administrativo.endpoints import g_usuario
# Cria uma instância do APIRouter que vai agregar todas as rotas da API
api_router = APIRouter()
@ -11,3 +12,8 @@ api_router = APIRouter()
api_router.include_router(
c_caixa_item.router, prefix="/administrativo/caixa", tags=["Caixa"]
)
# Inclui as rotas de caixa
api_router.include_router(
g_usuario.router, prefix="/administrativo/usuarios", tags=["Usuário"]
)

View file

@ -1,6 +1,6 @@
from api.v1.packages.sequencia.schemas.g_sequencia import GSequenciaSchema
from api.v1.packages.sequencia.repositories.g_sequencia.get import Get
from core.base.base_action import BaseAction
from packages.v1.sequencia.repositories.g_sequencia.get import Get
from packages.v1.sequencia.schemas.g_sequencia import GSequenciaSchema
from abstracts.action import BaseAction
class GetAction(BaseAction):

View file

@ -1,6 +1,6 @@
from api.v1.packages.sequencia.schemas.g_sequencia import GSequenciaSchema
from api.v1.packages.sequencia.repositories.g_sequencia.save import Save
from core.base.base_action import BaseAction
from packages.v1.sequencia.repositories.g_sequencia.save import Save
from packages.v1.sequencia.schemas.g_sequencia import GSequenciaSchema
from abstracts.action import BaseAction
class SaveAction(BaseAction):

View file

@ -0,0 +1,21 @@
from packages.v1.sequencia.schemas.g_sequencia import GSequenciaSchema
from Api.abstracts.repository import BaseRepository
class Get(BaseRepository):
def execute(self, sequencia_schema : GSequenciaSchema):
# Montagem da consulta sql
sql = """ SELECT FIRST 1 * FROM G_SEQUENCIA gs WHERE gs.TABELA LIKE :tabela """
# Preenchimento dos parâmetros
params = {
"tabela" : sequencia_schema.tabela
}
# Execução do sql
response = self.fetch_one(sql, params)
# Transforma em dict associativo
return response

View file

@ -0,0 +1,25 @@
from packages.v1.sequencia.schemas.g_sequencia import GSequenciaSchema
from Api.abstracts.repository import BaseRepository
class Save(BaseRepository):
def execute(self, sequencia_schema : GSequenciaSchema):
# Construção do sql
sql = """ UPDATE G_SEQUENCIA
SET SEQUENCIA = :sequencia
WHERE TABELA LIKE :tabela
RETURNING TABELA, SEQUENCIA """
# Preenchimento de parâmetros
params = {
"sequencia": sequencia_schema.sequencia,
"tabela": sequencia_schema.tabela
}
# Execução do sql
response = self.run_and_return(sql, params)
# Retorna como verdadeiro se for salvo com sucesso
return response

View file

@ -1,6 +1,8 @@
from typing import Optional
from pydantic import BaseModel
class GSequenciaSchema(BaseModel):
tabela: Optional[str] = None
sequencia: Optional[str] = None

View file

@ -1,6 +1,7 @@
from api.v1.packages.sequencia.actions.g_sequencia.get_action import GetAction
from api.v1.packages.sequencia.actions.g_sequencia.save_action import SaveAction
from api.v1.packages.sequencia.schemas.g_sequencia import GSequenciaSchema
from packages.v1.sequencia.actions.g_sequencia.get_action import GetAction
from packages.v1.sequencia.actions.g_sequencia.save_action import \
SaveAction
from packages.v1.sequencia.schemas.g_sequencia import GSequenciaSchema
class GenerateService:
@ -15,7 +16,7 @@ class GenerateService:
sequencia_result = getAction.execute(sequencia_schema)
# Incrementa a sequência atual
sequencia_schema.sequencia = sequencia_result['sequencia'] + 1
sequencia_schema.sequencia = sequencia_result.sequencia + 1
# Atualiza a sequência atual
return saveAction.execute(sequencia_schema)

View file

@ -1,5 +1,6 @@
from api.v1.packages.sequencia.actions.g_sequencia.save_action import SaveAction
from api.v1.packages.sequencia.schemas.g_sequencia import GSequenciaSchema
from packages.v1.sequencia.actions.g_sequencia.save_action import \
SaveAction
from packages.v1.sequencia.schemas.g_sequencia import GSequenciaSchema
class SaveService:

View file

@ -0,0 +1,22 @@
from Api.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

View 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)
}

View 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

55
Api/storage/temp.json Normal file
View file

@ -0,0 +1,55 @@
[
"storage/temp/http_exception_handler.json",
"storage/temp/http_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/http_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/http_exception_handler.json",
"storage/temp/http_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/http_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/http_exception_handler.json",
"storage/temp/http_exception_handler.json",
"storage/temp/http_exception_handler.json",
"storage/temp/http_exception_handler.json",
"storage/temp/http_exception_handler.json",
"storage/temp/http_exception_handler.json",
"storage/temp/http_exception_handler.json",
"storage/temp/http_exception_handler.json",
"storage/temp/http_exception_handler.json",
"storage/temp/http_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json",
"storage/temp/validation_exception_handler.json"
]

View file

View file

@ -31,19 +31,13 @@
"method": "GET",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "username",
"value": "keven@softwiki.com.br",
"type": "text"
},
{
"key": "password",
"value": "123",
"type": "text"
"mode": "raw",
"raw": "{\r\n \"data_pagamento\": {\r\n \"date_start\": \"2024-01-01\",\r\n \"date_end\": \"2024-02-01\"\r\n }\r\n}",
"options": {
"raw": {
"language": "json"
}
]
}
},
"url": {
"raw": "{{BaseUrlV1}}administrativo/caixa",
@ -74,7 +68,7 @@
"header": [],
"body": {
"mode": "raw",
"raw": "{\r\n \"especie_pagamento\": \"D\",\r\n \"caixa_item_id\" : 12,\r\n \"caixa_servico_id\": 9,\r\n \"usuario_servico_id\": 123456,\r\n \"usuario_caixa_id\": null,\r\n \"chave_servico\": null,\r\n \"descricao\": \"1\",\r\n \"data_pagamento\": null,\r\n \"situacao\": \"3\",\r\n \"tipo_documento\": \"C\",\r\n \"tipo_transacao\": \"C\",\r\n \"valor_servico\": 61,\r\n \"valor_pago\": 61,\r\n \"observacao\": null,\r\n \"caixa_cheque_id\": null,\r\n \"hora_pagamento\": null,\r\n \"caixa_id\": null,\r\n \"recibo_id\": null,\r\n \"tipo_servico\": \"17\",\r\n \"qtd\": 1,\r\n \"apresentante\": \"1\",\r\n \"mensalista_id\": null,\r\n \"quitado_caixa_id\": null,\r\n \"registrado\": 1,\r\n \"emolumento\": 33,\r\n \"taxa_judiciaria\": 14,\r\n \"fundesp\": 13,\r\n \"desconto\": 0,\r\n \"valor_documento\": 0,\r\n \"outra_taxa1\": 0,\r\n \"chave_servico_sec\": null,\r\n \"emolumento_item_id\": null,\r\n \"caixa_registroselo_id\": null,\r\n \"fundo_ri\": null,\r\n \"valor_recibo\": null,\r\n \"boleto_pdf\": null,\r\n \"boleto_vencimento\": null,\r\n \"iss\": 1,\r\n \"nlote\": 0,\r\n \"tabela\": \"1\",\r\n \"campo_id\": 5,\r\n \"boleto_id\": null,\r\n \"valor_adicional\": null,\r\n \"pix_id\": null\r\n}",
"raw": "{\r\n \"especie_pagamento\": \"D\",\r\n \"caixa_item_id\": 275547.00,\r\n \"caixa_servico_id\": 2.00,\r\n \"usuario_servico_id\": 123456.00,\r\n \"usuario_caixa_id\": 123456.00,\r\n \"descricao\": \"{{$randomProductName}}\",\r\n \"data_pagamento\": \"2025-07-02 00:00:00.000\",\r\n \"situacao\": \"4\",\r\n \"tipo_documento\": \"C\",\r\n \"tipo_transacao\": \"C\",\r\n \"valor_servico\": 123123.000,\r\n \"valor_pago\": 123123.000,\r\n \"observacao\": \"{{$randomJobDescriptor}}\",\r\n \"hora_pagamento\": \"12:58\",\r\n \"tipo_servico\": \"25\",\r\n \"registrado\": \"3\"\r\n}\r\n",
"options": {
"raw": {
"language": "json"
@ -127,13 +121,13 @@
]
},
"url": {
"raw": "{{BaseUrlV1}}administrativo/caixa/3",
"raw": "{{BaseUrlV1}}administrativo/caixa/27551",
"host": [
"{{BaseUrlV1}}administrativo"
],
"path": [
"caixa",
"3"
"27551"
]
}
},

6
server.bat Normal file
View file

@ -0,0 +1,6 @@
@echo off
cd \
cd C:\IIS\Orius\Api
call .venv\Scripts\activate.bat
uvicorn main:app --reload
pause