diff --git a/abstracts/repository.py b/abstracts/repository.py index d447580..30e8fe3 100644 --- a/abstracts/repository.py +++ b/abstracts/repository.py @@ -1,6 +1,6 @@ # abstracts/repository.py from sqlalchemy.orm import Session -from database.postgres import SessionLocal +from database.mysql import SessionLocal class BaseRepository: diff --git a/actions/config/config.py b/actions/config/config.py index 177b551..a43155b 100644 --- a/actions/config/config.py +++ b/actions/config/config.py @@ -1,20 +1,60 @@ import json +import os +import re from pathlib import Path from types import SimpleNamespace class Config: + """Classe responsável por carregar arquivos JSON e substituir variáveis de ambiente.""" + + @staticmethod + def _resolve_env_vars(value): + """ + Substitui placeholders ${VAR} por valores das variáveis de ambiente. + Funciona recursivamente para dicionários, listas e strings. + """ + if isinstance(value, str): + # Procura padrões como ${VAR_NAME} + pattern = re.compile(r"\$\{([^}^{]+)\}") + matches = pattern.findall(value) + for var in matches: + env_value = os.getenv(var) + if env_value is None: + raise ValueError(f"Variável de ambiente '{var}' não definida.") + value = value.replace(f"${{{var}}}", env_value) + return value + + elif isinstance(value, dict): + return {k: Config._resolve_env_vars(v) for k, v in value.items()} + + elif isinstance(value, list): + return [Config._resolve_env_vars(v) for v in value] + + return value @staticmethod def get(name: str): - # Caminho absoluto do arquivo atual + """ + Carrega um arquivo JSON de configuração e substitui variáveis de ambiente. + + Args: + name (str): Nome do arquivo dentro de /config (ex: "database/mysql.json") + + Returns: + SimpleNamespace: Objeto com os valores resolvidos acessíveis via ponto. + """ base_dir = Path(__file__).resolve().parent + config_path = base_dir.parent.parent / "config" / name - # Caminho absoluto para o config.json (subindo dois níveis e entrando em config/) - config_path = base_dir.parent.parent / 'config' / name + # Lê o arquivo JSON + with open(config_path, "r", encoding="utf-8") as f: + data = json.load(f) - # 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)) + # Substitui variáveis de ambiente + resolved_data = Config._resolve_env_vars(data) - return config \ No newline at end of file + # Retorna como objeto acessível por ponto + return json.loads( + json.dumps(resolved_data), object_hook=lambda d: SimpleNamespace(**d) + ) diff --git a/actions/dynamic_import/dynamic_import.py b/actions/dynamic_import/dynamic_import.py index fddc643..1f2cca9 100644 --- a/actions/dynamic_import/dynamic_import.py +++ b/actions/dynamic_import/dynamic_import.py @@ -3,7 +3,6 @@ from actions.config.config import Config from typing import Optional, Any, Type - class DynamicImport: def __init__(self) -> None: @@ -18,7 +17,7 @@ class DynamicImport: def set_table(self, table: str): self.table = table - def service(self, name: str, class_name : str) -> Type[Any]: + def service(self, name: str, class_name: str) -> Type[Any]: try: # Define o nome do Módulo module_file = f"{name}" diff --git a/actions/security/security.py b/actions/security/security.py index e23c3a6..8447b9e 100644 --- a/actions/security/security.py +++ b/actions/security/security.py @@ -6,7 +6,7 @@ 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') +CRYPTO = CryptContext(schemes=["bcrypt"], deprecated="auto") class Security: @@ -19,21 +19,19 @@ class Security: """ 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: + def verify_password(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: + def hash_password(plain_senha_api: str) -> str: """ Gera e retorna o hash da senha fornecida. diff --git a/config/database/mysql.json b/config/database/mysql.json new file mode 100644 index 0000000..d8c2251 --- /dev/null +++ b/config/database/mysql.json @@ -0,0 +1,15 @@ +{ + "host": "${DB_HOST}", + "port": "${DB_PORT}", + "name": "${DB_NAME}", + "user": "${DB_USER}", + "password": "${DB_PASSWORD}", + "aeskey": "${AES_KEY}", + "charset": "utf8mb4", + "pool": { + "pre_ping": true, + "size": 5, + "max_overflow": 10 + }, + "debug": false +} diff --git a/database/mysql.py b/database/mysql.py new file mode 100644 index 0000000..803c36a --- /dev/null +++ b/database/mysql.py @@ -0,0 +1,50 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, declarative_base +from actions.config.config import Config + +# === BASE ORM === +# Essa base é herdada por todos os modelos (tabelas) do SQLAlchemy. +Base = declarative_base() + + +def get_database_settings(): + """ + Lê e retorna as configurações do arquivo database/mysql.json, + com substituição automática das variáveis de ambiente (${VAR}). + """ + return Config.get("database/mysql.json") + + +def get_mysql_engine(): + """ + Cria e retorna a engine de conexão com o banco MySQL. + A engine é responsável por gerenciar o pool de conexões. + """ + db = get_database_settings() + + # === Monta a string DSN (Data Source Name) === + dsn = ( + f"mysql+pymysql://{db.user}:{db.password}@" + f"{db.host}:{db.port}/{db.name}?charset={db.charset}" + ) + + # === Cria a engine SQLAlchemy === + engine = create_engine( + dsn, + echo=bool(getattr(db, "debug", False)), # Exibe SQLs no log se habilitado + pool_pre_ping=bool(db.pool.pre_ping), # Testa conexões antes de usar + pool_size=int(db.pool.size), # Tamanho do pool de conexões + max_overflow=int(db.pool.max_overflow), # Conexões extras permitidas + connect_args={"connect_timeout": 10}, # Timeout de conexão + ) + + return engine + + +# === Engine global === +# Criada uma única vez durante o ciclo de vida da aplicação. +engine = get_mysql_engine() + +# === Sessão ORM === +# Cada request da aplicação cria uma instância de SessionLocal. +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) diff --git a/main.py b/main.py index 465063b..1a865d9 100644 --- a/main.py +++ b/main.py @@ -3,30 +3,36 @@ 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__), '..'))) +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 middleware de captura de erros junto ao banco de dados +from middlewares.error_handler import database_error_handler + # 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 +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') +config = Config.get("app.json") # Instancia o app FastAPI com um título personalizado -app = FastAPI(title='Mirror | Orius') +app = FastAPI(title="Mirror | Orius") + +# Adiciona o middleware global de erro +# app.middleware("http")(database_error_handler) # Controle de erros personalizados register_exception_handlers(app) @@ -40,6 +46,7 @@ app.add_middleware( allow_headers=["*"], ) + @app.on_event("startup") async def on_startup(): @@ -49,20 +56,21 @@ async def on_startup(): # 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 + # Ação responsavel por registrar o log de requisição log = Log() - config = Config.get('app.json') + config = Config.get("app.json") # Obtem os dados da requisição log_data = { "method": request.method, "url": str(request.url), - "headers": dict(request.headers) + "headers": dict(request.headers), } - + # Gera o nome do arquivo file = Path(config.log.request.path) / config.log.request.name @@ -74,16 +82,18 @@ async def log_tempo_requisicao(request: Request, call_next): 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__': +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 + "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 ) diff --git a/middlewares/error_handler.py b/middlewares/error_handler.py new file mode 100644 index 0000000..06624ad --- /dev/null +++ b/middlewares/error_handler.py @@ -0,0 +1,58 @@ +# middlewares/error_handler.py +from fastapi import Request +from fastapi.responses import JSONResponse +from sqlalchemy.exc import OperationalError, IntegrityError +import pymysql + + +async def database_error_handler(request: Request, call_next): + """ + Middleware para capturar erros de banco de dados e retornar respostas JSON amigáveis. + """ + try: + response = await call_next(request) + return response + + except OperationalError as e: + # Erros de conexão (ex: Access denied, banco inexistente) + return JSONResponse( + status_code=500, + content={ + "success": False, + "error": "Database connection failed", + "details": str(e.orig) if hasattr(e, "orig") else str(e), + }, + ) + + except IntegrityError as e: + # Erros de integridade (ex: unique constraint, foreign key) + return JSONResponse( + status_code=400, + content={ + "success": False, + "error": "Database integrity error", + "details": str(e.orig) if hasattr(e, "orig") else str(e), + }, + ) + + except pymysql.err.OperationalError as e: + # Erro específico do PyMySQL (ex: senha errada) + return JSONResponse( + status_code=500, + content={ + "success": False, + "error": "MySQL Operational Error", + "details": str(e), + }, + ) + + except Exception as e: + # Qualquer outro erro genérico + return JSONResponse( + status_code=500, + content={ + "success": False, + "error": "Internal Server Error", + "details": str(e), + }, + ) diff --git a/packages/v1/administrativo/actions/ato_documento/ato_documento_delete_action.py b/packages/v1/administrativo/actions/ato_documento/ato_documento_delete_action.py new file mode 100644 index 0000000..91efdfc --- /dev/null +++ b/packages/v1/administrativo/actions/ato_documento/ato_documento_delete_action.py @@ -0,0 +1,13 @@ +from packages.v1.administrativo.schemas.ato_documento_schema import AtoDocumentoSchema +from packages.v1.administrativo.repositories.ato_documento.ato_documento_delete_repository import ( + DeleteRepository, +) + + +class DeleteAction: + + def execute(self, ato_documento_schema: AtoDocumentoSchema): + + delete_repository = DeleteRepository() + + return delete_repository.execute(ato_documento_schema) diff --git a/packages/v1/administrativo/actions/ato_documento/ato_documento_index_action.py b/packages/v1/administrativo/actions/ato_documento/ato_documento_index_action.py new file mode 100644 index 0000000..09c5958 --- /dev/null +++ b/packages/v1/administrativo/actions/ato_documento/ato_documento_index_action.py @@ -0,0 +1,18 @@ +from abstracts.action import BaseAction +from packages.v1.administrativo.repositories.ato_documento.ato_documento_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 diff --git a/packages/v1/administrativo/actions/ato_documento/ato_documento_save_action.py b/packages/v1/administrativo/actions/ato_documento/ato_documento_save_action.py new file mode 100644 index 0000000..0857d61 --- /dev/null +++ b/packages/v1/administrativo/actions/ato_documento/ato_documento_save_action.py @@ -0,0 +1,15 @@ +from packages.v1.administrativo.schemas.ato_documento_schema import ( + AtoDocumentoSaveSchema, +) +from packages.v1.administrativo.repositories.ato_documento.ato_documento_save_repository import ( + SaveRepository, +) + + +class SaveAction: + + def execute(self, ato_documento_schema: AtoDocumentoSaveSchema): + + save_repository = SaveRepository() + + return save_repository.execute(ato_documento_schema) diff --git a/packages/v1/administrativo/actions/ato_documento/ato_documento_show_action.py b/packages/v1/administrativo/actions/ato_documento/ato_documento_show_action.py new file mode 100644 index 0000000..a10ca94 --- /dev/null +++ b/packages/v1/administrativo/actions/ato_documento/ato_documento_show_action.py @@ -0,0 +1,19 @@ +from abstracts.action import BaseAction +from packages.v1.administrativo.schemas.ato_documento_schema import AtoDocumentoSchema +from packages.v1.administrativo.repositories.ato_documento.ato_documento_show_repository import ( + ShowRepository, +) + + +class ShowAction(BaseAction): + + def execute(self, ato_documento_schema: AtoDocumentoSchema): + + # Instânciamento do repositório sql + show_repository = ShowRepository() + + # Execução do sql + response = show_repository.execute(ato_documento_schema) + + # Retorno da informação + return response diff --git a/packages/v1/administrativo/actions/ato_documento/ato_documento_update_action.py b/packages/v1/administrativo/actions/ato_documento/ato_documento_update_action.py new file mode 100644 index 0000000..3b6d8c3 --- /dev/null +++ b/packages/v1/administrativo/actions/ato_documento/ato_documento_update_action.py @@ -0,0 +1,17 @@ +from packages.v1.administrativo.schemas.ato_documento_schema import ( + AtoDocumentoUpdateSchema, +) +from packages.v1.administrativo.repositories.ato_documento.ato_documento_update_repository import ( + UpdateRepository, +) + + +class UpdateAction: + + def execute( + self, ato_documento_id: int, ato_documento_schema: AtoDocumentoUpdateSchema + ): + + update_repository = UpdateRepository() + + return update_repository.execute(ato_documento_id, ato_documento_schema) diff --git a/packages/v1/administrativo/actions/ato_documento/ato_principal_show_action.py b/packages/v1/administrativo/actions/ato_documento/ato_principal_show_action.py new file mode 100644 index 0000000..042e3b9 --- /dev/null +++ b/packages/v1/administrativo/actions/ato_documento/ato_principal_show_action.py @@ -0,0 +1,16 @@ +from packages.v1.administrativo.schemas.ato_documento_schema import AtoDocumentoSchema +from packages.v1.administrativo.repositories.ato_documento.ato_principal_show_repository import ( + ShowRepository, +) + + +class ShowAction: + + # O método execute espera um schema que contenha o ID do principal + def execute(self, ato_documento_schema: AtoDocumentoSchema): + + # Instânciamento do repositório + show_repository = ShowRepository() + + # Execução do repositório + return show_repository.execute(ato_documento_schema) diff --git a/packages/v1/administrativo/actions/ato_parte/ato_parte_delete_action.py b/packages/v1/administrativo/actions/ato_parte/ato_parte_delete_action.py new file mode 100644 index 0000000..47ec8c6 --- /dev/null +++ b/packages/v1/administrativo/actions/ato_parte/ato_parte_delete_action.py @@ -0,0 +1,13 @@ +from packages.v1.administrativo.schemas.ato_parte_schema import AtoParteSchema +from packages.v1.administrativo.repositories.ato_parte.ato_parte_delete_repository import ( + DeleteRepository, +) + + +class DeleteAction: + + def execute(self, ato_parte_schema: AtoParteSchema): + + delete_repository = DeleteRepository() + + return delete_repository.execute(ato_parte_schema) diff --git a/packages/v1/administrativo/actions/ato_parte/ato_parte_index_action.py b/packages/v1/administrativo/actions/ato_parte/ato_parte_index_action.py new file mode 100644 index 0000000..c1b1f1b --- /dev/null +++ b/packages/v1/administrativo/actions/ato_parte/ato_parte_index_action.py @@ -0,0 +1,18 @@ +from abstracts.action import BaseAction +from packages.v1.administrativo.repositories.ato_parte.ato_parte_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 diff --git a/packages/v1/administrativo/actions/ato_parte/ato_parte_save_action.py b/packages/v1/administrativo/actions/ato_parte/ato_parte_save_action.py new file mode 100644 index 0000000..3509c91 --- /dev/null +++ b/packages/v1/administrativo/actions/ato_parte/ato_parte_save_action.py @@ -0,0 +1,15 @@ +from packages.v1.administrativo.schemas.ato_parte_schema import ( + AtoParteSaveSchema, +) +from packages.v1.administrativo.repositories.ato_parte.ato_parte_save_repository import ( + SaveRepository, +) + + +class SaveAction: + + def execute(self, ato_parte_schema: AtoParteSaveSchema): + + save_repository = SaveRepository() + + return save_repository.execute(ato_parte_schema) diff --git a/packages/v1/administrativo/actions/ato_parte/ato_parte_show_action.py b/packages/v1/administrativo/actions/ato_parte/ato_parte_show_action.py new file mode 100644 index 0000000..2bfc06f --- /dev/null +++ b/packages/v1/administrativo/actions/ato_parte/ato_parte_show_action.py @@ -0,0 +1,19 @@ +from abstracts.action import BaseAction +from packages.v1.administrativo.schemas.ato_parte_schema import AtoParteSchema +from packages.v1.administrativo.repositories.ato_parte.ato_parte_show_repository import ( + ShowRepository, +) + + +class ShowAction(BaseAction): + + def execute(self, ato_parte_schema: AtoParteSchema): + + # Instânciamento do repositório sql + show_repository = ShowRepository() + + # Execução do sql + response = show_repository.execute(ato_parte_schema) + + # Retorno da informação + return response diff --git a/packages/v1/administrativo/actions/ato_parte/ato_parte_update_action.py b/packages/v1/administrativo/actions/ato_parte/ato_parte_update_action.py new file mode 100644 index 0000000..8f15f61 --- /dev/null +++ b/packages/v1/administrativo/actions/ato_parte/ato_parte_update_action.py @@ -0,0 +1,15 @@ +from packages.v1.administrativo.schemas.ato_parte_schema import ( + AtoParteUpdateSchema, +) +from packages.v1.administrativo.repositories.ato_parte.ato_parte_update_repository import ( + UpdateRepository, +) + + +class UpdateAction: + + def execute(self, ato_parte_id: int, ato_parte_schema: AtoParteUpdateSchema): + + update_repository = UpdateRepository() + + return update_repository.execute(ato_parte_id, ato_parte_schema) diff --git a/packages/v1/administrativo/actions/ato_parte/ato_principal_show_action.py b/packages/v1/administrativo/actions/ato_parte/ato_principal_show_action.py new file mode 100644 index 0000000..52d080a --- /dev/null +++ b/packages/v1/administrativo/actions/ato_parte/ato_principal_show_action.py @@ -0,0 +1,16 @@ +from packages.v1.administrativo.schemas.ato_parte_schema import AtoParteSchema +from packages.v1.administrativo.repositories.ato_parte.ato_principal_show_repository import ( + ShowRepository, +) + + +class AtoPartePrincipalShowAction: + + # O método execute espera um schema que contenha o ID do principal + def execute(self, ato_parte_schema: AtoParteSchema): + + # Instânciamento do repositório + show_repository = ShowRepository() + + # Execução do repositório + return show_repository.execute(ato_parte_schema) diff --git a/packages/v1/administrativo/actions/ato_principal/ato_principal_delete_action.py b/packages/v1/administrativo/actions/ato_principal/ato_principal_delete_action.py new file mode 100644 index 0000000..40e9606 --- /dev/null +++ b/packages/v1/administrativo/actions/ato_principal/ato_principal_delete_action.py @@ -0,0 +1,13 @@ +from packages.v1.administrativo.schemas.ato_principal_schema import AtoPrincipalIdSchema +from packages.v1.administrativo.repositories.ato_principal.ato_principal_delete_repository import ( + DeleteRepository, +) + + +class DeleteAction: + + def execute(self, ato_principal_schema: AtoPrincipalIdSchema): + + delete_repository = DeleteRepository() + + return delete_repository.execute(ato_principal_schema) diff --git a/packages/v1/administrativo/actions/ato_principal/ato_principal_index_action.py b/packages/v1/administrativo/actions/ato_principal/ato_principal_index_action.py index 93687fd..b4c5b02 100644 --- a/packages/v1/administrativo/actions/ato_principal/ato_principal_index_action.py +++ b/packages/v1/administrativo/actions/ato_principal/ato_principal_index_action.py @@ -1,13 +1,15 @@ from abstracts.action import BaseAction -from packages.v1.administrativo.repositories.ato_principal.ato_principal_index_repository import AtoPrincipalIndexRepository +from packages.v1.administrativo.repositories.ato_principal.ato_principal_index_repository import ( + IndexRepository, +) -class AtoPrincipalIndexAction(BaseAction): +class IndexAction(BaseAction): def execute(self): # Instânciamento de repositório - ato_principal_index_repository = AtoPrincipalIndexRepository() + ato_principal_index_repository = IndexRepository() # Retorna todos produtos - return ato_principal_index_repository.execute() \ No newline at end of file + return ato_principal_index_repository.execute() diff --git a/packages/v1/administrativo/actions/ato_principal/ato_principal_save_action.py b/packages/v1/administrativo/actions/ato_principal/ato_principal_save_action.py new file mode 100644 index 0000000..42deb00 --- /dev/null +++ b/packages/v1/administrativo/actions/ato_principal/ato_principal_save_action.py @@ -0,0 +1,15 @@ +from packages.v1.administrativo.schemas.ato_principal_schema import ( + AtoPrincipalSaveSchema, +) +from packages.v1.administrativo.repositories.ato_principal.ato_principal_save_repository import ( + SaveRepository, +) + + +class SaveAction: + + def execute(self, ato_principal_schema: AtoPrincipalSaveSchema): + + save_repository = SaveRepository() + + return save_repository.execute(ato_principal_schema) diff --git a/packages/v1/administrativo/actions/ato_principal/ato_principal_save_multiple_action.py b/packages/v1/administrativo/actions/ato_principal/ato_principal_save_multiple_action.py new file mode 100644 index 0000000..bd490bd --- /dev/null +++ b/packages/v1/administrativo/actions/ato_principal/ato_principal_save_multiple_action.py @@ -0,0 +1,19 @@ +from typing import List +from packages.v1.administrativo.schemas.ato_principal_schema import ( + AtoPrincipalSaveSchema, +) +from packages.v1.administrativo.repositories.ato_principal.ato_principal_save_multiple_repository import ( + SaveMultipleRepository, +) + + +class SaveMultipleAction: + + def execute(self, atos_principais: List[AtoPrincipalSaveSchema]): + save_repository = SaveMultipleRepository() + + # A lista completa é passada diretamente para o Repository. + # O Repository é a única camada que deve iterar. + results = save_repository.execute(atos_principais) + + return results diff --git a/packages/v1/administrativo/actions/ato_principal/ato_principal_show_action.py b/packages/v1/administrativo/actions/ato_principal/ato_principal_show_action.py new file mode 100644 index 0000000..f77de71 --- /dev/null +++ b/packages/v1/administrativo/actions/ato_principal/ato_principal_show_action.py @@ -0,0 +1,19 @@ +from abstracts.action import BaseAction +from packages.v1.administrativo.schemas.ato_principal_schema import AtoPrincipalSchema +from packages.v1.administrativo.repositories.ato_principal.ato_principal_show_repository import ( + ShowRepository, +) + + +class ShowAction(BaseAction): + + def execute(self, ato_principal_schema: AtoPrincipalSchema): + + # Instânciamento do repositório sql + show_repository = ShowRepository() + + # Execução do sql + response = show_repository.execute(ato_principal_schema) + + # Retorno da informação + return response diff --git a/packages/v1/administrativo/actions/ato_principal/ato_principal_update_action.py b/packages/v1/administrativo/actions/ato_principal/ato_principal_update_action.py new file mode 100644 index 0000000..8cb4c25 --- /dev/null +++ b/packages/v1/administrativo/actions/ato_principal/ato_principal_update_action.py @@ -0,0 +1,17 @@ +from packages.v1.administrativo.schemas.ato_principal_schema import ( + AtoPrincipalUpdateSchema, +) +from packages.v1.administrativo.repositories.ato_principal.ato_principal_update_repository import ( + UpdateRepository, +) + + +class UpdateAction: + + def execute( + self, ato_principal_id: int, ato_principal_schema: AtoPrincipalUpdateSchema + ): + + update_repository = UpdateRepository() + + return update_repository.execute(ato_principal_id, ato_principal_schema) diff --git a/packages/v1/administrativo/actions/usuario/usuario_delete_action.py b/packages/v1/administrativo/actions/usuario/usuario_delete_action.py new file mode 100644 index 0000000..b2621dc --- /dev/null +++ b/packages/v1/administrativo/actions/usuario/usuario_delete_action.py @@ -0,0 +1,13 @@ +from packages.v1.administrativo.schemas.usuario_schema import UsuarioIdSchema +from packages.v1.administrativo.repositories.usuario.usuario_delete_repository import ( + DeleteRepository, +) + + +class DeleteAction: + + def execute(self, usuario_schema: UsuarioIdSchema): + + delete_repository = DeleteRepository() + + return delete_repository.execute(usuario_schema) diff --git a/packages/v1/administrativo/actions/usuario/usuario_get_by_authenticate_action.py b/packages/v1/administrativo/actions/usuario/usuario_get_by_authenticate_action.py new file mode 100644 index 0000000..1d146c1 --- /dev/null +++ b/packages/v1/administrativo/actions/usuario/usuario_get_by_authenticate_action.py @@ -0,0 +1,16 @@ +from abstracts.action import BaseAction +from packages.v1.administrativo.schemas.usuario_schema import UsuarioAuthenticateSchema +from packages.v1.administrativo.repositories.usuario.usuario_get_by_authenticate_repository import ( + GetByAuthenticateRepository, +) + + +class GetByAuthenticateAction(BaseAction): + + def execute(self, usuario_authenticate_schema: UsuarioAuthenticateSchema): + + # Instânciamento do repositório de busca pelo authenticate + get_by_authenticate_repository = GetByAuthenticateRepository() + + # Execução do repositório + return get_by_authenticate_repository.execute(usuario_authenticate_schema) diff --git a/packages/v1/administrativo/actions/usuario/usuario_get_by_email_action.py b/packages/v1/administrativo/actions/usuario/usuario_get_by_email_action.py new file mode 100644 index 0000000..aebfd5e --- /dev/null +++ b/packages/v1/administrativo/actions/usuario/usuario_get_by_email_action.py @@ -0,0 +1,16 @@ +from abstracts.action import BaseAction +from packages.v1.administrativo.schemas.usuario_schema import UsuarioEmailSchema +from packages.v1.administrativo.repositories.usuario.usuario_get_by_email_repository import ( + GetByUsuarioEmailRepository, +) + + +class GetByUsuarioEmailAction(BaseAction): + + def execute(self, usuario_schema=UsuarioEmailSchema): + + # Importação do repositório + get_by_email_repository = GetByUsuarioEmailRepository() + + # Execução do repositório + return get_by_email_repository.execute(usuario_schema) diff --git a/packages/v1/administrativo/actions/usuario/usuario_get_by_usuario_id_action.py b/packages/v1/administrativo/actions/usuario/usuario_get_by_usuario_id_action.py new file mode 100644 index 0000000..267102c --- /dev/null +++ b/packages/v1/administrativo/actions/usuario/usuario_get_by_usuario_id_action.py @@ -0,0 +1,15 @@ +from packages.v1.administrativo.schemas.usuario_schema import UsuarioSchema +from packages.v1.administrativo.repositories.usuario.usuario_get_by_usuario_id_repository import ( + GetByUsuarioIdRepository, +) + + +class GetByUsuarioIdAction: + + def execute(self, usuario_schema=UsuarioSchema): + + # Importação do repositório + get_by_usuario_id_repository = GetByUsuarioIdRepository() + + # Execução do repositório + return get_by_usuario_id_repository.execute(usuario_schema) diff --git a/packages/v1/administrativo/actions/usuario/usuario_index_action.py b/packages/v1/administrativo/actions/usuario/usuario_index_action.py new file mode 100644 index 0000000..901ebbc --- /dev/null +++ b/packages/v1/administrativo/actions/usuario/usuario_index_action.py @@ -0,0 +1,18 @@ +from abstracts.action import BaseAction +from packages.v1.administrativo.repositories.usuario.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 diff --git a/packages/v1/administrativo/actions/usuario/usuario_save_action.py b/packages/v1/administrativo/actions/usuario/usuario_save_action.py new file mode 100644 index 0000000..ab5b8e1 --- /dev/null +++ b/packages/v1/administrativo/actions/usuario/usuario_save_action.py @@ -0,0 +1,13 @@ +from packages.v1.administrativo.schemas.usuario_schema import UsuarioSaveSchema +from packages.v1.administrativo.repositories.usuario.usuario_save_repository import ( + SaveRepository, +) + + +class SaveAction: + + def execute(self, usuario_schema: UsuarioSaveSchema): + + save_repository = SaveRepository() + + return save_repository.execute(usuario_schema) diff --git a/packages/v1/administrativo/actions/usuario/usuario_show_action.py b/packages/v1/administrativo/actions/usuario/usuario_show_action.py new file mode 100644 index 0000000..62e2dcc --- /dev/null +++ b/packages/v1/administrativo/actions/usuario/usuario_show_action.py @@ -0,0 +1,19 @@ +from abstracts.action import BaseAction +from packages.v1.administrativo.schemas.usuario_schema import UsuarioSchema +from packages.v1.administrativo.repositories.usuario.usuario_show_repository import ( + ShowRepository, +) + + +class ShowAction(BaseAction): + + def execute(self, usuario_schema: UsuarioSchema): + + # Instânciamento do repositório sql + show_repository = ShowRepository() + + # Execução do sql + response = show_repository.execute(usuario_schema) + + # Retorno da informação + return response diff --git a/packages/v1/administrativo/actions/usuario/usuario_update_action.py b/packages/v1/administrativo/actions/usuario/usuario_update_action.py new file mode 100644 index 0000000..270e6bc --- /dev/null +++ b/packages/v1/administrativo/actions/usuario/usuario_update_action.py @@ -0,0 +1,13 @@ +from packages.v1.administrativo.schemas.usuario_schema import UsuarioUpdateSchema +from packages.v1.administrativo.repositories.usuario.usuario_update_repository import ( + UpdateRepository, +) + + +class UpdateAction: + + def execute(self, usuario_id: int, usuario_schema: UsuarioUpdateSchema): + + save_repository = UpdateRepository() + + return save_repository.execute(usuario_id, usuario_schema) diff --git a/packages/v1/administrativo/controllers/ato_documento_controller.py b/packages/v1/administrativo/controllers/ato_documento_controller.py new file mode 100644 index 0000000..9255701 --- /dev/null +++ b/packages/v1/administrativo/controllers/ato_documento_controller.py @@ -0,0 +1,113 @@ +from actions.dynamic_import.dynamic_import import DynamicImport +from packages.v1.administrativo.services.ato_documento.ato_documento_index_service import ( + IndexService, +) +from packages.v1.administrativo.services.ato_documento.ato_documento_save_service import ( + SaveService, +) +from packages.v1.administrativo.services.ato_documento.ato_documento_show_service import ( + ShowService, +) +from packages.v1.administrativo.services.ato_documento.ato_principal_show_service import ( + ShowAtoPrincipalService, +) +from packages.v1.administrativo.services.ato_documento.ato_documento_update_service import ( + UpdateService, +) +from packages.v1.administrativo.services.ato_documento.ato_documento_delete_service import ( + DeleteService, +) +from packages.v1.administrativo.schemas.ato_documento_schema import ( + AtoDocumentoSchema, + AtoDocumentoSaveSchema, + AtoDocumentoUpdateSchema, + AtoDocumentoIdSchema, +) + + +class AtoDocumentoController: + + 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("ato_documento") + pass + + # Lista todos os documentos + def index(self): + + # Instânciamento da classe service + index_service = IndexService() + + # Lista todos os documentos + return { + "message": "Documentos localizados com sucesso", + "data": index_service.execute(), + } + + # Busca um documento especifico pelo ID do ato principal + def showAtoPrincipal(self, ato_documento_schema: AtoDocumentoSchema): + + # Instânciamento da classe desejada + show_service = ShowAtoPrincipalService() + + # Busca e retorna o documento desejado + return { + "message": "Documento localizado com sucesso", + "data": show_service.execute(ato_documento_schema), + } + + # Busca um documento especifico pelo ID + def show(self, ato_documento_schema: AtoDocumentoSchema): + + # Instânciamento da classe desejada + show_service = ShowService() + + # Busca e retorna o documento desejado + return { + "message": "Documento localizado com sucesso", + "data": show_service.execute(ato_documento_schema), + } + + # Cadastra um novo documento + def save(self, ato_documento_schema: AtoDocumentoSaveSchema): + + # Instânciamento da classe desejada + save_service = SaveService() + + # Busca e retorna o documento desejado + return { + "message": "Documento salvo com sucesso", + "data": save_service.execute(ato_documento_schema), + } + + # Atualiza os dados de um documento + def update( + self, ato_documento_id: int, ato_documento_schema: AtoDocumentoUpdateSchema + ): + + # Instânciamento da classe desejada + update_service = UpdateService() + + # Busca e retorna o documento desejado + return { + "message": "Documento atualizado com sucesso", + "data": update_service.execute(ato_documento_id, ato_documento_schema), + } + + # Exclui um documento + def delete(self, ato_documento_schema: AtoDocumentoSchema): + + # Instânciamento da classe desejada + delete_service = DeleteService() + + # Busca e retorna o documento desejado + return { + "message": "Documento removido com sucesso", + "data": delete_service.execute(ato_documento_schema), + } diff --git a/packages/v1/administrativo/controllers/ato_parte_controller.py b/packages/v1/administrativo/controllers/ato_parte_controller.py new file mode 100644 index 0000000..afe785e --- /dev/null +++ b/packages/v1/administrativo/controllers/ato_parte_controller.py @@ -0,0 +1,111 @@ +from actions.dynamic_import.dynamic_import import DynamicImport +from packages.v1.administrativo.services.ato_parte.ato_parte_index_service import ( + IndexService, +) +from packages.v1.administrativo.services.ato_parte.ato_parte_save_service import ( + SaveService, +) +from packages.v1.administrativo.services.ato_parte.ato_parte_show_service import ( + ShowService, +) +from packages.v1.administrativo.services.ato_parte.ato_principal_show_service import ( + AtoPrincipalShowService, +) +from packages.v1.administrativo.services.ato_parte.ato_parte_update_service import ( + UpdateService, +) +from packages.v1.administrativo.services.ato_parte.ato_parte_delete_service import ( + DeleteService, +) +from packages.v1.administrativo.schemas.ato_parte_schema import ( + AtoParteSchema, + AtoParteSaveSchema, + AtoParteUpdateSchema, + AtoParteIdSchema, +) + + +class AtoParteController: + + 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("ato_parte") + pass + + # Lista todas as partes + def index(self): + + # Instânciamento da classe service + index_service = IndexService() + + # Lista todas as partes + return { + "message": "Partes localizadas com sucesso", + "data": index_service.execute(), + } + + # Busca uma parte especifica pelo ID do ato principal + def showAtoPrincipal(self, ato_parte_schema: AtoParteSchema): + + # Instânciamento da classe desejada + show_service = AtoPrincipalShowService() + + # Busca e retorna a parte desejada + return { + "message": "Parte localizada com sucesso", + "data": show_service.execute(ato_parte_schema), + } + + # Busca uma parte especifica pelo ID + def show(self, ato_parte_schema: AtoParteSchema): + + # Instânciamento da classe desejada + show_service = ShowService() + + # Busca e retorna a parte desejada + return { + "message": "Parte localizada com sucesso", + "data": show_service.execute(ato_parte_schema), + } + + # Cadastra uma nova parte + def save(self, ato_parte_schema: AtoParteSaveSchema): + + # Instânciamento da classe desejada + save_service = SaveService() + + # Busca e retorna a parte desejada + return { + "message": "Parte salva com sucesso", + "data": save_service.execute(ato_parte_schema), + } + + # Atualiza os dados de uma parte + def update(self, ato_parte_id: int, ato_parte_schema: AtoParteUpdateSchema): + + # Instânciamento da classe desejada + update_service = UpdateService() + + # Busca e retorna a parte desejada + return { + "message": "Parte atualizada com sucesso", + "data": update_service.execute(ato_parte_id, ato_parte_schema), + } + + # Exclui uma parte + def delete(self, ato_parte_schema: AtoParteSchema): + + # Instânciamento da classe desejada + delete_service = DeleteService() + + # Busca e retorna a parte desejada + return { + "message": "Parte removida com sucesso", + "data": delete_service.execute(ato_parte_schema), + } diff --git a/packages/v1/administrativo/controllers/ato_principal_controller.py b/packages/v1/administrativo/controllers/ato_principal_controller.py index 3d69406..702a240 100644 --- a/packages/v1/administrativo/controllers/ato_principal_controller.py +++ b/packages/v1/administrativo/controllers/ato_principal_controller.py @@ -1,20 +1,124 @@ -# 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 + +# Importa os serviços com o prefixo 'ato_principal' +from packages.v1.administrativo.services.ato_principal.ato_principal_index_service import ( + IndexService, +) +from packages.v1.administrativo.services.ato_principal.ato_principal_save_service import ( + SaveService, +) +from packages.v1.administrativo.services.ato_principal.ato_principal_save_multiple_service import ( + SaveMultipleService, +) +from packages.v1.administrativo.services.ato_principal.ato_principal_show_service import ( + ShowService, +) +from packages.v1.administrativo.services.ato_principal.ato_principal_update_service import ( + UpdateService, +) +from packages.v1.administrativo.services.ato_principal.ato_principal_delete_service import ( + DeleteService, +) + +# Importa os Schemas com o prefixo 'ato_principal' +from packages.v1.administrativo.schemas.ato_principal_schema import ( + AtoPrincipalSchema, + AtoPrincipalSaveSchema, + AtoPrincipalUpdateSchema, + AtoPrincipalIdSchema, +) +# Mantendo o padrão de nome de classe class AtoPrincipalController: + """ + Controller responsável pelas operações CRUD da entidade Ato Principal. + """ + 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 (agora 'ato_principal') + self.dynamic_import.set_table("ato_principal") + pass + + # Lista todos os atos principais def index(self): - # Importação da classe desejad - ato_principal_index_service = AtoPrincipalIndexService() + # Instânciamento da classe service + index_service = IndexService() - # Intânciamento da classe service - self.index_service = ato_principal_index_service - - # Lista todos os produtos + # Lista todos os atos principais return { - 'message' : 'Registros localizados com sucesso', - 'data': self.index_service.execute() - } \ No newline at end of file + "message": "Atos Principais localizados com sucesso", + "data": index_service.execute(), + } + + # Busca um ato principal especifica pelo ID + # Note: O método 'showAtoPrincipal' foi removido por não se aplicar + # à entidade principal. + def show(self, ato_principal_schema: AtoPrincipalSchema): + + # Instânciamento da classe desejada + show_service = ShowService() + + # Busca e retorna o ato principal desejado + return { + "message": "Ato Principal localizado com sucesso", + "data": show_service.execute(ato_principal_schema), + } + + # Cadastra um novo ato principal + def save(self, ato_principal_schema: AtoPrincipalSaveSchema): + + # Instânciamento da classe desejada + save_service = SaveService() + + # Salva e retorna o ato principal + return { + "message": "Ato Principal salvo com sucesso", + "data": save_service.execute(ato_principal_schema), + } + + # Cadastra múltiplos itens + def save_multiple(self, atos_principais: list[AtoPrincipalSaveSchema]): + # A lista completa é passada diretamente para o Service. + # O Service e o Action também devem ser corrigidos para parar de iterar. + save_service = SaveMultipleService() + + responses = save_service.execute(atos_principais) + + return { + "message": "Processamento de múltiplos atos concluído", + "results": responses, # O Service já retorna a lista de resultados + } + + # Atualiza os dados de um ato principal + def update( + self, ato_principal_id: int, ato_principal_schema: AtoPrincipalUpdateSchema + ): + + # Instânciamento da classe desejada + update_service = UpdateService() + + # Atualiza e retorna o ato principal + return { + "message": "Ato Principal atualizado com sucesso", + "data": update_service.execute(ato_principal_id, ato_principal_schema), + } + + # Exclui um ato principal + def delete(self, ato_principal_schema: AtoPrincipalSchema): + + # Instânciamento da classe desejada + delete_service = DeleteService() + + # Exclui e retorna a parte desejada + return { + "message": "Ato Principal removido com sucesso", + "data": delete_service.execute(ato_principal_schema), + } diff --git a/packages/v1/administrativo/controllers/usuario_controller.py b/packages/v1/administrativo/controllers/usuario_controller.py new file mode 100644 index 0000000..977d7c6 --- /dev/null +++ b/packages/v1/administrativo/controllers/usuario_controller.py @@ -0,0 +1,145 @@ +from actions.dynamic_import.dynamic_import import DynamicImport +from packages.v1.administrativo.services.usuario.usuario_authenticate_service import ( + AuthenticateService, +) +from packages.v1.administrativo.services.usuario.usuario_me_service import ( + MeService, +) +from packages.v1.administrativo.services.usuario.usuario_index_service import ( + IndexService, +) +from packages.v1.administrativo.services.usuario.usuario_save_service import ( + SaveService, +) +from packages.v1.administrativo.services.usuario.usuario_get_email_service import ( + GetEmailService, +) +from packages.v1.administrativo.services.usuario.usuario_show_service import ( + ShowService, +) +from packages.v1.administrativo.services.usuario.usuario_update_service import ( + UpdateService, +) +from packages.v1.administrativo.services.usuario.usuario_delete_service import ( + DeleteService, +) +from packages.v1.administrativo.schemas.usuario_schema import ( + UsuarioSchema, + UsuarioAuthenticateSchema, + UsuarioSaveSchema, + UsuarioUpdateSchema, + UsuarioEmailSchema, + UsuarioIdSchema, +) + + +class UsuarioController: + + 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("usuario") + pass + + # Efetua o acesso junto ao sistema por um determinado usuário + def authenticate(self, user_authenticate_schema: UsuarioAuthenticateSchema): + + # Importação de service de Authenticate + authenticate_service = AuthenticateService() + + # Retorna o usuário logado + return { + "message": "Usuário localizado com sucesso", + "data": {"token": authenticate_service.execute(user_authenticate_schema)}, + } + + # Carrega os dados do usuário logado + def me(self, current_user): + + # Instânciamento da service + me_service = MeService() + + # Retorna o usuário logado + return { + "message": "Usuário localizado com sucesso", + "data": me_service.execute(current_user), + } + + # Lista todos os usuários + def index(self): + + # Instânciamento da classe service + indexService = IndexService() + + # Lista todos os usuários + return { + "message": "Usuários localizados com sucesso", + "data": indexService.execute(), + } + + # Busca um usuário especifico pelo ID + def show(self, usuario_schema: UsuarioSchema): + + # Instânciamento da classe desejada + show_service = ShowService() + + # Busca e retorna o usuário desejado + return { + "message": "Usuário localizado com sucesso", + "data": show_service.execute(usuario_schema), + } + + # Busca um usuário especifico pelo e-mail + def getEmail(self, usuario_schema: UsuarioEmailSchema): + + # Instânciamento da classe desejada + get_email_service = GetEmailService() + + # Busca e retorna o usuário desejado + return { + "message": "E-mail localizado com sucesso", + "data": get_email_service.execute( + usuario_schema, True + ), # True para retornar a mensagem de erro caso não localize o usuario + } + + # Cadastra um novo usuário + def save(self, usuario_schema: UsuarioSaveSchema): + + # Instânciamento da classe desejada + save_service = SaveService() + + # Busca e retorna o usuário desejado + return { + "message": "Usuário salvo com sucesso", + "data": save_service.execute(usuario_schema), + } + + # Atualiza os dados de um usuário + def update(self, usuario_id: int, usuario_schema: UsuarioUpdateSchema): + + # Instânciamento da classe desejada + update_service = UpdateService() + + # Busca e retorna o usuário desejado + return { + "message": "Usuário atualizado com sucesso", + "data": update_service.execute(usuario_id, usuario_schema), + } + + # Exclui um usuário + def delete(self, usuario_schema: UsuarioIdSchema): + + # Instânciamento da classe desejada + delete_service = DeleteService() + + # Busca e retorna o usuário desejado + return { + "message": "Usuário removido com sucesso", + "data": delete_service.execute(usuario_schema), + } diff --git a/packages/v1/administrativo/endpoints/ato_documento_endpoint.py b/packages/v1/administrativo/endpoints/ato_documento_endpoint.py new file mode 100644 index 0000000..ca242d4 --- /dev/null +++ b/packages/v1/administrativo/endpoints/ato_documento_endpoint.py @@ -0,0 +1,133 @@ +# Importação de bibliotecas +from typing import Optional +from fastapi import APIRouter, Depends, status +from actions.jwt.get_current_user import get_current_user +from packages.v1.administrativo.controllers.ato_documento_controller import ( + AtoDocumentoController, +) +from packages.v1.administrativo.schemas.ato_documento_schema import ( + AtoDocumentoSchema, + AtoDocumentoSaveSchema, + AtoDocumentoUpdateSchema, + AtoDocumentoIdSchema, +) + +# Inicializa o roteador para as rotas de ato_documento +router = APIRouter() + +# Instânciamento do controller desejado +ato_documento_controller = AtoDocumentoController() + + +# Lista todos os documentos +@router.get( + "/", + status_code=status.HTTP_200_OK, + summary="Lista todos os documentos cadastrados", + response_description="Lista todos os documentos cadastrados", +) +async def index(current_user: dict = Depends(get_current_user)): + + # Busca todos os documentos cadastrados + response = ato_documento_controller.index() + + # Retorna os dados localizados + return response + + +# Localiza um documento pelo ato_principal_id +@router.get( + "/ato_principal/{ato_principal_id}", + status_code=status.HTTP_200_OK, + summary="Busca um registro em especifico pelo ID do ato principal", + response_description="Busca um registro em especifico", +) +async def showAtoPrincipal( + ato_principal_id: int, current_user: dict = Depends(get_current_user) +): + + # Cria o schema com os dados recebidos + ato_documento_schema = AtoDocumentoSchema(ato_principal_id=ato_principal_id) + + # Busca um documento especifico pelo ID + response = ato_documento_controller.showAtoPrincipal(ato_documento_schema) + + # Retorna os dados localizados + return response + + +# Localiza um documento pelo ID +@router.get( + "/{ato_documento_id}", + status_code=status.HTTP_200_OK, + summary="Busca um registro em especifico pelo ID do documento", + response_description="Busca um registro em especifico", +) +async def show(ato_documento_id: int, current_user: dict = Depends(get_current_user)): + + # Cria o schema com os dados recebidos + ato_documento_schema = AtoDocumentoSchema(ato_documento_id=ato_documento_id) + + # Busca um documento especifico pelo ID + response = ato_documento_controller.show(ato_documento_schema) + + # Retorna os dados localizados + return response + + +# Cadastro de documentos +@router.post( + "/", + status_code=status.HTTP_200_OK, + summary="Cadastra um documento", + response_description="Cadastra um documento", +) +async def save( + ato_documento_schema: AtoDocumentoSaveSchema, + current_user: dict = Depends(get_current_user), +): + + # Efetua o cadastro do documento junto ao banco de dados + response = ato_documento_controller.save(ato_documento_schema) + + # Retorna os dados localizados + return response + + +# Atualiza os dados de documento +@router.put( + "/{ato_documento_id}", + status_code=status.HTTP_200_OK, + summary="Atualiza um documento", + response_description="Atualiza um documento", +) +async def update( + ato_documento_id: int, + ato_documento_schema: AtoDocumentoUpdateSchema, + current_user: dict = Depends(get_current_user), +): + + # Efetua a atualização dos dados do documento + response = ato_documento_controller.update(ato_documento_id, ato_documento_schema) + + # Retorna os dados localizados + return response + + +# Exclui um determinado documento +@router.delete( + "/{ato_documento_id}", + status_code=status.HTTP_200_OK, + summary="Remove um documento", + response_description="Remove um documento", +) +async def delete(ato_documento_id: int, current_user: dict = Depends(get_current_user)): + + # Cria o schema com os dados recebidos + ato_documento_schema = AtoDocumentoSchema(ato_documento_id=ato_documento_id) + + # Efetua a exclusão de um determinado documento + response = ato_documento_controller.delete(ato_documento_schema) + + # Retorna os dados localizados + return response diff --git a/packages/v1/administrativo/endpoints/ato_parte_endpoint.py b/packages/v1/administrativo/endpoints/ato_parte_endpoint.py new file mode 100644 index 0000000..745303b --- /dev/null +++ b/packages/v1/administrativo/endpoints/ato_parte_endpoint.py @@ -0,0 +1,133 @@ +# Importação de bibliotecas +from typing import Optional +from fastapi import APIRouter, Depends, status +from actions.jwt.get_current_user import get_current_user +from packages.v1.administrativo.controllers.ato_parte_controller import ( + AtoParteController, +) +from packages.v1.administrativo.schemas.ato_parte_schema import ( + AtoParteSchema, + AtoParteSaveSchema, + AtoParteUpdateSchema, + AtoParteIdSchema, +) + +# Inicializa o roteador para as rotas de ato_parte +router = APIRouter() + +# Instânciamento do controller desejado +ato_parte_controller = AtoParteController() + + +# Lista todas as partes +@router.get( + "/", + status_code=status.HTTP_200_OK, + summary="Lista todas as partes cadastradas", + response_description="Lista todas as partes cadastradas", +) +async def index(current_user: dict = Depends(get_current_user)): + + # Busca todas as partes cadastradas + response = ato_parte_controller.index() + + # Retorna os dados localizados + return response + + +# Localiza uma parte pelo ato_principal_id +@router.get( + "/ato_principal/{ato_principal_id}", + status_code=status.HTTP_200_OK, + summary="Busca um registro em especifico pelo ID do ato principal", + response_description="Busca um registro em especifico", +) +async def showAtoPrincipal( + ato_principal_id: int, current_user: dict = Depends(get_current_user) +): + + # Cria o schema com os dados recebidos + ato_parte_schema = AtoParteSchema(ato_principal_id=ato_principal_id) + + # Busca uma parte especifica pelo ID do ato principal + response = ato_parte_controller.showAtoPrincipal(ato_parte_schema) + + # Retorna os dados localizados + return response + + +# Localiza uma parte pelo ID +@router.get( + "/{ato_parte_id}", + status_code=status.HTTP_200_OK, + summary="Busca um registro em especifico pelo ID da parte", + response_description="Busca um registro em especifico", +) +async def show(ato_parte_id: int, current_user: dict = Depends(get_current_user)): + + # Cria o schema com os dados recebidos + ato_parte_schema = AtoParteSchema(ato_parte_id=ato_parte_id) + + # Busca uma parte especifica pelo ID + response = ato_parte_controller.show(ato_parte_schema) + + # Retorna os dados localizados + return response + + +# Cadastro de partes +@router.post( + "/", + status_code=status.HTTP_200_OK, + summary="Cadastra uma parte", + response_description="Cadastra uma parte", +) +async def save( + ato_parte_schema: AtoParteSaveSchema, + current_user: dict = Depends(get_current_user), +): + + # Efetua o cadastro da parte junto ao banco de dados + response = ato_parte_controller.save(ato_parte_schema) + + # Retorna os dados localizados + return response + + +# Atualiza os dados de uma parte +@router.put( + "/{ato_parte_id}", + status_code=status.HTTP_200_OK, + summary="Atualiza uma parte", + response_description="Atualiza uma parte", +) +async def update( + ato_parte_id: int, + ato_parte_schema: AtoParteUpdateSchema, + current_user: dict = Depends(get_current_user), +): + + # Efetua a atualização dos dados da parte + response = ato_parte_controller.update(ato_parte_id, ato_parte_schema) + + # Retorna os dados localizados + return response + + +# Exclui uma determinada parte +@router.delete( + "/{ato_parte_id}", + status_code=status.HTTP_200_OK, + summary="Remove uma parte", + response_description="Remove uma parte", +) +async def delete(ato_parte_id: int, current_user: dict = Depends(get_current_user)): + + # Cria o schema com os dados recebidos + ato_parte_schema = AtoParteSchema(ato_parte_id=ato_parte_id) + + # Efetua a exclusão de uma determinada parte + response = ato_parte_controller.delete(ato_parte_schema) + + # Retorna os dados localizados + return response diff --git a/packages/v1/administrativo/endpoints/ato_principal_endpoint.py b/packages/v1/administrativo/endpoints/ato_principal_endpoint.py index fe67098..be147a8 100644 --- a/packages/v1/administrativo/endpoints/ato_principal_endpoint.py +++ b/packages/v1/administrativo/endpoints/ato_principal_endpoint.py @@ -1,22 +1,132 @@ # Importação de bibliotecas +from typing import List +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.ato_principal_controller import AtoPrincipalController +from packages.v1.administrativo.controllers.ato_principal_controller import ( + AtoPrincipalController, +) +from packages.v1.administrativo.schemas.ato_principal_schema import ( + AtoPrincipalSchema, + AtoPrincipalSaveSchema, + AtoPrincipalUpdateSchema, + AtoPrincipalIdSchema, +) -# Inicializar o roteaodr para as rotas de produtos +# Inicializa o roteador para as rotas de ato principal router = APIRouter() -# Instãnciamento do controller desejado +# 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 +# Lista todos os atos principais +@router.get( + "/", + status_code=status.HTTP_200_OK, + summary="Lista todos os atos principais cadastrados", + response_description="Lista todos os atos principais cadastrados", +) +async def index(current_user: dict = Depends(get_current_user)): + + # Busca todos os atos principais cadastrados response = ato_principal_controller.index() - # Retornar os dados localizados - return response \ No newline at end of file + # Retorna os dados localizados + return response + + +# Localiza um ato principal pelo ID +@router.get( + "/{ato_principal_id}", + status_code=status.HTTP_200_OK, + summary="Busca um registro em especifico pelo ID do ato principal", + response_description="Busca um registro em especifico", +) +async def show(ato_principal_id: int, current_user: dict = Depends(get_current_user)): + + # Cria o schema com os dados recebidos + ato_principal_schema = AtoPrincipalIdSchema(ato_principal_id=ato_principal_id) + + # Busca um ato principal especifico pelo ID + response = ato_principal_controller.show(ato_principal_schema) + + # Retorna os dados localizados + return response + + +# Cadastro de múltiplos itens +@router.post( + "/batch", + status_code=status.HTTP_200_OK, + summary="Cadastra múltiplos atos principais", + response_description="Cadastra vários atos principais de uma vez", +) +async def save_multiple( + atos_principais: List[AtoPrincipalSaveSchema], + current_user: dict = Depends(get_current_user), +): + # A lista completa (List[AtoPrincipalSaveSchema]) é passada + # DIRETAMENTE para o controller (que a passará ao Service, Action e Repository). + # O loop de iteração deve estar APENAS no Repository. + responses = ato_principal_controller.save_multiple(atos_principais) + + return {"success": True, "data": responses} + + +# Cadastro de ato principal +@router.post( + "/", + status_code=status.HTTP_200_OK, + summary="Cadastra um ato principal", + response_description="Cadastra um ato principal", +) +async def save( + ato_principal_schema: AtoPrincipalSaveSchema, + current_user: dict = Depends(get_current_user), +): + + # Efetua o cadastro do ato principal junto ao banco de dados + response = ato_principal_controller.save(ato_principal_schema) + + # Retorna os dados localizados + return response + + +# Atualiza os dados de ato principal +@router.put( + "/{ato_principal_id}", + status_code=status.HTTP_200_OK, + summary="Atualiza um ato principal", + response_description="Atualiza um ato principal", +) +async def update( + ato_principal_id: int, + ato_principal_schema: AtoPrincipalUpdateSchema, + current_user: dict = Depends(get_current_user), +): + + # Efetua a atualização dos dados de ato principal + response = ato_principal_controller.update(ato_principal_id, ato_principal_schema) + + # Retorna os dados localizados + return response + + +# Exclui um determinado ato principal +@router.delete( + "/{ato_principal_id}", + status_code=status.HTTP_200_OK, + summary="Remove um ato principal", + response_description="Remove um ato principal", +) +async def delete(ato_principal_id: int, current_user: dict = Depends(get_current_user)): + + # Cria o schema com os dados recebidos + ato_principal_schema = AtoPrincipalIdSchema(ato_principal_id=ato_principal_id) + + # Efetua a exclusão de um determinado ato principal + response = ato_principal_controller.delete(ato_principal_schema) + + # Retorna os dados localizados + return response diff --git a/packages/v1/administrativo/endpoints/usuario_endpoint.py b/packages/v1/administrativo/endpoints/usuario_endpoint.py new file mode 100644 index 0000000..9d3daa6 --- /dev/null +++ b/packages/v1/administrativo/endpoints/usuario_endpoint.py @@ -0,0 +1,164 @@ +# 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.usuario_controller import UsuarioController +from packages.v1.administrativo.schemas.usuario_schema import ( + UsuarioSchema, + UsuarioAuthenticateSchema, + UsuarioSaveSchema, + UsuarioUpdateSchema, + UsuarioEmailSchema, + UsuarioIdSchema, +) + +# Inicializa o roteador para as rotas de usuário +router = APIRouter() + +# Instânciamento do controller desejado +user_controller = UsuarioController() + + +# Autenticação de usuário +@router.post( + "/authenticate", + 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(user_authenticate_schema: UsuarioAuthenticateSchema): + + # Efetua a autenticação de um usuário junto ao sistema + response = user_controller.authenticate(user_authenticate_schema) + + # Retorna os dados localizados + return response + + +# Dados do usuário logado +@router.get( + "/me", + status_code=status.HTTP_200_OK, + summary="Retorna os dados do usuário que efetuou o login", + response_description="Dados do usuário que efetuou o login", +) +async def me(current_user: dict = Depends(get_current_user)): + + # Busca os dados do usuário logado + response = user_controller.me(current_user) + + # Retorna os dados localizados + return response + + +# Lista todos os usuários +@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 = user_controller.index() + + # Retorna os dados localizados + return response + + +# Localiza um usuário pelo email +@router.get( + "/email/{email}", + status_code=status.HTTP_200_OK, + summary="Busca um registro em especifico por e-mail informado", + response_description="Busca um registro em especifico", +) +async def getEmail(email: str, current_user: dict = Depends(get_current_user)): + + # Cria o schema com os dados recebidos + usuario_schema = UsuarioEmailSchema(email=email) + + print(usuario_schema) + + # Busca um usuário especifico pelo e-mail + response = user_controller.getEmail(usuario_schema) + + # Retorna os dados localizados + return response + + +# Localiza um usuário pelo ID +@router.get( + "/{usuario_id}", + status_code=status.HTTP_200_OK, + summary="Busca um registro em especifico pelo ID do usuário", + response_description="Busca um registro em especifico", +) +async def show(usuario_id: int, current_user: dict = Depends(get_current_user)): + + # Cria o schema com os dados recebidos + usuario_schema = UsuarioIdSchema(usuario_id=usuario_id) + + # Busca um usuário especifico pelo ID + response = user_controller.show(usuario_schema) + + # Retorna os dados localizados + return response + + +# Cadastro de usuários +@router.post( + "/", + status_code=status.HTTP_200_OK, + summary="Cadastra um usuário", + response_description="Cadastra um usuário", +) +async def save( + usuario_schema: UsuarioSaveSchema, current_user: dict = Depends(get_current_user) +): + + # Efetua o cadastro do usuário junto ao banco de dados + response = user_controller.save(usuario_schema) + + # Retorna os dados localizados + return response + + +# Atualiza os dados de usuário +@router.put( + "/{usuario_id}", + status_code=status.HTTP_200_OK, + summary="Atualiza um usuário", + response_description="Atualiza um usuário", +) +async def update( + usuario_id: int, + usuario_schema: UsuarioUpdateSchema, + current_user: dict = Depends(get_current_user), +): + + # Efetua a atualização dos dados de usuário + response = user_controller.update(usuario_id, usuario_schema) + + # Retorna os dados localizados + return response + + +# Exclui um determinado usuário +@router.delete( + "/{usuario_id}", + status_code=status.HTTP_200_OK, + summary="Remove um usuário", + response_description="Remove um usuário", +) +async def delete(usuario_id: int, current_user: dict = Depends(get_current_user)): + + # Cria o schema com os dados recebidos + usuario_schema = UsuarioIdSchema(usuario_id=usuario_id) + + # Efetua a exclusão de um determinado usuário + response = user_controller.delete(usuario_schema) + + # Retorna os dados localizados + return response diff --git a/packages/v1/administrativo/models/ato_documento_model.py b/packages/v1/administrativo/models/ato_documento_model.py new file mode 100644 index 0000000..0f493f3 --- /dev/null +++ b/packages/v1/administrativo/models/ato_documento_model.py @@ -0,0 +1,62 @@ +# packages/v1/administrativo/models/ato_documento_model.py +# Gerado a partir da DDL da tabela 'ato_documento' + +from sqlalchemy import ( + Column, + BigInteger, + String, + DateTime, + Text, # Necessário para o campo 'url' + ForeignKey, # Necessário para a chave estrangeira +) + +from sqlalchemy.sql import func + +# Importa Base do MySQL (assumindo que o caminho 'database.mysql' é o correto) +from database.mysql import Base + + +class AtoDocumento(Base): + """ + Representa o modelo da tabela 'ato_documento' no banco de dados MySQL. + Mapeia a DDL fornecida. + """ + + __tablename__ = "ato_documento" + + # ato_documento_id bigint unsigned NOT NULL AUTO_INCREMENT, PRIMARY KEY + ato_documento_id = Column(BigInteger, primary_key=True, autoincrement=True) + + # ato_principal_id bigint NOT NULL, FOREIGN KEY + # Mapeamento da chave estrangeira + ato_principal_id = Column( + BigInteger, ForeignKey("ato_principal.ato_principal_id"), nullable=False + ) + + # url text NOT NULL (URL pública HTTPS do documento) + url = Column(Text, nullable=False) + + # nome_documento varchar(255) NOT NULL (Nome do arquivo PDF) + nome_documento = Column(String(255), nullable=False) + + # tipo_documento varchar(50) NOT NULL (Tipo textual do documento) + tipo_documento = Column(String(50), nullable=False) + + # created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP + created_at = Column(DateTime, server_default=func.now(), nullable=False) + + # updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + updated_at = Column( + DateTime, + server_default=func.now(), # Default CURRENT_TIMESTAMP + onupdate=func.now(), # ON UPDATE CURRENT_TIMESTAMP + nullable=False, + ) + + def __repr__(self): + """Representação legível do objeto.""" + return ( + f"" + ) diff --git a/packages/v1/administrativo/models/ato_parte_model.py b/packages/v1/administrativo/models/ato_parte_model.py new file mode 100644 index 0000000..bc8c059 --- /dev/null +++ b/packages/v1/administrativo/models/ato_parte_model.py @@ -0,0 +1,61 @@ +# packages/v1/administrativo/models/ato_parte_model.py +# Gerado a partir da DDL da tabela 'ato_parte' + +from sqlalchemy import ( + Column, + BigInteger, + String, + DateTime, + ForeignKey, +) + +from sqlalchemy.sql import func + +# Importa Base do MySQL (assumindo que o caminho 'database.mysql' é o correto) +from database.mysql import Base + + +class AtoParte(Base): + """ + Representa o modelo da tabela 'ato_parte' no banco de dados MySQL. + Mapeia a DDL fornecida. + """ + + __tablename__ = "ato_parte" + + # ato_parte_id bigint NOT NULL AUTO_INCREMENT, PRIMARY KEY + ato_parte_id = Column(BigInteger, primary_key=True, autoincrement=True) + + # ato_principal_id bigint NOT NULL, FOREIGN KEY + # Mapeamento da chave estrangeira + ato_principal_id = Column( + BigInteger, ForeignKey("ato_principal.ato_principal_id"), nullable=False + ) + + # nome varchar(255) NOT NULL (Nome completo da parte envolvida no ato.) + nome = Column(String(255), nullable=False) + + # telefone varchar(20) DEFAULT NULL (Telefone da parte com DDI e DDD.) + telefone = Column(String(20), nullable=True) + + # cpf_cnpj varchar(20) NOT NULL (CPF ou CNPJ da parte, contendo apenas números.) + cpf_cnpj = Column(String(20), nullable=False) + + # created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP + created_at = Column(DateTime, server_default=func.now(), nullable=False) + + # updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + updated_at = Column( + DateTime, + server_default=func.now(), # Default CURRENT_TIMESTAMP + onupdate=func.now(), # ON UPDATE CURRENT_TIMESTAMP + nullable=False, + ) + + def __repr__(self): + """Representação legível do objeto.""" + return ( + f"" + ) diff --git a/packages/v1/administrativo/models/ato_principal_model.py b/packages/v1/administrativo/models/ato_principal_model.py index bb205da..398b24a 100644 --- a/packages/v1/administrativo/models/ato_principal_model.py +++ b/packages/v1/administrativo/models/ato_principal_model.py @@ -11,26 +11,36 @@ from sqlalchemy import ( ForeignKey, CheckConstraint, ) -from sqlalchemy.dialects.postgresql import INET + +# No MySQL, usaremos String(45) para armazenar endereços IPv4/IPv6 from sqlalchemy.sql import func -from database.postgres import Base + +# Atualize para importar Base do MySQL +from database.mysql import Base class AtoPrincipal(Base): """ - Representa o modelo da tabela 'ato_principal' no banco de dados PostgreSQL. + Representa o modelo da tabela 'ato_principal' no banco de dados MySQL. """ __tablename__ = "ato_principal" - ato_principal_id = Column(BigInteger, primary_key=True, autoincrement=True, index=True) + # ID principal do ato + ato_principal_id = Column( + BigInteger, primary_key=True, autoincrement=True, index=True + ) + # Relacionamento com o próprio ato (auto-relacionamento) origem_ato_principal_id = Column( BigInteger, - ForeignKey("ato_principal.ato_principal_id", ondelete="SET NULL", onupdate="CASCADE"), + ForeignKey( + "ato_principal.ato_principal_id", ondelete="SET NULL", onupdate="CASCADE" + ), nullable=True, ) + # Campos principais identificacao_pedido_cgj = Column(BigInteger, nullable=False) tipo_ato = Column(Integer, nullable=False) codigo_selo = Column(String(50), nullable=False, unique=True) @@ -38,19 +48,36 @@ class AtoPrincipal(Base): 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) + + # Substituído INET por String(45) + # O tamanho 45 cobre IPv6, IPv4 e localhost + ip_maquina = Column(String(45), nullable=True) + + # Conteúdo principal do ato inteiro_teor = Column(Text, nullable=False) + + # Valores financeiros 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) + + # Protocolos opcionais 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) + # Controle de auditoria + 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 + # Mantém constraint de valores positivos __table_args__ = ( CheckConstraint( "(COALESCE(valor_entrada, 0) >= 0) AND (emolumento >= 0) AND (taxa_judiciaria >= 0) AND (fundos_estaduais >= 0)", @@ -59,6 +86,7 @@ class AtoPrincipal(Base): ) def __repr__(self): + """Representação legível do objeto.""" return ( f"" diff --git a/packages/v1/administrativo/models/usuario_model.py b/packages/v1/administrativo/models/usuario_model.py new file mode 100644 index 0000000..bb91170 --- /dev/null +++ b/packages/v1/administrativo/models/usuario_model.py @@ -0,0 +1,59 @@ +# packages/v1/administrativo/models/usuario_model.py +# Gerado a partir da DDL corrigida da tabela 'usuario' + +from sqlalchemy import ( + Column, + BigInteger, # Alterado para BigInteger para usuario_id e IDs de auditoria + String, + DateTime, + # Não há Text, Numeric ou CheckConstraint nesta DDL +) + +from sqlalchemy.sql import func + +# Importa Base do MySQL (assumindo que o caminho 'database.mysql' é o correto) +from database.mysql import Base + + +class Usuario(Base): + """ + Representa o modelo da tabela 'usuario' no banco de dados MySQL. + Mapeia a DDL corrigida. + """ + + __tablename__ = "usuario" + + # usuario_id bigint NOT NULL AUTO_INCREMENT, PRIMARY KEY + usuario_id = Column(BigInteger, primary_key=True, autoincrement=True) + + # Campos principais (varchar) + nome = Column(String(255), nullable=True) + email = Column(String(255), nullable=True) + username = Column(String(120), nullable=True) + password = Column(String(255), nullable=True) + + # status varchar(1) NOT NULL DEFAULT 'A' + status = Column(String(1), nullable=False, default="A") + + # date_register datetime NOT NULL DEFAULT CURRENT_TIMESTAMP + date_register = Column(DateTime(), server_default=func.now(), nullable=False) + + # date_update datetime DEFAULT NULL + date_update = Column(DateTime(), onupdate=func.now(), nullable=True) + + # usuario_id_create bigint DEFAULT NULL + user_id_create = Column(BigInteger, nullable=True) + + # user_id_update bigint DEFAULT NULL + user_id_update = Column(BigInteger, nullable=True) + + # Não há __table_args__ (como CheckConstraint) na DDL original + + def __repr__(self): + """Representação legível do objeto.""" + return ( + f"" + ) diff --git a/packages/v1/administrativo/repositories/ato_documento/ato_documento_delete_repository.py b/packages/v1/administrativo/repositories/ato_documento/ato_documento_delete_repository.py new file mode 100644 index 0000000..0e13aae --- /dev/null +++ b/packages/v1/administrativo/repositories/ato_documento/ato_documento_delete_repository.py @@ -0,0 +1,63 @@ +from fastapi import HTTPException, status +from database.mysql import SessionLocal +from packages.v1.administrativo.models.ato_documento_model import AtoDocumento +from packages.v1.administrativo.schemas.ato_documento_schema import AtoDocumentoSchema + + +class DeleteRepository: + """ + Classe responsável por excluir documentos no banco de dados. + Segue a mesma metodologia do SaveRepository/UpdateRepository: + - abre sessão + - busca registro + - trata not found + - deleta/commita + - rollback em erro + - fecha sessão + """ + + def execute(self, ato_documento_schema: AtoDocumentoSchema): + db = SessionLocal() + + try: + # 1. Buscar documento + documento_db = ( + db.query(AtoDocumento) + .filter( + AtoDocumento.ato_documento_id + == ato_documento_schema.ato_documento_id + ) + .first() + ) + + if not documento_db: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Documento não encontrado.", + ) + + # 2. Excluir (delete físico) + db.delete(documento_db) + db.commit() + + return { + "success": True, + "message": "Documento excluído com sucesso!", + "data": { + "ato_documento_id": ato_documento_schema.ato_documento_id, + }, + } + + except HTTPException: + db.rollback() + raise + + except Exception as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Erro ao excluir documento: {e}", + ) + + finally: + db.close() diff --git a/packages/v1/administrativo/repositories/ato_documento/ato_documento_index_repository.py b/packages/v1/administrativo/repositories/ato_documento/ato_documento_index_repository.py new file mode 100644 index 0000000..223c2da --- /dev/null +++ b/packages/v1/administrativo/repositories/ato_documento/ato_documento_index_repository.py @@ -0,0 +1,41 @@ +from database.mysql import SessionLocal, get_database_settings +from sqlalchemy import func +from packages.v1.administrativo.models.ato_documento_model import AtoDocumento +from packages.v1.administrativo.schemas.ato_documento_schema import ( + AtoDocumentoSchema, +) + +# === Recupera as configurações do banco === +DB_SETTINGS = get_database_settings() +# Mantemos a referência a AES_KEY por consistência do ambiente, mas não a utilizamos. +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) + + +class IndexRepository: + def execute(self): + # Cria a sessão dentro do repositório + db = SessionLocal() + try: + # Executa a query, selecionando todos os campos de ato_documento + result = db.query( + AtoDocumento.ato_documento_id, + AtoDocumento.ato_principal_id, + func.AES_DECRYPT(AtoDocumento.url, AES_KEY).label("url"), + func.AES_DECRYPT(AtoDocumento.nome_documento, AES_KEY).label( + "nome_documento" + ), + func.AES_DECRYPT(AtoDocumento.tipo_documento, AES_KEY).label( + "tipo_documento" + ), + AtoDocumento.created_at, + AtoDocumento.updated_at, + ).all() + + # Converte os models SQLAlchemy em schemas Pydantic + data = [AtoDocumentoSchema.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() diff --git a/packages/v1/administrativo/repositories/ato_documento/ato_documento_save_repository.py b/packages/v1/administrativo/repositories/ato_documento/ato_documento_save_repository.py new file mode 100644 index 0000000..0155b64 --- /dev/null +++ b/packages/v1/administrativo/repositories/ato_documento/ato_documento_save_repository.py @@ -0,0 +1,67 @@ +import os +from fastapi import HTTPException, status +from sqlalchemy import func +from database.mysql import SessionLocal, get_database_settings +from database.mysql import SessionLocal +from packages.v1.administrativo.models.ato_documento_model import AtoDocumento +from packages.v1.administrativo.schemas.ato_documento_schema import ( + AtoDocumentoSaveSchema, +) + +# pega do mesmo lugar que o engine pega +DB_SETTINGS = get_database_settings() + +# A chave AES não é utilizada neste modelo, mas é mantida por consistência do ambiente +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) + + +class SaveRepository: + """ + Classe responsável por salvar (inserir) novos documentos no banco de dados. + """ + + def execute(self, ato_documento_schema: AtoDocumentoSaveSchema): + db = SessionLocal() + + try: + # Não há verificações de unicidade específicas para documentos neste contexto. + + # 1) monta o objeto com os dados fornecidos pelo schema + new_documento = AtoDocumento( + ato_principal_id=ato_documento_schema.ato_principal_id, + ) + + # Criptografa os dados sensiveis + new_documento.url = func.AES_ENCRYPT(ato_documento_schema.url, AES_KEY) + new_documento.nome_documento = func.AES_ENCRYPT( + ato_documento_schema.nome_documento, AES_KEY + ) + new_documento.tipo_documento = func.AES_ENCRYPT( + ato_documento_schema.tipo_documento, AES_KEY + ) + + # 2) persiste + db.add(new_documento) + db.commit() + db.refresh(new_documento) + + return { + "success": True, + "message": "Documento criado com sucesso!", + "data": { + "ato_documento_id": new_documento.ato_documento_id, + "ato_principal_id": new_documento.ato_principal_id, + }, + } + + except HTTPException: + db.rollback() + raise + except Exception as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Erro ao salvar documento: {e}", + ) + finally: + db.close() diff --git a/packages/v1/administrativo/repositories/ato_documento/ato_documento_show_repository.py b/packages/v1/administrativo/repositories/ato_documento/ato_documento_show_repository.py new file mode 100644 index 0000000..69bce88 --- /dev/null +++ b/packages/v1/administrativo/repositories/ato_documento/ato_documento_show_repository.py @@ -0,0 +1,53 @@ +from typing import Optional +from database.mysql import SessionLocal, get_database_settings +from sqlalchemy import func +from packages.v1.administrativo.models.ato_documento_model import AtoDocumento +from packages.v1.administrativo.schemas.ato_documento_schema import AtoDocumentoSchema + +# === Recupera as configurações do banco === +DB_SETTINGS = get_database_settings() +# AES_KEY não é utilizada neste modelo, mas mantemos o padrão de importação +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) + + +class ShowRepository: + """ + Repositório responsável por buscar um documento pelo ID. + Retorna `AtoDocumentoSchema` se encontrar, senão `None`. + """ + + def execute(self, ato_documento_schema: AtoDocumentoSchema): + db = SessionLocal() + + try: + # 1. pega o ID que veio do schema + ato_documento_id_to_find = ato_documento_schema.ato_documento_id + + # 2. busca no banco, selecionando todos os campos da DDL + result = ( + db.query( + AtoDocumento.ato_documento_id, + AtoDocumento.ato_principal_id, + func.AES_DECRYPT(AtoDocumento.url, AES_KEY).label("url"), + func.AES_DECRYPT(AtoDocumento.nome_documento, AES_KEY).label( + "nome_documento" + ), + func.AES_DECRYPT(AtoDocumento.tipo_documento, AES_KEY).label( + "tipo_documento" + ), + AtoDocumento.created_at, + AtoDocumento.updated_at, + ) + .filter(AtoDocumento.ato_documento_id == ato_documento_id_to_find) + .first() + ) + + # 3. se não achou, devolve None (comportamento de SHOW) + if result is None: + return None + + # 4. se achou, converte para pydantic + return AtoDocumentoSchema.model_validate(result) + + finally: + db.close() diff --git a/packages/v1/administrativo/repositories/ato_documento/ato_documento_update_repository.py b/packages/v1/administrativo/repositories/ato_documento/ato_documento_update_repository.py new file mode 100644 index 0000000..8caf2fd --- /dev/null +++ b/packages/v1/administrativo/repositories/ato_documento/ato_documento_update_repository.py @@ -0,0 +1,91 @@ +from datetime import datetime +from fastapi import HTTPException, status +from database.mysql import SessionLocal, get_database_settings +from sqlalchemy import func +from packages.v1.administrativo.models.ato_documento_model import AtoDocumento +from packages.v1.administrativo.schemas.ato_documento_schema import ( + AtoDocumentoUpdateSchema, +) + +# === Recupera as configurações do banco === +DB_SETTINGS = get_database_settings() +# Mantemos a referência a AES_KEY por consistência do ambiente, mas não a utilizamos. +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) + + +class UpdateRepository: + """ + Classe responsável por atualizar documentos no banco de dados. + """ + + def execute( + self, ato_documento_id: int, ato_documento_schema: AtoDocumentoUpdateSchema + ): + db = SessionLocal() + + try: + # 1. Busca o documento existente + documento_db = ( + db.query(AtoDocumento) + .filter(AtoDocumento.ato_documento_id == ato_documento_id) + .first() + ) + + if not documento_db: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Documento não encontrado.", + ) + + # 2. Atualiza os campos se o valor foi fornecido no schema + + # ato_principal_id (bigint NOT NULL) + if ato_documento_schema.ato_principal_id is not None: + documento_db.ato_principal_id = ato_documento_schema.ato_principal_id + + # url (text NOT NULL) + if ato_documento_schema.url is not None: + documento_db.url = func.AES_ENCRYPT(ato_documento_schema.url, AES_KEY) + + # nome_documento (varchar(255) NOT NULL) + if ato_documento_schema.nome_documento is not None: + documento_db.nome_documento = func.AES_ENCRYPT( + ato_documento_schema.nome_documento, AES_KEY + ) + + # tipo_documento (varchar(50) NOT NULL) + if ato_documento_schema.tipo_documento is not None: + documento_db.tipo_documento = func.AES_ENCRYPT( + ato_documento_schema.tipo_documento, AES_KEY + ) + + # Nota: updated_at é atualizado automaticamente pelo ORM (onupdate=func.now()) + + # 3. Persiste as alterações + db.add(documento_db) + db.commit() + db.refresh(documento_db) + + # 4. Retorna o resultado + return { + "success": True, + "message": "Documento atualizado com sucesso!", + "data": { + "ato_documento_id": documento_db.ato_documento_id, + "ato_principal_id": documento_db.ato_principal_id, + }, + } + + except HTTPException: + db.rollback() + raise + + except Exception as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Erro ao atualizar documento: {e}", + ) + + finally: + db.close() diff --git a/packages/v1/administrativo/repositories/ato_documento/ato_principal_show_repository.py b/packages/v1/administrativo/repositories/ato_documento/ato_principal_show_repository.py new file mode 100644 index 0000000..ecab158 --- /dev/null +++ b/packages/v1/administrativo/repositories/ato_documento/ato_principal_show_repository.py @@ -0,0 +1,60 @@ +from fastapi import HTTPException, status +from database.mysql import SessionLocal, get_database_settings +from sqlalchemy import func +from packages.v1.administrativo.models.ato_documento_model import AtoDocumento +from packages.v1.administrativo.schemas.ato_documento_schema import AtoDocumentoSchema + +# === Recupera as configurações do banco === +# A chave AES não é necessária para este model, mas mantemos as configurações por consistência +DB_SETTINGS = get_database_settings() +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) # compatível com SimpleNamespace + + +class ShowRepository: + + # O esquema de entrada deve conter o ato_documento_id para a busca + def execute(self, ato_documento_schema: AtoDocumentoSchema): + + # Cria a sessão dentro do repositório + db = SessionLocal() + + try: + + # 1. Obtém o ID do documento + ato_principal_id_to_find = ato_documento_schema.ato_principal_id + + # 2. Executa a query com filtro, selecionando todos os campos do DDL + result = ( + db.query( + AtoDocumento.ato_documento_id, + AtoDocumento.ato_principal_id, + func.AES_DECRYPT(AtoDocumento.url, AES_KEY).label("url"), + func.AES_DECRYPT(AtoDocumento.nome_documento, AES_KEY).label( + "nome_documento" + ), + func.AES_DECRYPT(AtoDocumento.tipo_documento, AES_KEY).label( + "tipo_documento" + ), + AtoDocumento.created_at, + AtoDocumento.updated_at, + ) + .filter(AtoDocumento.ato_principal_id == ato_principal_id_to_find) + .all() + ) + + # 3. Verifica se o documento foi encontrado + if not result: + # Lança uma exceção HTTP 404 (Not Found) se não houver resultado + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Documento '{ato_principal_id_to_find}' não encontrado.", + ) + + # 4. Converte os models SQLAlchemy em schemas Pydantic + data = [AtoDocumentoSchema.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() diff --git a/packages/v1/administrativo/repositories/ato_parte/ato_parte_delete_repository.py b/packages/v1/administrativo/repositories/ato_parte/ato_parte_delete_repository.py new file mode 100644 index 0000000..e951cb2 --- /dev/null +++ b/packages/v1/administrativo/repositories/ato_parte/ato_parte_delete_repository.py @@ -0,0 +1,60 @@ +from fastapi import HTTPException, status +from database.mysql import SessionLocal +from packages.v1.administrativo.models.ato_parte_model import AtoParte +from packages.v1.administrativo.schemas.ato_parte_schema import AtoParteSchema + + +class DeleteRepository: + """ + Classe responsável por excluir partes (pessoas físicas ou jurídicas) no banco de dados. + Segue a mesma metodologia do SaveRepository/UpdateRepository: + - abre sessão + - busca registro + - trata not found + - deleta/commita + - rollback em erro + - fecha sessão + """ + + def execute(self, ato_parte_schema: AtoParteSchema): + db = SessionLocal() + + try: + # 1. Buscar a parte pelo ID + parte_db = ( + db.query(AtoParte) + .filter(AtoParte.ato_parte_id == ato_parte_schema.ato_parte_id) + .first() + ) + + if not parte_db: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Parte não encontrada.", + ) + + # 2. Excluir (delete físico) + db.delete(parte_db) + db.commit() + + return { + "success": True, + "message": "Parte excluída com sucesso!", + "data": { + "ato_parte_id": ato_parte_schema.ato_parte_id, + }, + } + + except HTTPException: + db.rollback() + raise + + except Exception as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Erro ao excluir parte: {e}", + ) + + finally: + db.close() diff --git a/packages/v1/administrativo/repositories/ato_parte/ato_parte_index_repository.py b/packages/v1/administrativo/repositories/ato_parte/ato_parte_index_repository.py new file mode 100644 index 0000000..4c07845 --- /dev/null +++ b/packages/v1/administrativo/repositories/ato_parte/ato_parte_index_repository.py @@ -0,0 +1,41 @@ +from database.mysql import SessionLocal, get_database_settings +from sqlalchemy import func +from packages.v1.administrativo.models.ato_parte_model import AtoParte +from packages.v1.administrativo.schemas.ato_parte_schema import ( + AtoParteSchema, +) + +# === Recupera as configurações do banco === +DB_SETTINGS = get_database_settings() +# Mantemos a referência a AES_KEY por consistência do ambiente, mas não a utilizamos. +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) + + +class IndexRepository: + """ + Classe responsável por listar todas as partes cadastradas. + """ + + def execute(self): + # Cria a sessão dentro do repositório + db = SessionLocal() + try: + # Executa a query, selecionando todos os campos de ato_parte + result = db.query( + AtoParte.ato_parte_id, + AtoParte.ato_principal_id, + func.AES_DECRYPT(AtoParte.nome, AES_KEY).label("nome"), + func.AES_DECRYPT(AtoParte.telefone, AES_KEY).label("telefone"), + func.AES_DECRYPT(AtoParte.cpf_cnpj, AES_KEY).label("cpf_cnpj"), + AtoParte.created_at, + AtoParte.updated_at, + ).all() + + # Converte os models SQLAlchemy em schemas Pydantic + data = [AtoParteSchema.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() diff --git a/packages/v1/administrativo/repositories/ato_parte/ato_parte_save_repository.py b/packages/v1/administrativo/repositories/ato_parte/ato_parte_save_repository.py new file mode 100644 index 0000000..071c7ef --- /dev/null +++ b/packages/v1/administrativo/repositories/ato_parte/ato_parte_save_repository.py @@ -0,0 +1,60 @@ +import os +from fastapi import HTTPException, status +from sqlalchemy import func +from database.mysql import SessionLocal, get_database_settings +from packages.v1.administrativo.models.ato_parte_model import AtoParte +from packages.v1.administrativo.schemas.ato_parte_schema import ( + AtoParteSaveSchema, +) + +# pega do mesmo lugar que o engine pega +DB_SETTINGS = get_database_settings() + +# A chave AES não é utilizada neste modelo, mas é mantida por consistência do ambiente +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) + + +class SaveRepository: + """ + Classe responsável por salvar (inserir) novas partes (pessoas físicas ou jurídicas) no banco de dados. + """ + + def execute(self, ato_parte_schema: AtoParteSaveSchema): + db = SessionLocal() + + try: + # 1) monta o objeto com os dados fornecidos pelo schema + new_parte = AtoParte( + ato_principal_id=ato_parte_schema.ato_principal_id, + ) + + # Para os campos sensíveis (nomes, telefones, cpf/cnpj), atribui expressão SQL para criptografia + new_parte.nome = func.AES_ENCRYPT(ato_parte_schema.nome, AES_KEY) + new_parte.telefone = func.AES_ENCRYPT(ato_parte_schema.telefone, AES_KEY) + new_parte.cpf_cnpj = func.AES_ENCRYPT(ato_parte_schema.cpf_cnpj, AES_KEY) + + # 2) persiste + db.add(new_parte) + db.commit() + db.refresh(new_parte) + + return { + "success": True, + "message": "Parte criada com sucesso!", + "data": { + "ato_parte_id": new_parte.ato_parte_id, + "ato_principal_id": new_parte.ato_principal_id, + }, + } + + except HTTPException: + db.rollback() + raise + except Exception as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Erro ao salvar parte: {e}", + ) + finally: + db.close() diff --git a/packages/v1/administrativo/repositories/ato_parte/ato_parte_show_repository.py b/packages/v1/administrativo/repositories/ato_parte/ato_parte_show_repository.py new file mode 100644 index 0000000..a11b413 --- /dev/null +++ b/packages/v1/administrativo/repositories/ato_parte/ato_parte_show_repository.py @@ -0,0 +1,49 @@ +from typing import Optional +from database.mysql import SessionLocal, get_database_settings +from sqlalchemy import func +from packages.v1.administrativo.models.ato_parte_model import AtoParte +from packages.v1.administrativo.schemas.ato_parte_schema import AtoParteSchema + +# === Recupera as configurações do banco === +DB_SETTINGS = get_database_settings() +# AES_KEY não é utilizada neste modelo, mas mantemos o padrão de importação +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) + + +class ShowRepository: + """ + Repositório responsável por buscar uma parte (pessoa física ou jurídica) pelo ID. + Retorna `AtoParteSchema` se encontrar, senão `None`. + """ + + def execute(self, ato_parte_schema: AtoParteSchema): + db = SessionLocal() + + try: + # 1. Pega o ID que veio do schema + ato_parte_id_to_find = ato_parte_schema.ato_parte_id + + # 2. Busca no banco, selecionando todos os campos da DDL + result = ( + db.query( + AtoParte.ato_parte_id, + AtoParte.ato_principal_id, + func.AES_DECRYPT(AtoParte.nome, AES_KEY).label("nome"), + func.AES_DECRYPT(AtoParte.telefone, AES_KEY).label("telefone"), + func.AES_DECRYPT(AtoParte.cpf_cnpj, AES_KEY).label("cpf_cnpj"), + AtoParte.created_at, + AtoParte.updated_at, + ) + .filter(AtoParte.ato_parte_id == ato_parte_id_to_find) + .first() + ) + + # 3. Se não achou, devolve None (comportamento de SHOW) + if result is None: + return None + + # 4. Se achou, converte para pydantic + return AtoParteSchema.model_validate(result) + + finally: + db.close() diff --git a/packages/v1/administrativo/repositories/ato_parte/ato_parte_update_repository.py b/packages/v1/administrativo/repositories/ato_parte/ato_parte_update_repository.py new file mode 100644 index 0000000..376d600 --- /dev/null +++ b/packages/v1/administrativo/repositories/ato_parte/ato_parte_update_repository.py @@ -0,0 +1,84 @@ +from datetime import datetime +from fastapi import HTTPException, status +from sqlalchemy import func +from database.mysql import SessionLocal, get_database_settings +from packages.v1.administrativo.models.ato_parte_model import AtoParte +from packages.v1.administrativo.schemas.ato_parte_schema import ( + AtoParteUpdateSchema, +) + +# pega do mesmo lugar que o engine pega +DB_SETTINGS = get_database_settings() + +# A chave AES não é utilizada neste modelo, mas é mantida por consistência do ambiente +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) + + +class UpdateRepository: + """ + Classe responsável por atualizar partes no banco de dados. + """ + + def execute(self, ato_parte_id: int, ato_parte_schema: AtoParteUpdateSchema): + db = SessionLocal() + + try: + # 1. Busca a parte existente + parte_db = ( + db.query(AtoParte).filter(AtoParte.ato_parte_id == ato_parte_id).first() + ) + + if not parte_db: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Parte não encontrada.", + ) + + # 2. Atualiza os campos se o valor foi fornecido no schema + + # ato_principal_id (bigint NOT NULL) + if ato_parte_schema.ato_principal_id is not None: + parte_db.ato_principal_id = ato_parte_schema.ato_principal_id + + # nome (varchar(255) NOT NULL) + if ato_parte_schema.nome is not None: + parte_db.nome = func.AES_ENCRYPT(ato_parte_schema.nome, AES_KEY) + + # telefone (varchar(20) DEFAULT NULL) + if ato_parte_schema.telefone is not None: + parte_db.telefone = func.AES_ENCRYPT(ato_parte_schema.telefone, AES_KEY) + + # cpf_cnpj (varchar(20) NOT NULL) + if ato_parte_schema.cpf_cnpj is not None: + parte_db.cpf_cnpj = func.AES_ENCRYPT(ato_parte_schema.cpf_cnpj, AES_KEY) + + # Nota: updated_at é atualizado automaticamente pelo ORM (onupdate=func.now() ou similar) + + # 3. Persiste as alterações + db.add(parte_db) + db.commit() + db.refresh(parte_db) + + # 4. Retorna o resultado + return { + "success": True, + "message": "Parte atualizada com sucesso!", + "data": { + "ato_parte_id": parte_db.ato_parte_id, + "ato_principal_id": parte_db.ato_principal_id, + }, + } + + except HTTPException: + db.rollback() + raise + + except Exception as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Erro ao atualizar parte: {e}", + ) + + finally: + db.close() diff --git a/packages/v1/administrativo/repositories/ato_parte/ato_principal_show_repository.py b/packages/v1/administrativo/repositories/ato_parte/ato_principal_show_repository.py new file mode 100644 index 0000000..1692133 --- /dev/null +++ b/packages/v1/administrativo/repositories/ato_parte/ato_principal_show_repository.py @@ -0,0 +1,60 @@ +from fastapi import HTTPException, status +from database.mysql import SessionLocal, get_database_settings +from sqlalchemy import func +from packages.v1.administrativo.models.ato_parte_model import AtoParte +from packages.v1.administrativo.schemas.ato_parte_schema import AtoParteSchema + +# === Recupera as configurações do banco === +# A chave AES não é necessária para este model, mas mantemos as configurações por consistência +DB_SETTINGS = get_database_settings() +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) # compatível com SimpleNamespace + + +class ShowRepository: + """ + Classe responsável por buscar partes de um ato principal no banco de dados. + """ + + # O esquema de entrada deve conter o ato_principal_id para a busca + def execute(self, ato_parte_schema: AtoParteSchema): + + # Cria a sessão dentro do repositório + db = SessionLocal() + + try: + + # 1. Obtém o ID do ato principal + ato_principal_id_to_find = ato_parte_schema.ato_principal_id + + # 2. Executa a query com filtro, selecionando todos os campos do DDL + result = ( + db.query( + AtoParte.ato_parte_id, + AtoParte.ato_principal_id, + func.AES_DECRYPT(AtoParte.nome, AES_KEY).label("nome"), + func.AES_DECRYPT(AtoParte.telefone, AES_KEY).label("telefone"), + func.AES_DECRYPT(AtoParte.cpf_cnpj, AES_KEY).label("cpf_cnpj"), + AtoParte.created_at, + AtoParte.updated_at, + ) + .filter(AtoParte.ato_principal_id == ato_principal_id_to_find) + .all() + ) + + # 3. Verifica se o resultado foi encontrado + if not result: + # Lança uma exceção HTTP 404 (Not Found) se não houver resultado + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Nenhuma parte encontrada para o ato principal ID '{ato_principal_id_to_find}'.", + ) + + # 4. Converte os models SQLAlchemy em schemas Pydantic + # Nota: É importante que o schema 'AtoParteSchema' esteja pronto para receber todos os campos. + data = [AtoParteSchema.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() diff --git a/packages/v1/administrativo/repositories/ato_principal/ato_principal_delete_repository.py b/packages/v1/administrativo/repositories/ato_principal/ato_principal_delete_repository.py new file mode 100644 index 0000000..b9a1771 --- /dev/null +++ b/packages/v1/administrativo/repositories/ato_principal/ato_principal_delete_repository.py @@ -0,0 +1,63 @@ +from fastapi import HTTPException, status +from database.mysql import SessionLocal +from packages.v1.administrativo.models.ato_principal_model import AtoPrincipal +from packages.v1.administrativo.schemas.ato_principal_schema import AtoPrincipalIdSchema + + +class DeleteRepository: + """ + Classe responsável por excluir atos principais no banco de dados. + Segue a mesma metodologia do SaveRepository/UpdateRepository: + - abre sessão + - busca registro + - trata not found + - deleta/commita + - rollback em erro + - fecha sessão + """ + + def execute(self, ato_principal_schema: AtoPrincipalIdSchema): + db = SessionLocal() + + try: + # 1. Buscar ato principal + ato_principal_db = ( + db.query(AtoPrincipal) + .filter( + AtoPrincipal.ato_principal_id + == ato_principal_schema.ato_principal_id + ) + .first() + ) + + if not ato_principal_db: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Ato principal não encontrado.", + ) + + # 2. Excluir (delete físico) + db.delete(ato_principal_db) + db.commit() + + return { + "success": True, + "message": "Ato principal excluído com sucesso!", + "data": { + "ato_principal_id": ato_principal_schema.ato_principal_id, + }, + } + + except HTTPException: + db.rollback() + raise + + except Exception as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Erro ao excluir ato principal: {e}", + ) + + finally: + db.close() diff --git a/packages/v1/administrativo/repositories/ato_principal/ato_principal_index_repository.py b/packages/v1/administrativo/repositories/ato_principal/ato_principal_index_repository.py index 7a94a96..59e7e5e 100644 --- a/packages/v1/administrativo/repositories/ato_principal/ato_principal_index_repository.py +++ b/packages/v1/administrativo/repositories/ato_principal/ato_principal_index_repository.py @@ -1,21 +1,52 @@ -from database.postgres import SessionLocal +from database.mysql import SessionLocal, get_database_settings +from sqlalchemy import func from packages.v1.administrativo.models.ato_principal_model import AtoPrincipal -from packages.v1.administrativo.schemas.ato_principal_schema import AtoPrincipalResponseSchema +from packages.v1.administrativo.schemas.ato_principal_schema import ( + AtoPrincipalSchema, +) + +# === Recupera as configurações do banco === +DB_SETTINGS = get_database_settings() +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) # compatível com SimpleNamespace -class AtoPrincipalIndexRepository: +class IndexRepository: def execute(self): # Cria a sessão dentro do repositório db = SessionLocal() try: # Executa a query - result = db.query(AtoPrincipal).all() + result = db.query( + AtoPrincipal.ato_principal_id, + AtoPrincipal.origem_ato_principal_id, + AtoPrincipal.identificacao_pedido_cgj, + AtoPrincipal.tipo_ato, + AtoPrincipal.codigo_selo, + AtoPrincipal.codigo_ato, + func.AES_DECRYPT(AtoPrincipal.nome_civil_ato, AES_KEY).label( + "nome_civil_ato" + ), + func.AES_DECRYPT( + AtoPrincipal.nome_serventuario_praticou_ato, AES_KEY + ).label("nome_serventuario_praticou_ato"), + AtoPrincipal.data_solicitacao, + AtoPrincipal.ip_maquina, + AtoPrincipal.inteiro_teor, + AtoPrincipal.valor_entrada, + AtoPrincipal.emolumento, + AtoPrincipal.taxa_judiciaria, + AtoPrincipal.fundos_estaduais, + AtoPrincipal.protocolo_protesto, + AtoPrincipal.protocolo_imovel, + AtoPrincipal.created_at, + AtoPrincipal.updated_at, + ).all() # Converte os models SQLAlchemy em schemas Pydantic - data = [AtoPrincipalResponseSchema.model_validate(obj) for obj in result] + data = [AtoPrincipalSchema.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() \ No newline at end of file + db.close() diff --git a/packages/v1/administrativo/repositories/ato_principal/ato_principal_save_multiple_repository.py b/packages/v1/administrativo/repositories/ato_principal/ato_principal_save_multiple_repository.py new file mode 100644 index 0000000..c5fcb63 --- /dev/null +++ b/packages/v1/administrativo/repositories/ato_principal/ato_principal_save_multiple_repository.py @@ -0,0 +1,200 @@ +from typing import List, Optional +from fastapi import HTTPException, status +import traceback +from sqlalchemy import func +from sqlalchemy.orm import Session # Importação para tipagem da session +from database.mysql import SessionLocal, get_database_settings +from packages.v1.administrativo.models.ato_principal_model import AtoPrincipal +from packages.v1.administrativo.models.ato_parte_model import AtoParte +from packages.v1.administrativo.models.ato_documento_model import AtoDocumento +from packages.v1.administrativo.schemas.ato_principal_schema import ( + AtoPrincipalSaveSchema, +) + +# Configuração da Chave AES +DB_SETTINGS = get_database_settings() +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) + + +class SaveMultipleRepository: + """ + Repositório para salvar múltiplos atos principais com suas partes e documentos, + usando criptografia nativa do MySQL via AES_ENCRYPT. + Implementa lógica recursiva para atos vinculados. + """ + + def _save_single_recursive_transaction( + self, + db: Session, + ato_schema: AtoPrincipalSaveSchema, + parent_ato_principal_id: Optional[int] = None, + ) -> AtoPrincipal: + """ + Salva um único Ato Principal, seus filhos (partes/documentos) e + chama recursivamente o salvamento dos atos vinculados. + """ + codigo_selo = ato_schema.codigo_selo + + # 1. Pré-processamento e Criptografia + ato_data = ato_schema.model_dump( + exclude_unset=True, + exclude={"ato_partes", "ato_documentos", "atos_vinculados"}, + ) + + # Define o ID de origem se for um ato vinculado + if parent_ato_principal_id is not None: + ato_data["origem_ato_principal_id"] = parent_ato_principal_id + elif ato_data.get("origem_ato_principal_id") is None: + ato_data["origem_ato_principal_id"] = None + + # Verifica duplicidade usando descriptografia + existing_ato = ( + db.query(AtoPrincipal) + .filter(func.aes_decrypt(AtoPrincipal.codigo_selo, AES_KEY) == codigo_selo) + .first() + ) + + if existing_ato: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=f"O Código do Selo '{codigo_selo}' já está cadastrado.", + ) + + campos_criptografar = [ + "nome_civil_ato", + "nome_serventuario_praticou_ato", + ] + + # Criptografa os campos de texto necessários + for campo in campos_criptografar: + valor = ato_data.get(campo) + if isinstance(valor, str) and valor.strip(): + ato_data[campo] = func.aes_encrypt(valor, AES_KEY) + elif campo not in ato_data or valor is None: + ato_data[campo] = None + + # 2. Criação e Persistência do Ato Principal + new_ato = AtoPrincipal(**ato_data) + db.add(new_ato) + db.flush() + new_ato_id = new_ato.ato_principal_id + + # 3. Salva os Filhos Diretos: Ato Partes + for parte in ato_schema.ato_partes: + parte_data = parte.model_dump(exclude_unset=True) + + parte_campos_criptografar = ["nome", "telefone", "cpf_cnpj"] + + for campo in parte_campos_criptografar: + valor = parte_data.get(campo) + # A validação/conversão para string já foi feita pelo Pydantic, + # mas mantemos a checagem de tipo e strip para segurança antes da criptografia + if isinstance(valor, str) and valor.strip(): + parte_data[campo] = func.aes_encrypt(valor, AES_KEY) + else: + parte_data[campo] = None + + new_parte = AtoParte(**parte_data, ato_principal_id=new_ato_id) + db.add(new_parte) + + # 4. Salva os Filhos Diretos: Ato Documentos + for doc in ato_schema.ato_documentos: + doc_data = doc.model_dump(exclude_unset=True) + + doc_campos_criptografar = ["url", "nome_documento", "tipo_documento"] + + for campo in doc_campos_criptografar: + valor = doc_data.get(campo) + if isinstance(valor, str) and valor.strip(): + doc_data[campo] = func.aes_encrypt(valor, AES_KEY) + else: + doc_data[campo] = None + + new_documento = AtoDocumento(**doc_data, ato_principal_id=new_ato_id) + db.add(new_documento) + + # 5. Lógica Recursiva para Atos Vinculados (Estrutura de loop idêntica) + if ato_schema.atos_vinculados: + print(f"--- Iniciando salvamento recursivo para {new_ato_id} ---") + + # A iteração é semelhante, mas o que é executado é a chamada recursiva + for linked_ato_schema in ato_schema.atos_vinculados: + # O ato vinculado é persistido chamando a rotina de salvamento completa + self._save_single_recursive_transaction( + db, + linked_ato_schema, + parent_ato_principal_id=new_ato_id, + ) + + print(f"--- Fim do salvamento recursivo para {new_ato_id} ---") + + return new_ato + + # Método principal (chamado pela Action) + def execute(self, atos_principais: List[AtoPrincipalSaveSchema]): + db = SessionLocal() + results = [] + + # 1. Checa a chave AES + if not AES_KEY: + db.close() + raise Exception("A chave AES (aeskey) não está configurada.") + + # 2. Loop principal: Cada iteração é uma transação completa (incluindo recursão) + for ato_schema in atos_principais: + codigo_selo_log = getattr(ato_schema, "codigo_selo", "SELO_INDISPONÍVEL") + + try: + # A rotina completa de salvamento (incluindo filhos e recursão) é encapsulada + saved_ato = self._save_single_recursive_transaction( + db, + ato_schema, + parent_ato_principal_id=None, # Ato de nível superior + ) + + # ---------- COMMIT final para a transação completa (Ato Principal + Filhos + Atos Vinculados) ---------- + db.commit() + + # Retorno de sucesso + ato_result = { + "success": True, + "message": "Ato Principal e vinculados salvos com sucesso", + "data": { + "ato_principal_id": saved_ato.ato_principal_id, + "codigo_selo": codigo_selo_log, + "tipo_ato": saved_ato.tipo_ato, + }, + } + results.append(ato_result) + + # Tratamento de erro específico para duplicidade ou HTTP + except HTTPException as he: + db.rollback() + results.append( + { + "success": False, + "error": he.detail, + "data": {"codigo_selo": codigo_selo_log}, + } + ) + + # Tratamento de erro genérico + except Exception as e: + db.rollback() + + # Log completo do erro + print("====") + print(f"ERRO DE PERSISTÊNCIA NO SELO: {codigo_selo_log}") + traceback.print_exc() + print("----") + + results.append( + { + "success": False, + "error": "Erro ao salvar o registro. Verifique o formato dos dados ou os logs do servidor.", + "data": {"codigo_selo": codigo_selo_log}, + } + ) + + db.close() + return results diff --git a/packages/v1/administrativo/repositories/ato_principal/ato_principal_save_repository.py b/packages/v1/administrativo/repositories/ato_principal/ato_principal_save_repository.py new file mode 100644 index 0000000..62429c4 --- /dev/null +++ b/packages/v1/administrativo/repositories/ato_principal/ato_principal_save_repository.py @@ -0,0 +1,105 @@ +import os +from fastapi import HTTPException, status +from sqlalchemy import func +from database.mysql import SessionLocal, get_database_settings +from database.mysql import SessionLocal +from packages.v1.administrativo.models.ato_principal_model import AtoPrincipal +from packages.v1.administrativo.schemas.ato_principal_schema import ( + AtoPrincipalSaveSchema, +) + +# pega do mesmo lugar que o engine pega +DB_SETTINGS = get_database_settings() + +# pega aeskey de forma compatível com SimpleNamespace +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) + + +class SaveRepository: + """ + Classe responsável por salvar (inserir) novos atos principais no banco de dados. + Campos de nome criptografados via AES_ENCRYPT (nativo MySQL) usando só ORM. + """ + + def execute(self, ato_principal_schema: AtoPrincipalSaveSchema): + db = SessionLocal() + + try: + # 1) verifica codigo_selo (texto puro) + existing_by_selo = ( + db.query(AtoPrincipal) + .filter(AtoPrincipal.codigo_selo == ato_principal_schema.codigo_selo) + .first() + ) + if existing_by_selo: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Já existe um ato principal com esse código de selo.", + ) + + # 2) verifica codigo_ato (texto puro) + existing_by_codigo = ( + db.query(AtoPrincipal) + .filter(AtoPrincipal.codigo_ato == ato_principal_schema.codigo_ato) + .first() + ) + if existing_by_codigo: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Já existe um ato principal com esse código de ato.", + ) + + # 3) monta o objeto com todos os campos da DDL + new_ato = AtoPrincipal( + origem_ato_principal_id=ato_principal_schema.origem_ato_principal_id, + identificacao_pedido_cgj=ato_principal_schema.identificacao_pedido_cgj, + tipo_ato=ato_principal_schema.tipo_ato, + codigo_selo=ato_principal_schema.codigo_selo, + codigo_ato=ato_principal_schema.codigo_ato, + data_solicitacao=ato_principal_schema.data_solicitacao, + ip_maquina=ato_principal_schema.ip_maquina, + inteiro_teor=ato_principal_schema.inteiro_teor, + valor_entrada=ato_principal_schema.valor_entrada, + emolumento=ato_principal_schema.emolumento, + taxa_judiciaria=ato_principal_schema.taxa_judiciaria, + fundos_estaduais=ato_principal_schema.fundos_estaduais, + protocolo_protesto=ato_principal_schema.protocolo_protesto, + protocolo_imovel=ato_principal_schema.protocolo_imovel, + ) + + # 4) para os campos sensíveis (nomes), atribui expressão SQL para criptografia + new_ato.nome_civil_ato = func.AES_ENCRYPT( + ato_principal_schema.nome_civil_ato, AES_KEY + ) + new_ato.nome_serventuario_praticou_ato = func.AES_ENCRYPT( + ato_principal_schema.nome_serventuario_praticou_ato, AES_KEY + ) + + # 5) persiste + db.add(new_ato) + db.commit() + db.refresh(new_ato) + + return { + "success": True, + "message": "Ato principal criado com sucesso!", + "data": { + "ato_principal_id": new_ato.ato_principal_id, + "codigo_selo": ato_principal_schema.codigo_selo, + "codigo_ato": ato_principal_schema.codigo_ato, + "nome_civil_ato": ato_principal_schema.nome_civil_ato, + "data_solicitacao": ato_principal_schema.data_solicitacao.isoformat(), + }, + } + + except HTTPException: + db.rollback() + raise + except Exception as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Erro ao salvar ato principal: {e}", + ) + finally: + db.close() diff --git a/packages/v1/administrativo/repositories/ato_principal/ato_principal_show_repository.py b/packages/v1/administrativo/repositories/ato_principal/ato_principal_show_repository.py new file mode 100644 index 0000000..e9fc0ae --- /dev/null +++ b/packages/v1/administrativo/repositories/ato_principal/ato_principal_show_repository.py @@ -0,0 +1,78 @@ +from typing import Optional +from database.mysql import SessionLocal, get_database_settings +from sqlalchemy import func +from packages.v1.administrativo.models.ato_principal_model import AtoPrincipal +from packages.v1.administrativo.schemas.ato_principal_schema import AtoPrincipalSchema +from packages.v1.administrativo.schemas.ato_principal_schema import ( + AtoPrincipalIdSchema, +) # Assumindo um schema para receber o ID + +# === Recupera as configurações do banco === +# A chave AES (AES_KEY) não será utilizada neste repositório, +# pois nenhum campo da tabela 'ato_principal' parece requerer +# criptografia/decriptografia no banco de dados, ao contrário +# do 'usuario' original. + +# === Recupera as configurações do banco === +DB_SETTINGS = get_database_settings() +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) # compatível com SimpleNamespace + + +class ShowRepository: + """ + Repositório responsável por buscar um ato principal pelo ato_principal_id. + Retorna `AtoPrincipalSchema` se encontrar, senão `None`. + """ + + # O esquema de entrada deve ser aquele que contém o ID necessário. + def execute( + self, ato_principal_id_schema: AtoPrincipalIdSchema + ) -> Optional[AtoPrincipalSchema]: + db = SessionLocal() + + try: + # 1. pega o ID que veio do schema de entrada + # Ajuste: Assumindo que o schema de entrada (AtoPrincipalIdSchema) tem o campo 'ato_principal_id' + ato_principal_id_to_find = ato_principal_id_schema.ato_principal_id + + # 2. busca no banco, selecionando explicitamente todos os campos da DDL + result = ( + db.query( + AtoPrincipal.ato_principal_id, + AtoPrincipal.origem_ato_principal_id, + AtoPrincipal.identificacao_pedido_cgj, + AtoPrincipal.tipo_ato, + AtoPrincipal.codigo_selo, + AtoPrincipal.codigo_ato, + func.AES_DECRYPT(AtoPrincipal.nome_civil_ato, AES_KEY).label( + "nome_civil_ato" + ), + func.AES_DECRYPT( + AtoPrincipal.nome_serventuario_praticou_ato, AES_KEY + ).label("nome_serventuario_praticou_ato"), + AtoPrincipal.data_solicitacao, + AtoPrincipal.ip_maquina, + AtoPrincipal.inteiro_teor, + AtoPrincipal.valor_entrada, + AtoPrincipal.emolumento, + AtoPrincipal.taxa_judiciaria, + AtoPrincipal.fundos_estaduais, + AtoPrincipal.protocolo_protesto, + AtoPrincipal.protocolo_imovel, + AtoPrincipal.created_at, + AtoPrincipal.updated_at, + ) + .filter(AtoPrincipal.ato_principal_id == ato_principal_id_to_find) + .first() + ) + + # 3. se não achou, devolve None + if result is None: + return None + + # 4. se achou, converte para pydantic + # (Assumindo que AtoPrincipalSchema tem Config.from_attributes = True) + return AtoPrincipalSchema.model_validate(result) + + finally: + db.close() diff --git a/packages/v1/administrativo/repositories/ato_principal/ato_principal_update_repository.py b/packages/v1/administrativo/repositories/ato_principal/ato_principal_update_repository.py new file mode 100644 index 0000000..a39ecf3 --- /dev/null +++ b/packages/v1/administrativo/repositories/ato_principal/ato_principal_update_repository.py @@ -0,0 +1,169 @@ +from datetime import datetime +from typing import Dict, Any +from fastapi import HTTPException, status +from sqlalchemy import func +from database.mysql import SessionLocal, get_database_settings +from packages.v1.administrativo.models.ato_principal_model import AtoPrincipal +from packages.v1.administrativo.schemas.ato_principal_schema import ( + AtoPrincipalUpdateSchema, +) + +# pega as configurações do banco +DB_SETTINGS = get_database_settings() +# pega a chave AES do banco (mesma usada no save) +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) + + +class UpdateRepository: + """ + Classe responsável por atualizar atos principais no banco de dados. + Campos de nome são criptografados com AES_ENCRYPT (nativo MySQL). + """ + + def execute( + self, ato_principal_id: int, ato_principal_schema: AtoPrincipalUpdateSchema + ) -> Dict[str, Any]: + db = SessionLocal() + + try: + # 1. Busca o ato principal existente + ato_principal_db = ( + db.query(AtoPrincipal) + .filter(AtoPrincipal.ato_principal_id == ato_principal_id) + .first() + ) + + if not ato_principal_db: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Ato principal não encontrado.", + ) + + # 2. Verifica duplicidade de codigo_selo e codigo_ato + if ato_principal_schema.codigo_selo or ato_principal_schema.codigo_ato: + duplicidade_selo = None + if ato_principal_schema.codigo_selo: + duplicidade_selo = ( + db.query(AtoPrincipal) + .filter(AtoPrincipal.ato_principal_id != ato_principal_id) + .filter( + AtoPrincipal.codigo_selo == ato_principal_schema.codigo_selo + ) + .first() + ) + + duplicidade_ato = None + if ato_principal_schema.codigo_ato and not duplicidade_selo: + duplicidade_ato = ( + db.query(AtoPrincipal) + .filter(AtoPrincipal.ato_principal_id != ato_principal_id) + .filter( + AtoPrincipal.codigo_ato == ato_principal_schema.codigo_ato + ) + .first() + ) + + if duplicidade_selo or duplicidade_ato: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Já existe outro ato principal com este código de selo ou código de ato.", + ) + + # 3. Atualiza campos comuns (não criptografados) + if ato_principal_schema.origem_ato_principal_id is not None: + ato_principal_db.origem_ato_principal_id = ( + ato_principal_schema.origem_ato_principal_id + ) + + if ato_principal_schema.identificacao_pedido_cgj is not None: + ato_principal_db.identificacao_pedido_cgj = ( + ato_principal_schema.identificacao_pedido_cgj + ) + + if ato_principal_schema.tipo_ato is not None: + ato_principal_db.tipo_ato = ato_principal_schema.tipo_ato + + if ato_principal_schema.codigo_selo is not None: + ato_principal_db.codigo_selo = ato_principal_schema.codigo_selo + + if ato_principal_schema.codigo_ato is not None: + ato_principal_db.codigo_ato = ato_principal_schema.codigo_ato + + if ato_principal_schema.data_solicitacao is not None: + ato_principal_db.data_solicitacao = ( + ato_principal_schema.data_solicitacao + ) + + if ato_principal_schema.ip_maquina is not None: + ato_principal_db.ip_maquina = ato_principal_schema.ip_maquina + + if ato_principal_schema.inteiro_teor is not None: + ato_principal_db.inteiro_teor = ato_principal_schema.inteiro_teor + + if ato_principal_schema.valor_entrada is not None: + ato_principal_db.valor_entrada = ato_principal_schema.valor_entrada + + if ato_principal_schema.emolumento is not None: + ato_principal_db.emolumento = ato_principal_schema.emolumento + + if ato_principal_schema.taxa_judiciaria is not None: + ato_principal_db.taxa_judiciaria = ato_principal_schema.taxa_judiciaria + + if ato_principal_schema.fundos_estaduais is not None: + ato_principal_db.fundos_estaduais = ( + ato_principal_schema.fundos_estaduais + ) + + if ato_principal_schema.protocolo_protesto is not None: + ato_principal_db.protocolo_protesto = ( + ato_principal_schema.protocolo_protesto + ) + + if ato_principal_schema.protocolo_imovel is not None: + ato_principal_db.protocolo_imovel = ( + ato_principal_schema.protocolo_imovel + ) + + # 4. Atualiza campos criptografados se enviados + if ato_principal_schema.nome_civil_ato is not None: + ato_principal_db.nome_civil_ato = func.AES_ENCRYPT( + ato_principal_schema.nome_civil_ato, AES_KEY + ) + + if ato_principal_schema.nome_serventuario_praticou_ato is not None: + ato_principal_db.nome_serventuario_praticou_ato = func.AES_ENCRYPT( + ato_principal_schema.nome_serventuario_praticou_ato, AES_KEY + ) + + # 5. Atualiza o timestamp de modificação + ato_principal_db.updated_at = datetime.now() + + # 6. Persiste as alterações + db.add(ato_principal_db) + db.commit() + db.refresh(ato_principal_db) + + return { + "success": True, + "message": "Ato principal atualizado com sucesso!", + "data": { + "ato_principal_id": ato_principal_db.ato_principal_id, + "codigo_selo": ato_principal_db.codigo_selo, + "codigo_ato": ato_principal_db.codigo_ato, + "identificacao_pedido_cgj": ato_principal_db.identificacao_pedido_cgj, + }, + } + + except HTTPException: + db.rollback() + raise + + except Exception as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Erro ao atualizar ato principal: {e}", + ) + + finally: + db.close() diff --git a/packages/v1/administrativo/repositories/usuario/usuario_delete_repository.py b/packages/v1/administrativo/repositories/usuario/usuario_delete_repository.py new file mode 100644 index 0000000..2d3e105 --- /dev/null +++ b/packages/v1/administrativo/repositories/usuario/usuario_delete_repository.py @@ -0,0 +1,60 @@ +from fastapi import HTTPException, status +from database.mysql import SessionLocal +from packages.v1.administrativo.models.usuario_model import Usuario +from packages.v1.administrativo.schemas.usuario_schema import UsuarioIdSchema + + +class DeleteRepository: + """ + Classe responsável por excluir usuários no banco de dados. + Segue a mesma metodologia do SaveRepository/UpdateRepository: + - abre sessão + - busca registro + - trata not found + - deleta/commita + - rollback em erro + - fecha sessão + """ + + def execute(self, usuario_schema: UsuarioIdSchema): + db = SessionLocal() + + try: + # 1. Buscar usuário + usuario_db = ( + db.query(Usuario) + .filter(Usuario.usuario_id == usuario_schema.usuario_id) + .first() + ) + + if not usuario_db: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Usuário não encontrado.", + ) + + # 2. Excluir (delete físico) + db.delete(usuario_db) + db.commit() + + return { + "success": True, + "message": "Usuário excluído com sucesso!", + "data": { + "usuario_id": usuario_schema.usuario_id, + }, + } + + except HTTPException: + db.rollback() + raise + + except Exception as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Erro ao excluir usuário: {e}", + ) + + finally: + db.close() diff --git a/packages/v1/administrativo/repositories/usuario/usuario_get_by_authenticate_repository.py b/packages/v1/administrativo/repositories/usuario/usuario_get_by_authenticate_repository.py new file mode 100644 index 0000000..e1272a6 --- /dev/null +++ b/packages/v1/administrativo/repositories/usuario/usuario_get_by_authenticate_repository.py @@ -0,0 +1,63 @@ +from fastapi import HTTPException, status +from database.mysql import SessionLocal, get_database_settings +from sqlalchemy import func +from packages.v1.administrativo.models.usuario_model import Usuario +from packages.v1.administrativo.schemas.usuario_schema import ( + UsuarioAuthenticateSchema, +) + +# === Recupera as configurações do banco === +DB_SETTINGS = get_database_settings() +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) # compatível com SimpleNamespace + + +class GetByAuthenticateRepository: + + def execute(self, user_authenticate_schema: UsuarioAuthenticateSchema): + """ + Busca um usuário pelo email fornecido no schema e retorna seus dados. + Retorna HTTP 404 se não encontrado. + """ + + # Cria a sessão dentro do repositório + db = SessionLocal() + try: + # 1. Obtém o email do schema de autenticação + username_to_find = user_authenticate_schema.username + + # 2. Executa a query com filtro + # Substituímos .all() por .filter() e .first() + # Usamos Usuario.username para acessar a coluna do Model + result = ( + db.query( + Usuario.usuario_id, + func.AES_DECRYPT(Usuario.nome, AES_KEY).label("nome"), + func.AES_DECRYPT(Usuario.email, AES_KEY).label("email"), + Usuario.username, + Usuario.password, + Usuario.status, + Usuario.date_update, + Usuario.user_id_create, + Usuario.user_id_update, + ) + .filter(Usuario.username == username_to_find) + .first() + ) + + # 3. Verifica se o usuário foi encontrado + if not result: + # Lança uma exceção HTTP 404 (Not Found) se não houver resultado + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Usuário '{username_to_find}' não encontrado.", + ) + + # 4. Converte o model SQLAlchemy (um único objeto) para o schema Pydantic + # Note que não é mais uma lista, e sim um único objeto + data = UsuarioAuthenticateSchema.model_validate(result) + + return data + + finally: + # Fecha a sessão após o uso (evita vazamento de conexão) + db.close() diff --git a/packages/v1/administrativo/repositories/usuario/usuario_get_by_email_repository.py b/packages/v1/administrativo/repositories/usuario/usuario_get_by_email_repository.py new file mode 100644 index 0000000..4a5bae5 --- /dev/null +++ b/packages/v1/administrativo/repositories/usuario/usuario_get_by_email_repository.py @@ -0,0 +1,62 @@ +from typing import Optional +from sqlalchemy import func +from database.mysql import SessionLocal, get_database_settings +from packages.v1.administrativo.models.usuario_model import Usuario +from packages.v1.administrativo.schemas.usuario_schema import ( + UsuarioEmailSchema, + UsuarioSchema, +) + +# === Recupera as configurações do banco === +DB_SETTINGS = get_database_settings() +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) # compatível com SimpleNamespace + + +class GetByUsuarioEmailRepository: + """ + Repositório responsável por buscar um usuário pelo e-mail. + Retorna `UsuarioSchema` se encontrar, senão `None`. + """ + + def execute(self, usuario_schema: UsuarioEmailSchema) -> Optional[UsuarioSchema]: + db = SessionLocal() + try: + # 1. Pega o e-mail que veio do schema (texto puro) + usuario_email_to_find = usuario_schema.email + + # 2. Faz a busca com descriptografia AES_DECRYPT diretamente no SQL + result = ( + db.query( + Usuario.usuario_id, + func.AES_DECRYPT(Usuario.nome, AES_KEY).label("nome"), + func.AES_DECRYPT(Usuario.email, AES_KEY).label("email"), + Usuario.username, + Usuario.password, + Usuario.status, + Usuario.date_update, + Usuario.user_id_create, + Usuario.user_id_update, + ) + .filter( + func.AES_DECRYPT(Usuario.email, AES_KEY) == usuario_email_to_find + ) + .first() + ) + + # 3. Se não achou, devolve None + if not result: + return None + + # 4. Converte os campos criptografados de bytes → string + def to_str(value): + return ( + value.decode("utf-8") + if isinstance(value, (bytes, bytearray)) + else value + ) + + # 5. Valida e retorna via schema Pydantic + return UsuarioSchema.model_validate(result) + + finally: + db.close() diff --git a/packages/v1/administrativo/repositories/usuario/usuario_get_by_usuario_id_repository.py b/packages/v1/administrativo/repositories/usuario/usuario_get_by_usuario_id_repository.py new file mode 100644 index 0000000..d6f9b08 --- /dev/null +++ b/packages/v1/administrativo/repositories/usuario/usuario_get_by_usuario_id_repository.py @@ -0,0 +1,59 @@ +from fastapi import HTTPException, status +from database.mysql import SessionLocal, get_database_settings +from sqlalchemy import func +from packages.v1.administrativo.models.usuario_model import Usuario +from packages.v1.administrativo.schemas.usuario_schema import UsuarioSchema + +# === Recupera as configurações do banco === +DB_SETTINGS = get_database_settings() +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) # compatível com SimpleNamespace + + +class GetByUsuarioIdRepository: + + def execute(self, usuario_schema=UsuarioSchema): + + # Cria a sessão dentro do repositório + db = SessionLocal() + + try: + + # 1. Obtém o email do schema de autenticação + usuario_id_to_find = usuario_schema.usuario_id + + # 2. Executa a query com filtro + # Substituímos .all() por .filter() e .first() + # Usamos Usuario.username para acessar a coluna do Model + result = ( + db.query( + Usuario.usuario_id, + func.AES_DECRYPT(Usuario.nome, AES_KEY).label("nome"), + func.AES_DECRYPT(Usuario.email, AES_KEY).label("email"), + Usuario.username, + Usuario.password, + Usuario.status, + Usuario.date_update, + Usuario.user_id_create, + Usuario.user_id_update, + ) + .filter(Usuario.usuario_id == usuario_id_to_find) + .first() + ) + + # 3. Verifica se o usuário foi encontrado + if not result: + # Lança uma exceção HTTP 404 (Not Found) se não houver resultado + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Usuário '{usuario_id_to_find}' não encontrado.", + ) + + # 4. Converte o model SQLAlchemy (um único objeto) para o schema Pydantic + # Note que não é mais uma lista, e sim um único objeto + data = UsuarioSchema.model_validate(result) + + return data + + finally: + # Fecha a sessão após o uso (evita vazamento de conexão) + db.close() diff --git a/packages/v1/administrativo/repositories/usuario/usuario_index_repository.py b/packages/v1/administrativo/repositories/usuario/usuario_index_repository.py new file mode 100644 index 0000000..1d4abfd --- /dev/null +++ b/packages/v1/administrativo/repositories/usuario/usuario_index_repository.py @@ -0,0 +1,38 @@ +from database.mysql import SessionLocal, get_database_settings +from sqlalchemy import func +from packages.v1.administrativo.models.usuario_model import Usuario +from packages.v1.administrativo.schemas.usuario_schema import ( + UsuarioSchema, +) + +# === Recupera as configurações do banco === +DB_SETTINGS = get_database_settings() +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) # compatível com SimpleNamespace + + +class IndexRepository: + def execute(self): + # Cria a sessão dentro do repositório + db = SessionLocal() + try: + # Executa a query + result = db.query( + Usuario.usuario_id, + func.AES_DECRYPT(Usuario.nome, AES_KEY).label("nome"), + func.AES_DECRYPT(Usuario.email, AES_KEY).label("email"), + Usuario.username, + Usuario.password, + Usuario.status, + Usuario.date_update, + Usuario.user_id_create, + Usuario.user_id_update, + ).all() + + # Converte os models SQLAlchemy em schemas Pydantic + data = [UsuarioSchema.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() diff --git a/packages/v1/administrativo/repositories/usuario/usuario_save_repository.py b/packages/v1/administrativo/repositories/usuario/usuario_save_repository.py new file mode 100644 index 0000000..6fa43fe --- /dev/null +++ b/packages/v1/administrativo/repositories/usuario/usuario_save_repository.py @@ -0,0 +1,91 @@ +import os +from fastapi import HTTPException, status +from sqlalchemy import func +from database.mysql import SessionLocal, get_database_settings +from database.mysql import SessionLocal +from packages.v1.administrativo.models.usuario_model import Usuario +from packages.v1.administrativo.schemas.usuario_schema import UsuarioSaveSchema + +# pega do mesmo lugar que o engine pega +DB_SETTINGS = get_database_settings() + +# pega aeskey de forma compatível com SimpleNamespace +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) + + +class SaveRepository: + """ + Classe responsável por salvar (inserir) novos usuários no banco de dados. + Nome e e-mail criptografados via AES_ENCRYPT (nativo MySQL) usando só ORM. + """ + + def execute(self, usuario_schema: UsuarioSaveSchema): + db = SessionLocal() + + try: + # 1) verifica username (texto puro) + existing_by_username = ( + db.query(Usuario) + .filter(Usuario.username == usuario_schema.username) + .first() + ) + if existing_by_username: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Já existe um usuário com esse nome de usuário.", + ) + + # 2) verifica e-mail (está criptografado no banco) + existing_by_email = ( + db.query(Usuario) + .filter( + func.AES_DECRYPT(Usuario.email, AES_KEY) == usuario_schema.email + ) + .first() + ) + if existing_by_email: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Já existe um usuário com esse e-mail.", + ) + + # 3) monta o objeto normalmente + new_user = Usuario( + username=usuario_schema.username, + password=usuario_schema.password, # ideal: já vir hash + status=usuario_schema.status or "A", + user_id_create=usuario_schema.user_id_create, + ) + + # 4) para os campos sensíveis, atribui expressão SQL + new_user.nome = func.AES_ENCRYPT(usuario_schema.nome, AES_KEY) + new_user.email = func.AES_ENCRYPT(usuario_schema.email, AES_KEY) + + # 5) persiste + db.add(new_user) + db.commit() + db.refresh(new_user) + + return { + "success": True, + "message": "Usuário criado com sucesso!", + "data": { + "usuario_id": new_user.usuario_id, + "nome": usuario_schema.nome, + "email": usuario_schema.email, + "username": usuario_schema.username, + "status": new_user.status, + }, + } + + except HTTPException: + db.rollback() + raise + except Exception as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Erro ao salvar usuário: {e}", + ) + finally: + db.close() diff --git a/packages/v1/administrativo/repositories/usuario/usuario_show_repository.py b/packages/v1/administrativo/repositories/usuario/usuario_show_repository.py new file mode 100644 index 0000000..55c756b --- /dev/null +++ b/packages/v1/administrativo/repositories/usuario/usuario_show_repository.py @@ -0,0 +1,51 @@ +from typing import Optional +from database.mysql import SessionLocal, get_database_settings +from sqlalchemy import func +from packages.v1.administrativo.models.usuario_model import Usuario +from packages.v1.administrativo.schemas.usuario_schema import UsuarioSchema + +# === Recupera as configurações do banco === +DB_SETTINGS = get_database_settings() +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) # compatível com SimpleNamespace + + +class ShowRepository: + """ + Repositório responsável por buscar um usuário pelo e-mail. + Retorna `UsuarioSchema` se encontrar, senão `None`. + """ + + def execute(self, usuario_schema: UsuarioSchema): + db = SessionLocal() + + try: + # 1. pega o e-mail que veio do schema + usuario_id_to_find = usuario_schema.usuario_id + + # 2. busca no banco + result = ( + db.query( + Usuario.usuario_id, + func.AES_DECRYPT(Usuario.nome, AES_KEY).label("nome"), + func.AES_DECRYPT(Usuario.email, AES_KEY).label("email"), + Usuario.username, + Usuario.password, + Usuario.status, + Usuario.date_update, + Usuario.user_id_create, + Usuario.user_id_update, + ) + .filter(Usuario.usuario_id == usuario_id_to_find) + .first() + ) + + # 3. se não achou, devolve None + if result is None: + return None + + # 4. se achou, converte para pydantic + # (assumindo que UsuarioSchema tem Config.from_attributes = True) + return UsuarioSchema.model_validate(result) + + finally: + db.close() diff --git a/packages/v1/administrativo/repositories/usuario/usuario_update_repository.py b/packages/v1/administrativo/repositories/usuario/usuario_update_repository.py new file mode 100644 index 0000000..21286bc --- /dev/null +++ b/packages/v1/administrativo/repositories/usuario/usuario_update_repository.py @@ -0,0 +1,113 @@ +from datetime import datetime +from fastapi import HTTPException, status +from sqlalchemy import func +from database.mysql import SessionLocal, get_database_settings +from packages.v1.administrativo.models.usuario_model import Usuario +from packages.v1.administrativo.schemas.usuario_schema import UsuarioUpdateSchema + +# pega as configurações do banco +DB_SETTINGS = get_database_settings() +AES_KEY = getattr(DB_SETTINGS, "aeskey", None) + + +class UpdateRepository: + """ + Classe responsável por atualizar usuários no banco de dados. + Segue a mesma metodologia do SaveRepository (sessão, try/except, rollback, retorno padronizado). + """ + + def execute(self, usuario_id: int, usuario_schema: UsuarioUpdateSchema): + db = SessionLocal() + + try: + # Busca o usuário existente + usuario_db = ( + db.query(Usuario).filter(Usuario.usuario_id == usuario_id).first() + ) + + if not usuario_db: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Usuário não encontrado.", + ) + + # Verifica duplicidade de username e e-mail + if usuario_schema.email or usuario_schema.username: + consulta_duplicidade = db.query(Usuario).filter( + Usuario.usuario_id != usuario_id + ) + + # verifica duplicidade de username (texto puro) + if usuario_schema.username: + consulta_duplicidade = consulta_duplicidade.filter( + Usuario.username == usuario_schema.username + ) + + # verifica duplicidade de e-mail criptografado + if usuario_schema.email: + consulta_duplicidade = consulta_duplicidade.filter( + func.AES_DECRYPT(Usuario.email, AES_KEY) == usuario_schema.email + ) + + usuario_conflitante = consulta_duplicidade.first() + if usuario_conflitante: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Já existe outro usuário com este e-mail ou username.", + ) + + # Atualiza apenas campos enviados + if usuario_schema.nome is not None: + # Criptografa novamente o nome + usuario_db.nome = func.AES_ENCRYPT(usuario_schema.nome, AES_KEY) + + if usuario_schema.email is not None: + # Criptografa novamente o e-mail + usuario_db.email = func.AES_ENCRYPT(usuario_schema.email, AES_KEY) + + if usuario_schema.username is not None: + usuario_db.username = usuario_schema.username + + if usuario_schema.password is not None: + usuario_db.password = usuario_schema.password # deve vir com hash + + if usuario_schema.status is not None: + usuario_db.status = usuario_schema.status + + if usuario_schema.user_id_update is not None: + usuario_db.user_id_update = usuario_schema.user_id_update + + # Atualiza data da modificação + if hasattr(usuario_db, "date_update"): + usuario_db.date_update = datetime.now() + + # Persiste as alterações + db.add(usuario_db) + db.commit() + db.refresh(usuario_db) + + return { + "success": True, + "message": "Usuário atualizado com sucesso!", + "data": { + "usuario_id": usuario_db.usuario_id, + "nome": usuario_schema.nome, + "email": usuario_schema.email, + "username": usuario_db.username, + "status": usuario_db.status, + }, + } + + except HTTPException: + db.rollback() + raise + + except Exception as e: + db.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Erro ao atualizar usuário: {e}", + ) + + finally: + db.close() diff --git a/packages/v1/administrativo/schemas/ato_documento_schema.py b/packages/v1/administrativo/schemas/ato_documento_schema.py new file mode 100644 index 0000000..d55a3fc --- /dev/null +++ b/packages/v1/administrativo/schemas/ato_documento_schema.py @@ -0,0 +1,152 @@ +from pydantic import BaseModel, constr, field_validator, validator +from fastapi import HTTPException, status +from typing import Optional +from datetime import datetime + +# Funções para sanitização de entradas (evitar XSS, SQLi etc.) +# É importante que esta função seja mantida/implementada no seu ambiente +# from actions.validations.text import Text # Descomentar se for usar +# Funções para validar URL (ajustar importação conforme seu projeto) +# from actions.validations.url import URL # Descomentar se for usar + + +# ---------------------------------------------------- +# Schema Base: Usado para leitura (GET, SHOW, INDEX) +# Inclui todos os campos, incluindo os gerados pelo banco +# ---------------------------------------------------- +class AtoDocumentoSchema(BaseModel): + ato_documento_id: Optional[int] = None + ato_principal_id: Optional[int] = None # bigint NOT NULL + url: Optional[str] = None # text NOT NULL + nome_documento: Optional[str] = None # varchar(255) NOT NULL + tipo_documento: Optional[str] = None # varchar(50) NOT NULL + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None + + class Config: + from_attributes = True + + +# ---------------------------------------------------- +# Schema para Requisição de ID: Usado em SHOW e DELETE +# ---------------------------------------------------- +class AtoDocumentoIdSchema(BaseModel): + ato_documento_id: int + + @field_validator("ato_documento_id") + def validate_ato_documento_id(cls, v: int): + if v <= 0: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=[ + { + "input": "ato_documento_id", + "message": "ID do documento deve ser um valor positivo.", + } + ], + ) + return v + + +# ---------------------------------------------------- +# Schema para Criação (SAVE): Campos obrigatórios e sem ID +# ---------------------------------------------------- +class AtoDocumentoSaveSchema(BaseModel): + # Campos obrigatórios + ato_principal_id: Optional[int] = None # <<< tornar opcional + url: str + nome_documento: constr(max_length=255) + tipo_documento: constr(max_length=50) + + # Nota: created_at e updated_at são tratados pelo Model/Banco + + # Validação e Sanitização de URL (chk_url_https) + @field_validator("url") + def validate_url(cls, v: str): + v = v.strip() + if not v.startswith("https://"): + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=[ + {"input": "url", "message": "A URL do documento deve ser HTTPS."} + ], + ) + # Adicionar aqui a validação de URL (ex: URL.is_valid_url(v)) se disponível + return v + + # Validação e Sanitização de Tipo Documento (chk_tipo_documento_not_empty) + @field_validator("tipo_documento") + def validate_tipo_documento(cls, v: str): + v = v.strip() + if not v: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=[ + { + "input": "tipo_documento", + "message": "O tipo de documento não pode ser vazio.", + } + ], + ) + # Adicionar aqui a sanitização de texto (ex: Text.sanitize(v)) se disponível + return v + + # Validação e Sanitização de Nome Documento + @field_validator("nome_documento") + def validate_nome_documento(cls, v: str): + v = v.strip() + if not v: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=[ + { + "input": "nome_documento", + "message": "O nome do documento não pode ser vazio.", + } + ], + ) + return v + + +# ---------------------------------------------------- +# Schema para Atualização (UPDATE): Todos opcionais (parciais) +# ---------------------------------------------------- +class AtoDocumentoUpdateSchema(BaseModel): + # Todos os campos são opcionais no UPDATE + ato_principal_id: Optional[int] = None + url: Optional[str] = None + nome_documento: Optional[constr(max_length=255)] = None + tipo_documento: Optional[constr(max_length=50)] = None + + # Validação de URL + @field_validator("url") + def validate_url(cls, v: Optional[str]): + if v is None: + return v + v = v.strip() + if v and not v.startswith("https://"): + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=[ + {"input": "url", "message": "A URL do documento deve ser HTTPS."} + ], + ) + return v + + # Validação de Tipo Documento + @field_validator("tipo_documento") + def validate_tipo_documento(cls, v: Optional[str]): + if v is None: + return v + v = v.strip() + if not v: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=[ + { + "input": "tipo_documento", + "message": "O tipo de documento não pode ser vazio.", + } + ], + ) + return v diff --git a/packages/v1/administrativo/schemas/ato_parte_schema.py b/packages/v1/administrativo/schemas/ato_parte_schema.py new file mode 100644 index 0000000..3312a84 --- /dev/null +++ b/packages/v1/administrativo/schemas/ato_parte_schema.py @@ -0,0 +1,138 @@ +from pydantic import BaseModel, constr, field_validator +from fastapi import HTTPException, status +from typing import Optional +from datetime import datetime +import re + +# Funções para sanitização de entradas (evitar XSS, SQLi etc.) +# É importante que esta função seja mantida/implementada no seu ambiente +# from actions.validations.text import Text # Descomentar se for usar + + +# ---------------------------------------------------- +# Schema Base: Usado para leitura (GET, SHOW, INDEX) +# Inclui todos os campos, incluindo os gerados pelo banco +# ---------------------------------------------------- +class AtoParteSchema(BaseModel): + ato_parte_id: Optional[int] = None + ato_principal_id: Optional[int] = None # bigint NOT NULL + nome: Optional[str] = None # varchar(255) NOT NULL + telefone: Optional[str] = None # varchar(20) DEFAULT NULL + cpf_cnpj: Optional[str] = None # varchar(20) NOT NULL + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None + + class Config: + from_attributes = True + + +# ---------------------------------------------------- +# Schema para Requisição de ID: Usado em SHOW e DELETE +# ---------------------------------------------------- +class AtoParteIdSchema(BaseModel): + ato_parte_id: int + + @field_validator("ato_parte_id") + def validate_ato_parte_id(cls, v: int): + if v <= 0: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=[ + { + "input": "ato_parte_id", + "message": "ID da parte deve ser um valor positivo.", + } + ], + ) + return v + + +# ---------------------------------------------------- +# Funções de Validação Comuns +# ---------------------------------------------------- + + +def validate_cpf_cnpj(cls, v: str): + v = re.sub(r"[^\d]", "", v).strip() # Remove caracteres não numéricos + + # chk_cpf_cnpj_formato CHECK (regexp_like(`cpf_cnpj`,_utf8mb4'^[0-9]{11,14}$')) + if not re.match(r"^\d{11,14}$", v): + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=[ + { + "input": "cpf_cnpj", + "message": "CPF/CNPJ deve conter apenas 11 (CPF) ou 14 (CNPJ) dígitos numéricos.", + } + ], + ) + return v + + +def validate_nome_not_empty(cls, v: str): + v = v.strip() + if not v: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=[ + { + "input": "nome", + "message": "O nome não pode ser vazio.", + } + ], + ) + # Adicionar aqui a sanitização de texto (ex: Text.sanitize(v)) se disponível + return v + + +# ---------------------------------------------------- +# Schema para Criação (SAVE): Campos obrigatórios e sem ID +# ---------------------------------------------------- +class AtoParteSaveSchema(BaseModel): + # Campos obrigatórios + ato_principal_id: Optional[int] = None # <<< tornar opcional + nome: constr(max_length=255) + cpf_cnpj: constr( + max_length=20 + ) # Permitindo até 20 para acomodar a entrada antes de limpar + + # Campo opcional (nullable na DDL) + telefone: Optional[constr(max_length=20)] = None + + # Validação de Nome + @field_validator("nome") + def validate_nome(cls, v: str): + return validate_nome_not_empty(cls, v) + + # Validação de CPF/CNPJ + @field_validator("cpf_cnpj") + def validate_cpf_cnpj_field(cls, v: str): + return validate_cpf_cnpj(cls, v) + + +# ---------------------------------------------------- +# Schema para Atualização (UPDATE): Todos opcionais (parciais) +# ---------------------------------------------------- +class AtoParteUpdateSchema(BaseModel): + # Todos os campos são opcionais no UPDATE + ato_principal_id: Optional[int] = None + nome: Optional[constr(max_length=255)] = None + telefone: Optional[constr(max_length=20)] = None + cpf_cnpj: Optional[constr(max_length=20)] = None + + # Validação de Nome + @field_validator("nome") + def validate_nome_update(cls, v: Optional[str]): + if v is None: + return v + return validate_nome_not_empty(cls, v) + + # Validação de CPF/CNPJ + @field_validator("cpf_cnpj") + def validate_cpf_cnpj_update(cls, v: Optional[str]): + if v is None: + return v + return validate_cpf_cnpj(cls, v) + + # Nota: Telefone não precisa de validação complexa além do constr(max_length) + # se o objetivo for apenas armazenar a string fornecida. diff --git a/packages/v1/administrativo/schemas/ato_principal_schema.py b/packages/v1/administrativo/schemas/ato_principal_schema.py index 7d31751..64a2f2d 100644 --- a/packages/v1/administrativo/schemas/ato_principal_schema.py +++ b/packages/v1/administrativo/schemas/ato_principal_schema.py @@ -1,56 +1,277 @@ -from pydantic import BaseModel +from pydantic import BaseModel, constr, field_validator +from fastapi import HTTPException, status +from typing import List from typing import Optional from datetime import datetime -from ipaddress import IPv4Address, IPv6Address -from decimal import Decimal +from decimal import Decimal # Importar Decimal para campos monetários + +# Funções para sanitização de entradas (evitar XSS, SQLi etc.) +# É importante que esta função seja mantida/implementada no seu ambiente +# from actions.validations.text import Text # Descomentar se for usar + +# Schemas dos itens +from packages.v1.administrativo.schemas.ato_parte_schema import AtoParteSaveSchema +from packages.v1.administrativo.schemas.ato_documento_schema import ( + AtoDocumentoSaveSchema, +) # ---------------------------------------------------- -# Schema base - campos principais e comuns +# Schema Base: Usado para leitura (GET, SHOW, INDEX) +# Inclui todos os campos, incluindo os gerados pelo banco # ---------------------------------------------------- -class AtoPrincipalBaseSchema(BaseModel): +class AtoPrincipalSchema(BaseModel): + # DDL: bigint NOT NULL AUTO_INCREMENT + ato_principal_id: Optional[int] = None + # DDL: bigint DEFAULT NULL origem_ato_principal_id: Optional[int] = None + # DDL: bigint NOT NULL identificacao_pedido_cgj: int + # DDL: int NOT NULL tipo_ato: int - codigo_selo: str - codigo_ato: str - nome_civil_ato: str - nome_serventuario_praticou_ato: str + # DDL: varchar(50) NOT NULL (UNIQUE) + codigo_selo: constr(max_length=50) + # DDL: varchar(50) NOT NULL (UNIQUE) + codigo_ato: constr(max_length=50) + # DDL: varchar(255) NOT NULL (mas o banco pode conter valores NULL em registros antigos) + nome_civil_ato: Optional[constr(max_length=255)] = None + # DDL: varchar(255) NOT NULL (mas o banco pode conter valores NULL em registros antigos) + nome_serventuario_praticou_ato: Optional[constr(max_length=255)] = None + # DDL: datetime NOT NULL data_solicitacao: datetime - ip_maquina: Optional[IPv4Address | IPv6Address] = None + # DDL: varchar(45) DEFAULT NULL + ip_maquina: Optional[constr(max_length=45)] = None + # DDL: text NOT NULL inteiro_teor: str + # DDL: decimal(12,2) DEFAULT NULL valor_entrada: Optional[Decimal] = None + # DDL: decimal(12,2) NOT NULL emolumento: Decimal + # DDL: decimal(12,2) NOT NULL taxa_judiciaria: Decimal + # DDL: decimal(12,2) NOT NULL fundos_estaduais: Decimal - protocolo_protesto: Optional[str] = None - protocolo_imovel: Optional[str] = None + # DDL: varchar(50) DEFAULT NULL + protocolo_protesto: Optional[constr(max_length=50)] = None + # DDL: varchar(50) DEFAULT NULL + protocolo_imovel: Optional[constr(max_length=50)] = None + # DDL: datetime NOT NULL DEFAULT CURRENT_TIMESTAMP + created_at: Optional[datetime] = None + # DDL: datetime NOT NULL DEFAULT CURRENT_TIMESTAMP + updated_at: Optional[datetime] = None class Config: from_attributes = True + # Permite a conversão de float/int para Decimal + json_encoders = {Decimal: lambda v: float(v)} + # Define a precisão para Decimal + decimal_places = 2 # ---------------------------------------------------- -# Schema de resposta (GET) — inclui metadata +# Schema para Requisição de ID: Usado em SHOW e DELETE # ---------------------------------------------------- -class AtoPrincipalResponseSchema(AtoPrincipalBaseSchema): +class AtoPrincipalIdSchema(BaseModel): ato_principal_id: int - created_at: datetime - updated_at: datetime + + @field_validator("ato_principal_id") + def validate_ato_principal_id(cls, v: int): + if v <= 0: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=[ + { + "input": "ato_principal_id", + "message": "ID do Ato Principal deve ser um valor positivo.", + } + ], + ) + return v + + +# ---------------------------------------------------- +# Schema para Criação (SAVE): Campos obrigatórios e sem ID +# ---------------------------------------------------- +class AtoPrincipalSaveSchema(BaseModel): + ato_principal_id: Optional[int] = None # <<< tornar opcional + + # Campos obrigatórios baseados na DDL (NOT NULL) + origem_ato_principal_id: Optional[int] = None # bigint DEFAULT NULL + identificacao_pedido_cgj: int # bigint NOT NULL + tipo_ato: int # int NOT NULL + codigo_selo: constr(max_length=50) # varchar(50) NOT NULL + codigo_ato: constr(max_length=50) # varchar(50) NOT NULL + nome_civil_ato: constr(max_length=255) # varchar(255) NOT NULL + nome_serventuario_praticou_ato: constr(max_length=255) # varchar(255) NOT NULL + data_solicitacao: datetime # datetime NOT NULL + ip_maquina: Optional[constr(max_length=45)] = None # varchar(45) DEFAULT NULL + inteiro_teor: str # text NOT NULL + valor_entrada: Optional[Decimal] = None # decimal(12,2) DEFAULT NULL + emolumento: Decimal # decimal(12,2) NOT NULL + taxa_judiciaria: Decimal # decimal(12,2) NOT NULL + fundos_estaduais: Decimal # decimal(12,2) NOT NULL + protocolo_protesto: Optional[constr(max_length=50)] = ( + None # varchar(50) DEFAULT NULL + ) + protocolo_imovel: Optional[constr(max_length=50)] = None # varchar(50) DEFAULT NULL + ato_partes: List[AtoParteSaveSchema] = [] # precisa existir + ato_documentos: List[AtoDocumentoSaveSchema] = [] # precisa existir + + # NOVO CAMPO: Referência recursiva a outros atos principais + # Usando string para forward reference + atos_vinculados: Optional[List["AtoPrincipalSaveSchema"]] = None + + # Validação e Sanitização de campos de texto obrigatórios (adaptado do código fonte) + @field_validator( + "codigo_selo", + "codigo_ato", + "nome_civil_ato", + "nome_serventuario_praticou_ato", + "inteiro_teor", + ) + def validate_required_strings(cls, v: str): + v = v.strip() + if not v: + # Identifica o campo que falhou a validação + input_name = ( + "Erro de campo" # Será substituído se a função for mais complexa + ) + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=[ + { + "input": input_name, + "message": "Este campo obrigatório não pode ser vazio.", + } + ], + ) + # Adicionar aqui a sanitização de texto (ex: Text.sanitize(v)) se disponível + return v + + # Validação dos campos monetários (baseado na CHECK CONSTRAINT da DDL) + @field_validator( + "valor_entrada", "emolumento", "taxa_judiciaria", "fundos_estaduais" + ) + def validate_positive_values(cls, v: Optional[Decimal]): + if v is None and ( + "valor_entrada" in cls.__annotations__ + and cls.__annotations__["valor_entrada"] == Optional[Decimal] + ): + # valor_entrada é o único campo DEFAULT NULL, aceita None + return v + if v is not None and v < 0: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=[ + { + "input": "campo_monetario", + "message": "Os valores monetários não podem ser negativos.", + } + ], + ) + return v class Config: from_attributes = True + json_encoders = {Decimal: lambda v: float(v)} + decimal_places = 2 # ---------------------------------------------------- -# Schema de criação (POST) +# Schema para Atualização (UPDATE): Todos opcionais (parciais) # ---------------------------------------------------- -class AtoPrincipalCreateSchema(AtoPrincipalBaseSchema): - pass +class AtoPrincipalUpdateSchema(BaseModel): + # Todos os campos que podem ser atualizados são opcionais + origem_ato_principal_id: Optional[int] = None + identificacao_pedido_cgj: Optional[int] = None + tipo_ato: Optional[int] = None + codigo_selo: Optional[constr(max_length=50)] = None + codigo_ato: Optional[constr(max_length=50)] = None + nome_civil_ato: Optional[constr(max_length=255)] = None + nome_serventuario_praticou_ato: Optional[constr(max_length=255)] = None + data_solicitacao: Optional[datetime] = None + ip_maquina: Optional[constr(max_length=45)] = None + inteiro_teor: Optional[str] = None + valor_entrada: Optional[Decimal] = None + emolumento: Optional[Decimal] = None + taxa_judiciaria: Optional[Decimal] = None + fundos_estaduais: Optional[Decimal] = None + protocolo_protesto: Optional[constr(max_length=50)] = None + protocolo_imovel: Optional[constr(max_length=50)] = None + + # Reutiliza a validação de strings obrigatórias (só valida se o campo for fornecido) + @field_validator( + "codigo_selo", + "codigo_ato", + "nome_civil_ato", + "nome_serventuario_praticou_ato", + "inteiro_teor", + ) + def validate_non_empty_strings_on_update(cls, v: Optional[str]): + if v is not None: + v_stripped = v.strip() + if not v_stripped: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=[ + { + "input": "campo_de_texto", + "message": "Este campo não pode ser atualizado para um valor vazio.", + } + ], + ) + return v + + # Reutiliza a validação de valores positivos + @field_validator( + "valor_entrada", "emolumento", "taxa_judiciaria", "fundos_estaduais" + ) + def validate_positive_values_on_update(cls, v: Optional[Decimal]): + if v is not None and v < 0: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=[ + { + "input": "campo_monetario", + "message": "Os valores monetários não podem ser negativos.", + } + ], + ) + return v + + class Config: + from_attributes = True + json_encoders = {Decimal: lambda v: float(v)} + decimal_places = 2 # ---------------------------------------------------- -# Schema de atualização (PUT) +# Schema auxiliar para buscar por campos únicos (opcional, mas útil para o Save Service) # ---------------------------------------------------- -class AtoPrincipalUpdateSchema(AtoPrincipalBaseSchema): - ato_principal_id: int +class AtoPrincipalCodigoSchema(BaseModel): + codigo: constr(max_length=50) # Pode ser codigo_selo ou codigo_ato + + @field_validator("codigo") + def validate_codigo(cls, v: str): + v = v.strip() + if not v: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=[ + { + "input": "codigo", + "message": "O código de busca não pode ser vazio.", + } + ], + ) + return v + + +# --- ADIÇÃO CRÍTICA PARA RECURSIVIDADE (Resolução da forward reference) --- +# Necessário para resolver a referência da classe dentro de si mesma. +# Tenta Pydantic v2 (model_rebuild) e, se falhar, Pydantic v1 (update_forward_refs). +try: + AtoPrincipalSaveSchema.model_rebuild() +except AttributeError: + # Se estiver usando Pydantic v1 + AtoPrincipalSaveSchema.update_forward_refs() diff --git a/packages/v1/administrativo/schemas/usuario_schema.py b/packages/v1/administrativo/schemas/usuario_schema.py new file mode 100644 index 0000000..194ffeb --- /dev/null +++ b/packages/v1/administrativo/schemas/usuario_schema.py @@ -0,0 +1,223 @@ +from pydantic import BaseModel, EmailStr, constr, field_validator, model_validator +from pydantic_core import PydanticCustomError +from fastapi import HTTPException, status +from typing import Optional +from datetime import datetime + +# Funções utilitárias para segurança (hash e verificação de senha) +# Supondo que você ajustará as importações conforme seu projeto +from actions.security.security import Security + +# Funções para sanitização de entradas (evitar XSS, SQLi etc.) +from actions.validations.text import Text + +# Funções para validar E-mail +from actions.validations.email import Email + +# Funções para validar cpf (Não está na DDL, mas mantido como opcional no Save e Update) +# from actions.validations.cpf import CPF # Não utilizado neste novo escopo + + +# ---------------------------------------------------- +# Schema base +# ---------------------------------------------------- +class UsuarioSchema(BaseModel): + usuario_id: Optional[int] = None + nome: Optional[str] = None + email: Optional[EmailStr] = None + username: Optional[str] = None + password: Optional[str] = None + status: Optional[str] = None # Corresponde ao 'status' na DDL + date_register: Optional[datetime] = None + date_update: Optional[datetime] = None + user_id_create: Optional[int] = None + user_id_update: Optional[int] = None + + class Config: + from_attributes = True + + +# ---------------------------------------------------- +# Schema para acesso ao sistema (Autenticação) +# ---------------------------------------------------- +class UsuarioAuthenticateSchema(BaseModel): + + # Campos utilizados + username: str # Usando username para login, é mais comum em DDLs simples + password: str # Corresponde ao 'password' na DDL + status: Optional[str] = None + usuario_id: Optional[int] = None + nome: Optional[str] = None + email: Optional[str] = None + + # Validação e sanitização do email + @field_validator("username") + def validar_e_sanitizar_password(cls, v): + + if not v: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail="Informe o username" + ) + + # Sanitiza e-mail + return Text.sanitize_input(v) + + # Validação e sanitização da senha + @field_validator("password") + def validar_e_sanitizar_senha(cls, v): + + if not v: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail="Informe a password" + ) + + # Sanitiza a senha + return Text.sanitize_input(v) + + class Config: + from_attributes = True + + +# ---------------------------------------------------- +# Schema para localizar um usuário especifico pelo ID (GET) +# ---------------------------------------------------- +class UsuarioIdSchema(BaseModel): + usuario_id: int + + +# ---------------------------------------------------- +# Schema para criação de novo usuário (POST) +# ---------------------------------------------------- +class UsuarioSaveSchema(BaseModel): + usuario_id: Optional[int] = None + nome: constr(min_length=1) + email: EmailStr + username: constr(min_length=1) + password: constr(min_length=1) + status: Optional[str] = "A" + user_id_create: Optional[int] = None + + # Sanitiza os inputs enviados + @field_validator("nome", "email", "username", "password", "status") + def validate_and_sanitize_fields(cls, v): + if v is not None: + return Text.sanitize_input(v) + return v + + # Verifica se os campos obrigatórios foram enviados e hash da senha + @model_validator(mode="after") + def validate_all_fields_and_hash_password(self): + + errors = [] + + # Validação do nome + if not self.nome or len(self.nome.strip()) == 0: + errors.append({"input": "nome", "message": "O nome é obrigatório."}) + + # Validação do email (EmailStr do Pydantic já faz a validação de formato) + if not self.email or len(self.email.strip()) == 0: + errors.append({"input": "email", "message": "O e-mail é obrigatório."}) + + # Validação da username + if not self.username or len(self.username.strip()) == 0: + errors.append({"input": "username", "message": "O username é obrigatório."}) + + # Validação da senha + if not self.password or len(self.password.strip()) == 0: + errors.append({"input": "password", "message": "A senha é obrigatória."}) + + # Criptografa a senha + if ( + self.password and not errors + ): # Hash somente se a senha estiver presente e sem erros anteriores + self.password = Security.hash_password(self.password) + + # Se houver errors, lança uma única exceção + if errors: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=errors + ) + + return self + + +# ---------------------------------------------------- +# Schema para atualizar usuário (PUT) +# ---------------------------------------------------- +class UsuarioUpdateSchema(BaseModel): + + nome: Optional[str] = None + email: Optional[EmailStr] = None + username: Optional[str] = None + password: Optional[str] = None + status: Optional[str] = None + user_id_update: Optional[int] = None + + # Sanitiza os inputs enviados + @field_validator( + "nome", + "email", + "username", + "password", + "status", + ) + def validate_and_sanitize_fields(cls, v): + if v is not None: + return Text.sanitize_input(v) + return v + + # Hash da senha se ela for fornecida + @model_validator(mode="after") + def hash_password_if_provided(self): + + # Hash da nova senha, se fornecida. + if self.password: + self.password = Security.hash_password(self.password) + + # Não estamos forçando a obrigatoriedade de todos os campos aqui (PUT é parcial), + # mas garantimos a sanitização e o hash da senha se ela existir. + + return self + + +# ---------------------------------------------------- +# Schema para localizar usuário pelo e-mail +# ---------------------------------------------------- +class UsuarioEmailSchema(BaseModel): + + email: Optional[EmailStr] = None + + # Sanitiza o e-mail informado + @field_validator("email", mode="before") + def sanitize_email(cls, v: Optional[str]): + if v is None: + return v + v = v.strip() + return v + + # Verifica se o e-mail foi informado e se é válido + @field_validator("email") + def validate_email(cls, v: str): + + errors = [] + + # vazio + if not v: + + errors.append({"input": "e-mail", "message": "Informe um e-mail."}) + + # inválido pelo seu helper + if not Email.is_valid_email(v): + + errors.append({"input": "e-mail", "message": "E-mail inválido."}) + + # Se houver errors, lança uma única exceção + if errors: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=errors + ) + + return v + + class Config: + from_attributes = True diff --git a/packages/v1/administrativo/services/ato_documento/ato_documento_delete_service.py b/packages/v1/administrativo/services/ato_documento/ato_documento_delete_service.py new file mode 100644 index 0000000..aaf4e5a --- /dev/null +++ b/packages/v1/administrativo/services/ato_documento/ato_documento_delete_service.py @@ -0,0 +1,18 @@ +from packages.v1.administrativo.schemas.ato_documento_schema import AtoDocumentoSchema +from packages.v1.administrativo.actions.ato_documento.ato_documento_delete_action import ( + DeleteAction, +) + + +class DeleteService: + + def execute(self, ato_documento_schema: AtoDocumentoSchema): + + # Instânciamento de ação + delete_action = DeleteAction() + + # Executa a ação em questão, passando o schema com o ID + data = delete_action.execute(ato_documento_schema) + + # Retorno da informação (inclui a mensagem de sucesso do Repository/Action) + return data diff --git a/packages/v1/administrativo/services/ato_documento/ato_documento_index_service.py b/packages/v1/administrativo/services/ato_documento/ato_documento_index_service.py new file mode 100644 index 0000000..fede101 --- /dev/null +++ b/packages/v1/administrativo/services/ato_documento/ato_documento_index_service.py @@ -0,0 +1,29 @@ +from fastapi import HTTPException, status +from packages.v1.administrativo.actions.ato_documento.ato_documento_index_action import ( + IndexAction, +) + +# Nota: O Schema de índice (AtoDocumentoSchema) não é importado aqui +# porque a Action já faz a conversão do Model para Schema. + + +class IndexService: + + def execute(self): + + # Instânciamento da Action + index_action = IndexAction() + + # Executa a busca de todos os documentos + data = index_action.execute() + + # Verifica se foram localizados registros + if not data: + # Retorna uma exceção 404 + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Não foi possível localizar os documentos", + ) + + # Retorna as informações localizadas + return data diff --git a/packages/v1/administrativo/services/ato_documento/ato_documento_save_service.py b/packages/v1/administrativo/services/ato_documento/ato_documento_save_service.py new file mode 100644 index 0000000..c271927 --- /dev/null +++ b/packages/v1/administrativo/services/ato_documento/ato_documento_save_service.py @@ -0,0 +1,26 @@ +from packages.v1.administrativo.schemas.ato_documento_schema import ( + AtoDocumentoSaveSchema, +) +from packages.v1.administrativo.actions.ato_documento.ato_documento_save_action import ( + SaveAction, +) + + +class SaveService: + """ + Service responsável por validar o schema e executar a Action de salvamento + do documento. Não requer lógica de verificação de duplicidade de campos + que não são únicos (url, tipo_documento, nome_documento) ou + que são chaves estrangeiras (ato_principal_id). + """ + + def execute(self, ato_documento_schema: AtoDocumentoSaveSchema): + + # A verificação de duplicidade (como e-mail em usuário) não é necessária + # para ato_documento neste ponto. A Action/Repository é chamada diretamente. + + # Instânciamento da Action + save_action = SaveAction() + + # Executa a ação de salvamento + return save_action.execute(ato_documento_schema) diff --git a/packages/v1/administrativo/services/ato_documento/ato_documento_show_service.py b/packages/v1/administrativo/services/ato_documento/ato_documento_show_service.py new file mode 100644 index 0000000..1728601 --- /dev/null +++ b/packages/v1/administrativo/services/ato_documento/ato_documento_show_service.py @@ -0,0 +1,26 @@ +from fastapi import HTTPException, status +from packages.v1.administrativo.schemas.ato_documento_schema import AtoDocumentoSchema +from packages.v1.administrativo.actions.ato_documento.ato_documento_show_action import ( + ShowAction, +) + + +class ShowService: + + def execute(self, ato_documento_schema: AtoDocumentoSchema): + + # Instânciamento da Action + show_action = ShowAction() + + # Executa a ação em questão (busca pelo ID) + data = show_action.execute(ato_documento_schema) + + if not data: + # Retorna uma exceção 404 + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Não foi possível localizar o documento.", + ) + + # Retorno da informação + return data diff --git a/packages/v1/administrativo/services/ato_documento/ato_documento_update_service.py b/packages/v1/administrativo/services/ato_documento/ato_documento_update_service.py new file mode 100644 index 0000000..b4c90dd --- /dev/null +++ b/packages/v1/administrativo/services/ato_documento/ato_documento_update_service.py @@ -0,0 +1,26 @@ +from packages.v1.administrativo.schemas.ato_documento_schema import ( + AtoDocumentoUpdateSchema, +) +from packages.v1.administrativo.actions.ato_documento.ato_documento_update_action import ( + UpdateAction, +) + + +class UpdateService: + """ + Service responsável por validar o schema e executar a Action de atualização + do documento. + """ + + def execute( + self, ato_documento_id: int, ato_documento_schema: AtoDocumentoUpdateSchema + ): + + # A lógica de verificação de duplicidade de campos sensíveis (como e-mail) + # não é necessária para ato_documento neste ponto. + + # Instânciamento de ações + updateAction = UpdateAction() + + # Executa a atualização, passando o ID e o schema com os dados + return updateAction.execute(ato_documento_id, ato_documento_schema) diff --git a/packages/v1/administrativo/services/ato_documento/ato_principal_show_service.py b/packages/v1/administrativo/services/ato_documento/ato_principal_show_service.py new file mode 100644 index 0000000..55063e9 --- /dev/null +++ b/packages/v1/administrativo/services/ato_documento/ato_principal_show_service.py @@ -0,0 +1,26 @@ +from fastapi import HTTPException, status +from packages.v1.administrativo.schemas.ato_documento_schema import AtoDocumentoSchema +from packages.v1.administrativo.actions.ato_documento.ato_principal_show_action import ( + ShowAction, +) + + +class ShowAtoPrincipalService: + + def execute(self, ato_documento_schema: AtoDocumentoSchema): + + # Instânciamento da Action + show_action = ShowAction() + + # Executa a ação em questão (busca pelo ID) + data = show_action.execute(ato_documento_schema) + + if not data: + # Retorna uma exceção 404 + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Não foi possível localizar o documento.", + ) + + # Retorno da informação + return data diff --git a/packages/v1/administrativo/services/ato_parte/ato_parte_delete_service.py b/packages/v1/administrativo/services/ato_parte/ato_parte_delete_service.py new file mode 100644 index 0000000..5702f24 --- /dev/null +++ b/packages/v1/administrativo/services/ato_parte/ato_parte_delete_service.py @@ -0,0 +1,23 @@ +from packages.v1.administrativo.schemas.ato_parte_schema import AtoParteIdSchema +from packages.v1.administrativo.actions.ato_parte.ato_parte_delete_action import ( + DeleteAction, +) + + +class DeleteService: + """ + Service responsável por orquestrar a ação de exclusão de uma parte do ato. + """ + + def execute(self, ato_parte_schema: AtoParteIdSchema): + + # Instânciamento de ação (assumindo que o nome da Action é 'DeleteAction' + # e está no caminho 'packages.v1.administrativo.actions.ato_parte') + delete_action = DeleteAction() + + # Executa a ação em questão, passando o schema com o ID + # A Action deve receber um schema contendo apenas o ID, como AtoParteIdSchema + data = delete_action.execute(ato_parte_schema) + + # Retorno da informação (inclui a mensagem de sucesso do Repository/Action) + return data diff --git a/packages/v1/administrativo/services/ato_parte/ato_parte_index_service.py b/packages/v1/administrativo/services/ato_parte/ato_parte_index_service.py new file mode 100644 index 0000000..038336a --- /dev/null +++ b/packages/v1/administrativo/services/ato_parte/ato_parte_index_service.py @@ -0,0 +1,32 @@ +from fastapi import HTTPException, status +from packages.v1.administrativo.actions.ato_parte.ato_parte_index_action import ( + IndexAction, +) + +# Nota: O Schema de índice (AtoParteSchema) não é importado aqui +# porque a Action já faz a conversão do Model para Schema. + + +class IndexService: + """ + Service responsável por orquestrar a busca de todas as partes de ato. + """ + + def execute(self): + + # Instânciamento da Action + index_action = IndexAction() + + # Executa a busca de todas as partes + data = index_action.execute() + + # Verifica se foram localizados registros + if not data: + # Retorna uma exceção 404 + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Não foi possível localizar as partes de ato", + ) + + # Retorna as informações localizadas + return data diff --git a/packages/v1/administrativo/services/ato_parte/ato_parte_save_service.py b/packages/v1/administrativo/services/ato_parte/ato_parte_save_service.py new file mode 100644 index 0000000..1fb1bb3 --- /dev/null +++ b/packages/v1/administrativo/services/ato_parte/ato_parte_save_service.py @@ -0,0 +1,25 @@ +from packages.v1.administrativo.schemas.ato_parte_schema import ( + AtoParteSaveSchema, +) +from packages.v1.administrativo.actions.ato_parte.ato_parte_save_action import ( + SaveAction, +) + + +class SaveService: + """ + Service responsável por validar o schema e executar a Action de salvamento + da parte de ato. + Nota: Para CPF/CNPJ, é **crucial** que haja uma verificação de duplicidade, + pois é um campo que pode ser considerado único ou que precisa de validação + de existência no contexto da sua regra de negócio. + Para este template, mantive a estrutura simples. + """ + + def execute(self, ato_parte_schema: AtoParteSaveSchema): + + # Instânciamento da Action + save_action = SaveAction() + + # Executa a ação de salvamento + return save_action.execute(ato_parte_schema) diff --git a/packages/v1/administrativo/services/ato_parte/ato_parte_show_service.py b/packages/v1/administrativo/services/ato_parte/ato_parte_show_service.py new file mode 100644 index 0000000..3db665b --- /dev/null +++ b/packages/v1/administrativo/services/ato_parte/ato_parte_show_service.py @@ -0,0 +1,32 @@ +from fastapi import HTTPException, status +from packages.v1.administrativo.schemas.ato_parte_schema import AtoParteSchema +from packages.v1.administrativo.actions.ato_parte.ato_parte_show_action import ( + ShowAction, +) + + +class ShowService: + """ + Service responsável por orquestrar a busca de uma ou mais partes + vinculadas a um ato principal. + (O Schema AtoParteSchema é presumido para a entrada que contém + o ato_principal_id para busca) + """ + + def execute(self, ato_parte_schema: AtoParteSchema): + + # Instânciamento da Action + show_action = ShowAction() + + # Executa a ação em questão (busca pelo ato_principal_id) + data = show_action.execute(ato_parte_schema) + + if not data: + # Retorna uma exceção 404 + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Não foi possível localizar partes para o ato principal informado.", + ) + + # Retorno da informação + return data diff --git a/packages/v1/administrativo/services/ato_parte/ato_parte_update_service.py b/packages/v1/administrativo/services/ato_parte/ato_parte_update_service.py new file mode 100644 index 0000000..ace9ac0 --- /dev/null +++ b/packages/v1/administrativo/services/ato_parte/ato_parte_update_service.py @@ -0,0 +1,25 @@ +from packages.v1.administrativo.schemas.ato_parte_schema import ( + AtoParteUpdateSchema, +) +from packages.v1.administrativo.actions.ato_parte.ato_parte_update_action import ( + UpdateAction, +) + + +class UpdateService: + """ + Service responsável por validar o schema e executar a Action de atualização + da parte de ato. + """ + + def execute(self, ato_parte_id: int, ato_parte_schema: AtoParteUpdateSchema): + + # A lógica de verificação de duplicidade de campos sensíveis (como CPF/CNPJ) + # geralmente é tratada em um ponto anterior, como no Service de SAVE ou na Action. + # Aqui, o foco é na orquestração da atualização. + + # Instânciamento de ações + updateAction = UpdateAction() + + # Executa a atualização, passando o ID e o schema com os dados + return updateAction.execute(ato_parte_id, ato_parte_schema) diff --git a/packages/v1/administrativo/services/ato_parte/ato_principal_show_service.py b/packages/v1/administrativo/services/ato_parte/ato_principal_show_service.py new file mode 100644 index 0000000..89ed72b --- /dev/null +++ b/packages/v1/administrativo/services/ato_parte/ato_principal_show_service.py @@ -0,0 +1,30 @@ +from fastapi import HTTPException, status +from packages.v1.administrativo.schemas.ato_parte_schema import AtoParteSchema +from packages.v1.administrativo.actions.ato_parte.ato_principal_show_action import ( + AtoPartePrincipalShowAction, +) + + +class AtoPrincipalShowService: + """ + Service responsável por orquestrar a busca de partes vinculadas + a um ato principal específico (busca por ato_principal_id). + """ + + def execute(self, ato_parte_schema: AtoParteSchema): + + # Instânciamento da Action adaptada + show_action = AtoPartePrincipalShowAction() + + # Executa a ação em questão (busca pelas partes do ato principal) + data = show_action.execute(ato_parte_schema) + + if not data: + # Retorna uma exceção 404 + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Não foi possível localizar partes para o ato principal.", + ) + + # Retorno da informação + return data diff --git a/packages/v1/administrativo/services/ato_principal/ato_principal_delete_service.py b/packages/v1/administrativo/services/ato_principal/ato_principal_delete_service.py new file mode 100644 index 0000000..22349b7 --- /dev/null +++ b/packages/v1/administrativo/services/ato_principal/ato_principal_delete_service.py @@ -0,0 +1,25 @@ +from packages.v1.administrativo.schemas.ato_principal_schema import AtoPrincipalSchema +from packages.v1.administrativo.actions.ato_principal.ato_principal_delete_action import ( + DeleteAction, +) + + +class DeleteService: + """ + Camada de Serviço responsável por orquestrar a exclusão + de um AtoPrincipal, geralmente fazendo validações de negócio + antes de chamar a Action de exclusão. + """ + + def execute(self, ato_principal_schema: AtoPrincipalSchema): + + # Instânciamento de ação (DeleteAction, mantendo o padrão) + delete_action = DeleteAction() + + # Executa a ação em questão, passando o schema com o ID + # Assumindo que o schema AtoPrincipalSchema ou um específico + # como AtoPrincipalIdSchema possui o ID necessário para a exclusão. + data = delete_action.execute(ato_principal_schema) + + # Retorno da informação (inclui a mensagem de sucesso do Repository/Action) + return data diff --git a/packages/v1/administrativo/services/ato_principal/ato_principal_get_codigo_service.py b/packages/v1/administrativo/services/ato_principal/ato_principal_get_codigo_service.py new file mode 100644 index 0000000..6aa0b07 --- /dev/null +++ b/packages/v1/administrativo/services/ato_principal/ato_principal_get_codigo_service.py @@ -0,0 +1,51 @@ +# packages/v1/administrativo/services/ato_principal/ato_principal_get_codigo_service.py + +from fastapi import HTTPException, status +from database.mysql import SessionLocal +from packages.v1.administrativo.models.ato_principal_model import AtoPrincipal + + +class GetCodigoService: + """ + Serviço responsável por buscar um registro de ato_principal + com base no código do selo ou código do ato. + """ + + def execute(self, codigo_selo: str = None, codigo_ato: str = None): + # Garante que pelo menos um parâmetro foi informado + if not codigo_selo and not codigo_ato: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=[ + { + "input": "codigo", + "message": "Informe pelo menos 'codigo_selo' ou 'codigo_ato'.", + } + ], + ) + + db = SessionLocal() + try: + query = db.query(AtoPrincipal) + + if codigo_selo: + query = query.filter(AtoPrincipal.codigo_selo == codigo_selo) + elif codigo_ato: + query = query.filter(AtoPrincipal.codigo_ato == codigo_ato) + + result = query.first() + + if not result: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=[ + { + "input": "codigo", + "message": "Nenhum ato principal encontrado para o código informado.", + } + ], + ) + + return result + finally: + db.close() diff --git a/packages/v1/administrativo/services/ato_principal/ato_principal_index_service.py b/packages/v1/administrativo/services/ato_principal/ato_principal_index_service.py new file mode 100644 index 0000000..0103909 --- /dev/null +++ b/packages/v1/administrativo/services/ato_principal/ato_principal_index_service.py @@ -0,0 +1,33 @@ +from fastapi import HTTPException, status +from packages.v1.administrativo.actions.ato_principal.ato_principal_index_action import ( + IndexAction, +) + +# Nota: O Schema de índice (AtoPrincipalSchema) não é importado aqui +# porque a Action já deve fazer a conversão da lista de Models para lista de Schemas. + + +class IndexService: + """ + Camada de Serviço responsável por orquestrar a listagem (Index) + de todos os Atos Principais. + """ + + def execute(self): + + # Instânciamento da Action + index_action = IndexAction() + + # Executa a busca de todos os atos principais + data = index_action.execute() + + # Verifica se foram localizados registros + if not data: + # Retorna uma exceção 404 + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Não foi possível localizar os atos principais.", + ) + + # Retorna as informações localizadas + return data diff --git a/packages/v1/administrativo/services/ato_principal/ato_principal_save_multiple_service.py b/packages/v1/administrativo/services/ato_principal/ato_principal_save_multiple_service.py new file mode 100644 index 0000000..56d07f9 --- /dev/null +++ b/packages/v1/administrativo/services/ato_principal/ato_principal_save_multiple_service.py @@ -0,0 +1,22 @@ +from fastapi import status, HTTPException + +# Assumindo a existência dos Schemas com o prefixo 'ato_principal' +from packages.v1.administrativo.schemas.ato_principal_schema import ( + AtoPrincipalSaveSchema, +) + +# Assumindo a existência da Action de salvamento com o novo prefixo +from packages.v1.administrativo.actions.ato_principal.ato_principal_save_multiple_action import ( + SaveMultipleAction, +) + + +class SaveMultipleService: + + def execute(self, atos_principais: list[AtoPrincipalSaveSchema]): + save_action = SaveMultipleAction() + + # A lista completa é passada diretamente para a Action. + results = save_action.execute(atos_principais) + + return results diff --git a/packages/v1/administrativo/services/ato_principal/ato_principal_save_service.py b/packages/v1/administrativo/services/ato_principal/ato_principal_save_service.py new file mode 100644 index 0000000..d0fd493 --- /dev/null +++ b/packages/v1/administrativo/services/ato_principal/ato_principal_save_service.py @@ -0,0 +1,23 @@ +from fastapi import status, HTTPException + +# Assumindo a existência dos Schemas com o prefixo 'ato_principal' +from packages.v1.administrativo.schemas.ato_principal_schema import ( + AtoPrincipalSaveSchema, +) + +# Assumindo a existência da Action de salvamento com o novo prefixo +from packages.v1.administrativo.actions.ato_principal.ato_principal_save_action import ( + SaveAction, +) + + +# Mantendo o padrão de nome de classe +class SaveService: + + def execute(self, ato_principal_schema: AtoPrincipalSaveSchema): + + # Instânciamento da Action de salvamento + saveAction = SaveAction() + + # Retorna o resultado da ação de salvamento + return saveAction.execute(ato_principal_schema) diff --git a/packages/v1/administrativo/services/ato_principal/ato_principal_show_service.py b/packages/v1/administrativo/services/ato_principal/ato_principal_show_service.py new file mode 100644 index 0000000..56ff748 --- /dev/null +++ b/packages/v1/administrativo/services/ato_principal/ato_principal_show_service.py @@ -0,0 +1,37 @@ +from fastapi import HTTPException, status + +# Assumindo que o novo schema se chama AtoPrincipalSchema +from packages.v1.administrativo.schemas.ato_principal_schema import AtoPrincipalSchema + +# Assumindo que a nova action se chama ShowAction +from packages.v1.administrativo.actions.ato_principal.ato_principal_show_action import ( + ShowAction, +) + + +# Mantendo o padrão de nome de classe +class ShowService: + """ + Serviço responsável por buscar os dados de um Ato Principal + utilizando o seu ID como parâmetro de busca. + """ + + # O parâmetro do execute deve usar o novo Schema + def execute(self, ato_principal_schema: AtoPrincipalSchema): + + # Instânciamento da Action com o novo nome + show_action = ShowAction() + + # Executa a ação em questão (busca pelo ID) + # O action.execute receberá o schema com o ID a ser buscado + data = show_action.execute(ato_principal_schema) + + if not data: + # Retorna uma exceção 404 se não encontrar + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Não foi possível localizar o Ato Principal.", + ) + + # Retorno da informação (dados do Ato Principal) + return data diff --git a/packages/v1/administrativo/services/ato_principal/ato_principal_update_service.py b/packages/v1/administrativo/services/ato_principal/ato_principal_update_service.py new file mode 100644 index 0000000..446a80f --- /dev/null +++ b/packages/v1/administrativo/services/ato_principal/ato_principal_update_service.py @@ -0,0 +1,28 @@ +# Assumindo a existência do Schema de Update com o prefixo 'ato_principal' +from packages.v1.administrativo.schemas.ato_principal_schema import ( + AtoPrincipalUpdateSchema, +) + +# Assumindo a existência da Action de Update com o prefixo 'ato_principal' +from packages.v1.administrativo.actions.ato_principal.ato_principal_update_action import ( + UpdateAction, +) + + +# Mantendo o padrão de nome de classe +class UpdateService: + """ + Serviço responsável por coordenar a atualização de um Ato Principal + com base no seu ID e nos dados fornecidos. + """ + + # Ajusta os nomes dos parâmetros para o novo prefixo + def execute( + self, ato_principal_id: int, ato_principal_schema: AtoPrincipalUpdateSchema + ): + + # Instânciamento da Action + updateAction = UpdateAction() + + # Executa a ação de atualização, passando o ID e o Schema de atualização + return updateAction.execute(ato_principal_id, ato_principal_schema) diff --git a/packages/v1/administrativo/services/ato_principal/go/ato_principal_index_service.py b/packages/v1/administrativo/services/ato_principal/go/ato_principal_index_service.py deleted file mode 100644 index 3aa06bd..0000000 --- a/packages/v1/administrativo/services/ato_principal/go/ato_principal_index_service.py +++ /dev/null @@ -1,45 +0,0 @@ -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 diff --git a/packages/v1/administrativo/services/usuario/usuario_authenticate_service.py b/packages/v1/administrativo/services/usuario/usuario_authenticate_service.py new file mode 100644 index 0000000..855f4ac --- /dev/null +++ b/packages/v1/administrativo/services/usuario/usuario_authenticate_service.py @@ -0,0 +1,58 @@ +from fastapi import HTTPException, status +from actions.jwt.create_token import CreateToken +from packages.v1.administrativo.schemas.usuario_schema import UsuarioAuthenticateSchema +from packages.v1.administrativo.actions.usuario.usuario_get_by_authenticate_action import ( + GetByAuthenticateAction, +) +import json +from actions.security.security import Security + + +class AuthenticateService: + + def execute(self, user_authenticate_schema: UsuarioAuthenticateSchema): + + # Instância da action de autenticação + get_by_authenticate_action = GetByAuthenticateAction() + get_by_authenticate_result = get_by_authenticate_action.execute( + user_authenticate_schema + ) + + # Verifica se o usuário existe + if not get_by_authenticate_result: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Usuário não encontrado ou credenciais inválidas", + ) + + # Verifica se a senha armazenada é um hash válido + if not Security.is_hash(get_by_authenticate_result.password): + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="A senha armazenada é inválida", + ) + + # Verifica se a senha informada confere + if not Security.verify_password( + user_authenticate_schema.password, get_by_authenticate_result.password + ): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, detail="Senha incorreta" + ) + + # Verifica se o usuário está ativo + if get_by_authenticate_result.status != "A": + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="O usuário encontra-se desativado", + ) + + # Gera o token de acesso + create_token = CreateToken() + jwtUser = { + "usuario_id": int(get_by_authenticate_result.usuario_id), + "nome": str(get_by_authenticate_result.nome), + "email": str(get_by_authenticate_result.email), + } + + return create_token.execute("access-token", json.dumps(jwtUser)) diff --git a/packages/v1/administrativo/services/usuario/usuario_delete_service.py b/packages/v1/administrativo/services/usuario/usuario_delete_service.py new file mode 100644 index 0000000..d0c1283 --- /dev/null +++ b/packages/v1/administrativo/services/usuario/usuario_delete_service.py @@ -0,0 +1,18 @@ +from packages.v1.administrativo.schemas.usuario_schema import UsuarioIdSchema +from packages.v1.administrativo.actions.usuario.usuario_delete_action import ( + DeleteAction, +) + + +class DeleteService: + + def execute(self, usuario_schema: UsuarioIdSchema): + + # Instânciamento de ação + delete_action = DeleteAction() + + # Executa a ação em questão + data = delete_action.execute(usuario_schema) + + # Retorno da informação + return data diff --git a/packages/v1/administrativo/services/usuario/usuario_get_cpf_service.py b/packages/v1/administrativo/services/usuario/usuario_get_cpf_service.py new file mode 100644 index 0000000..cd91995 --- /dev/null +++ b/packages/v1/administrativo/services/usuario/usuario_get_cpf_service.py @@ -0,0 +1,26 @@ +from fastapi import HTTPException, status + +from packages.v1.administrativo.schemas.user_schema import UserCpfSchema +from packages.v1.administrativo.actions.user.user_get_by_cpf_action import GetByUsuarioCpfAction + +class GetCpfService: + + def execute(self, usuario_schema: UserCpfSchema, messageValidate: bool): + + # Instânciamento de ação + cpf_action = GetByUsuarioCpfAction() + + # Executa a ação em questão + data = cpf_action.execute(usuario_schema) + + if messageValidate: + + if not data: + # Retorna uma exceção + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail='Não foi possível localizar o CPF do usuário' + ) + + # Retorno da informação + return data \ No newline at end of file diff --git a/packages/v1/administrativo/services/usuario/usuario_get_email_service.py b/packages/v1/administrativo/services/usuario/usuario_get_email_service.py new file mode 100644 index 0000000..2a398bc --- /dev/null +++ b/packages/v1/administrativo/services/usuario/usuario_get_email_service.py @@ -0,0 +1,28 @@ +from fastapi import HTTPException, status +from packages.v1.administrativo.schemas.usuario_schema import UsuarioEmailSchema +from packages.v1.administrativo.actions.usuario.usuario_get_by_email_action import ( + GetByUsuarioEmailAction, +) + + +class GetEmailService: + + def execute(self, usuario_schema: UsuarioEmailSchema, messageValidate: bool): + + # Instânciamento de ação + email_action = GetByUsuarioEmailAction() + + # Executa a ação em questão + data = email_action.execute(usuario_schema) + + if messageValidate: + + if not data: + # Retorna uma exceção + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Não foi possível localizar o e-mail do usuário", + ) + + # Retorno da informação + return data diff --git a/packages/v1/administrativo/services/usuario/usuario_get_login_service.py b/packages/v1/administrativo/services/usuario/usuario_get_login_service.py new file mode 100644 index 0000000..c932a4d --- /dev/null +++ b/packages/v1/administrativo/services/usuario/usuario_get_login_service.py @@ -0,0 +1,26 @@ +from fastapi import HTTPException, status + +from packages.v1.administrativo.schemas.user_schema import UserLoginSchema +from packages.v1.administrativo.actions.user.user_get_by_login_action import GetByUsuarioLoginAction + +class GetLoginService: + + def execute(self, usuario_schema: UserLoginSchema, messageValidate: bool): + + # Instânciamento de ação + login_action = GetByUsuarioLoginAction() + + # Executa a ação em questão + data = login_action.execute(usuario_schema) + + if messageValidate: + + if not data: + # Retorna uma exceção + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail='Não foi possível localizar o login do usuário' + ) + + # Retorno da informação + return data \ No newline at end of file diff --git a/packages/v1/administrativo/services/usuario/usuario_index_service.py b/packages/v1/administrativo/services/usuario/usuario_index_service.py new file mode 100644 index 0000000..3d4bf47 --- /dev/null +++ b/packages/v1/administrativo/services/usuario/usuario_index_service.py @@ -0,0 +1,25 @@ +from fastapi import HTTPException, status +from packages.v1.administrativo.schemas.usuario_schema import UsuarioSchema +from packages.v1.administrativo.actions.usuario.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 diff --git a/packages/v1/administrativo/services/usuario/usuario_me_service.py b/packages/v1/administrativo/services/usuario/usuario_me_service.py new file mode 100644 index 0000000..e67bdb1 --- /dev/null +++ b/packages/v1/administrativo/services/usuario/usuario_me_service.py @@ -0,0 +1,21 @@ +import ast +from packages.v1.administrativo.schemas.usuario_schema import UsuarioIdSchema +from packages.v1.administrativo.actions.usuario.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 + usuario_schema = UsuarioIdSchema(usuario_id=int(usuario_data["usuario_id"])) + + # Executa a ação em questão + return get_by_usuario_id_action.execute(usuario_schema) diff --git a/packages/v1/administrativo/services/usuario/usuario_save_service.py b/packages/v1/administrativo/services/usuario/usuario_save_service.py new file mode 100644 index 0000000..0aa8177 --- /dev/null +++ b/packages/v1/administrativo/services/usuario/usuario_save_service.py @@ -0,0 +1,44 @@ +from fastapi import status, HTTPException +from packages.v1.administrativo.schemas.usuario_schema import ( + UsuarioSaveSchema, + UsuarioEmailSchema, +) +from packages.v1.administrativo.services.usuario.usuario_get_email_service import ( + GetEmailService, +) +from packages.v1.administrativo.actions.usuario.usuario_save_action import SaveAction + + +class SaveService: + + def execute(self, usuario_schema: UsuarioSaveSchema): + + # Armazena possíveis erros + errors = [] + + # Instânciamento da service + email_service = GetEmailService() + + # Verifica se o email já esta sendo utilizado + self.response = email_service.execute( + UsuarioEmailSchema(email=usuario_schema.email), False + ) + + # Se houver retorno significa que o e-mail já esta sendo utiizado + if self.response: + errors.append( + { + "input": "email", + "message": "O e-mail informado já esta sendo utilizado.", + } + ) + + # Se houver erros, informo + if errors: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=errors) + + # Instânciamento de ações + saveAction = SaveAction() + + # Retorna todos produtos desejados + return saveAction.execute(usuario_schema) diff --git a/packages/v1/administrativo/services/usuario/usuario_show_service.py b/packages/v1/administrativo/services/usuario/usuario_show_service.py new file mode 100644 index 0000000..c671917 --- /dev/null +++ b/packages/v1/administrativo/services/usuario/usuario_show_service.py @@ -0,0 +1,24 @@ +from fastapi import HTTPException, status +from packages.v1.administrativo.schemas.usuario_schema import UsuarioSchema +from packages.v1.administrativo.actions.usuario.usuario_show_action import ShowAction + + +class ShowService: + + def execute(self, usuario_schema: UsuarioSchema): + + # Instânciamento de ação + show_action = ShowAction() + + # Executa a ação em questão + data = show_action.execute(usuario_schema) + + if not data: + # Retorna uma exceção + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Não foi possível localizar o registro", + ) + + # Retorno da informação + return data diff --git a/packages/v1/administrativo/services/usuario/usuario_update_service.py b/packages/v1/administrativo/services/usuario/usuario_update_service.py new file mode 100644 index 0000000..28924f1 --- /dev/null +++ b/packages/v1/administrativo/services/usuario/usuario_update_service.py @@ -0,0 +1,15 @@ +from packages.v1.administrativo.schemas.usuario_schema import UsuarioUpdateSchema +from packages.v1.administrativo.actions.usuario.usuario_update_action import ( + UpdateAction, +) + + +class UpdateService: + + def execute(self, usuario_id: int, usuario_schema: UsuarioUpdateSchema): + + # Instânciamento de ações + updateAction = UpdateAction() + + # Retorna todos produtos desejados + return updateAction.execute(usuario_id, usuario_schema) diff --git a/packages/v1/api.py b/packages/v1/api.py index cb86aec..e7d43a6 100644 --- a/packages/v1/api.py +++ b/packages/v1/api.py @@ -3,11 +3,35 @@ from fastapi import APIRouter # Importa os módulos de rotas específicos from packages.v1.administrativo.endpoints import ato_principal_endpoint +from packages.v1.administrativo.endpoints import usuario_endpoint +from packages.v1.administrativo.endpoints import ato_documento_endpoint +from packages.v1.administrativo.endpoints import ato_parte_endpoint # Cria uma instância do APIRouter que vai agregar todas as rotas da API api_router = APIRouter() -# Inclui as rotas de g_cartorio +# Inclui as rotas de usuario +api_router.include_router( + usuario_endpoint.router, + prefix="/usuario", + tags=["Gerenciamento de usuários"], +) + +# Inclui as rotas de ato documento +api_router.include_router( + ato_documento_endpoint.router, + prefix="/ato_documento", + tags=["Gerenciamento de documentos do ato"], +) + +# Inclui as rotas de ato parte +api_router.include_router( + ato_parte_endpoint.router, + prefix="/ato_parte", + tags=["Gerenciamento de partes do ato"], +) + +# Inclui as rotas de ato_principal api_router.include_router( ato_principal_endpoint.router, prefix="/ato", tags=["Dados do Ato"] -) \ No newline at end of file +) diff --git a/requirements.txt b/requirements.txt index 2615e14..5567c00 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,10 @@ annotated-types==0.7.0 anyio==4.10.0 bcrypt==3.1.7 -cffi==1.17.1 +cffi==2.0.0 click==8.2.1 colorama==0.4.6 +cryptography==46.0.3 dnspython==2.7.0 ecdsa==0.19.1 email_validator==2.2.0 @@ -20,6 +21,7 @@ pyasn1==0.6.1 pycparser==2.22 pydantic==2.11.7 pydantic_core==2.33.2 +PyMySQL==1.1.2 python-dateutil==2.9.0.post0 python-jose==3.5.0 pytz==2025.2 @@ -32,4 +34,3 @@ starlette==0.47.2 typing-inspection==0.4.1 typing_extensions==4.14.1 uvicorn==0.35.0 -psycopg2-binary>=2.9.9