first commit

This commit is contained in:
Claudecio Martins
2025-11-04 18:22:02 +01:00
commit c1184d2878
4394 changed files with 444123 additions and 0 deletions

42
app/Common/Config/Config.php Executable file
View File

@@ -0,0 +1,42 @@
<?php
namespace Zampet\Config;
use Dotenv\Dotenv;
class Config {
private $config = [];
private static $instance = null;
/**
* Método construtor
*/
public function __construct() {
// Carrega apenas para $_ENV, não para $_SERVER
$dotEnv = Dotenv::createUnsafeImmutable(
paths: realpath(path: __DIR__ . "/../../../"),
names: '.env'
);
$dotEnv->safeLoad(); // não dá erro se .env não existir
// Carrega as configurações
$this->config = require_once realpath(path: __DIR__ . "/Consts.php");
}
/**
* Retorna a instância única da classe
*/
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new Config();
}
return self::$instance;
}
/**
* Retorna uma configuração
*/
public function get(string $key) {
return $this->config[$key] ?? null;
}
}

21
app/Common/Config/Consts.php Executable file
View File

@@ -0,0 +1,21 @@
<?php
return [
'SYSTEM_URL' => (string) getenv(name: 'SYSTEM_URL'),
'SYSTEM_VERSION' => (string) getenv(name: 'SYSTEM_VERSION'),
'SYSTEM_TIMEZONE' => (string) getenv(name: 'SYSTEM_TIMEZONE'),
'SYSTEM_ENVIRONMENT_ID' => (int) getenv(name: 'SYSTEM_ENVIRONMENT_ID'),
'SYSTEM_FRONTEND_URL' => (string) getenv(name: 'SYSTEM_FRONTEND_URL'),
'JWT_ALGO' => getenv(name: 'JWT_ALGO'),
'JWT_ISSUER' => getenv(name: 'JWT_ISSUER'),
'JWT_SECRET' => getenv(name: 'JWT_SECRET'),
'JWT_EXPIRATION' => (int) getenv(name: 'JWT_EXPIRATION'),
'DEFAULT_DATABASE_HOST' => getenv(name: 'DEFAULT_DATABASE_HOST'),
'DEFAULT_DATABASE_PORT' => getenv(name: 'DEFAULT_DATABASE_PORT'),
'DEFAULT_DATABASE_DRIVER' => getenv(name: 'DEFAULT_DATABASE_DRIVER'),
'DEFAULT_DATABASE_SCHEMA' => getenv(name: 'DEFAULT_DATABASE_SCHEMA'),
'DEFAULT_DATABASE_CHARSET' => getenv(name: 'DEFAULT_DATABASE_CHARSET'),
'DEFAULT_DATABASE_USERNAME' => getenv(name: 'DEFAULT_DATABASE_USERNAME'),
'DEFAULT_DATABASE_PASSWORD' => getenv(name: 'DEFAULT_DATABASE_PASSWORD'),
];

View File

@@ -0,0 +1,174 @@
<?php
namespace Zampet\Helpers;
class DataValidator {
private array $inputs;
private array $errors = [];
public function __construct(array $inputs) {
$this->inputs = $inputs;
}
/**
* Valida um campo usando uma regra específica.
*/
public function validate(string $field, callable|string $rule, string $message = '', array $additionalParams = []): static {
$value = $this->inputs[$field] ?? null;
// Se for string, monta callable usando a própria classe
if (is_string(value: $rule) && method_exists(object_or_class: self::class, method: $rule)) {
$rule = [self::class, $rule];
}
$params = array_merge([$value], $additionalParams);
$result = call_user_func_array(callback: $rule, args: $params);
// Caso seja validação de senha (retorna array)
if (is_array(value: $result) && isset($result['valid'])) {
if (!$result['valid']) {
$this->errors[$field] = [
'status' => 'is_invalid',
'message' => $result['errors']
];
}
}
// Para outras validações booleanas
elseif ($result === false) {
$this->errors[$field] = [
'status' => 'is_invalid',
'message' => $message
];
}
return $this;
}
public function getErrors(): array {
return $this->errors;
}
public function passes(): bool {
return empty($this->errors);
}
// ---------- Regras comuns ----------
public static function validateEmail(string $value): bool {
return (bool) filter_var(value: $value, filter: FILTER_VALIDATE_EMAIL);
}
public static function validatePhone(string $value, int $minLength = 10, int $maxLength = 13): bool {
$digits = preg_replace(pattern: '/\D+/', replacement: '', subject: $value);
$len = strlen(string: $digits);
return $len >= $minLength && $len <= $maxLength;
}
public static function validateCpf(string $value): bool {
$cpf = preg_replace(pattern: '/\D+/', replacement: '', subject: $value);
if (strlen(string: $cpf) !== 11 || preg_match(pattern: '/(\d)\1{10}/', subject: $cpf)) {
return false;
}
for ($t = 9; $t < 11; $t++) {
$d = 0;
for ($c = 0; $c < $t; $c++) {
$d += $cpf[$c] * (($t + 1) - $c);
}
$d = ((10 * $d) % 11) % 10;
if ($cpf[$c] != $d) {
return false;
}
}
return true;
}
public static function validateCnpj(string $value): bool {
$cnpj = preg_replace(pattern: '/\D+/', replacement: '', subject: $value);
if (strlen(string: $cnpj) != 14) {
return false;
}
if (preg_match(pattern: '/(\d)\1{13}/', subject: $cnpj)) {
return false;
}
$tamanho = 12;
$multiplicadores = [5,4,3,2,9,8,7,6,5,4,3,2];
for ($i = 0; $i < 2; $i++) {
$soma = 0;
for ($j = 0; $j < $tamanho; $j++) {
$soma += $cnpj[$j] * $multiplicadores[$j + ($i == 1 ? 1 : 0)];
}
$digito = ($soma % 11 < 2) ? 0 : 11 - ($soma % 11);
if ($cnpj[$tamanho] != $digito) {
return false;
}
$tamanho++;
}
return true;
}
public static function notEmpty(mixed $value): bool {
if (is_array(value: $value)) {
return !empty(array_filter(array: $value, callback: fn($v) => $v !== null && $v !== ''));
}
return !empty(trim(string: (string) $value));
}
public static function validatePassword(string $password, array $rules = []): array {
$errors = [];
$minLength = $rules['minLength'] ?? 8;
$maxLength = $rules['maxLength'] ?? 64;
$requireUpper = $rules['upper'] ?? true;
$requireLower = $rules['lower'] ?? true;
$requireDigit = $rules['digit'] ?? true;
$requireSymbol = $rules['symbol'] ?? true;
if (strlen(string: $password) < $minLength) {
$errors[] = "Senha deve ter no mínimo {$minLength} caracteres.";
}
if (strlen(string: $password) > $maxLength) {
$errors[] = "Senha deve ter no máximo {$maxLength} caracteres.";
}
if ($requireUpper && !preg_match(pattern: '/[A-Z]/', subject: $password)) {
$errors[] = "Senha deve conter pelo menos uma letra maiúscula.";
}
if ($requireLower && !preg_match(pattern: '/[a-z]/', subject: $password)) {
$errors[] = "Senha deve conter pelo menos uma letra minúscula.";
}
if ($requireDigit && !preg_match(pattern: '/\d/', subject: $password)) {
$errors[] = "Senha deve conter pelo menos um número.";
}
if ($requireSymbol && !preg_match(pattern: '/[\W_]/', subject: $password)) {
$errors[] = "Senha deve conter pelo menos um símbolo.";
}
return [
'valid' => empty($errors),
'errors' => $errors
];
}
public static function validateList(mixed $value, array $validOptions): bool {
if (is_array(value: $value)) {
foreach ($value as $item) {
if (!in_array(needle: $item, haystack: $validOptions, strict: true)) {
return false;
}
}
return true;
}
return in_array(needle: $value, haystack: $validOptions, strict: true);
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Zampet\Helpers;
use DateTime;
class Sanitizer {
public static function string(mixed $value): string|null {
if(is_null($value)) {
return null;
}
return trim(string: strip_tags(string: $value));
}
public static function int(mixed $value): int {
return filter_var(value: $value, filter: FILTER_VALIDATE_INT) ?? 0;
}
public static function float(mixed $value): float {
return filter_var(value: $value, filter: FILTER_VALIDATE_FLOAT) ?? 0.0;
}
public static function email(string $value): string {
$value = trim(string: strtolower(string: $value));
return filter_var(value: $value, filter: FILTER_VALIDATE_EMAIL) ?: '';
}
public static function phone(string $value): string {
// remove tudo que não for número
return preg_replace(pattern: '/\D+/', replacement: '', subject: $value) ?? '';
}
public static function document(string $value): string {
// CPF, CNPJ, RG etc. só números
return preg_replace(pattern: '/\D+/', replacement: '', subject: $value) ?? '';
}
public static function date(string $value, string $format = 'Y-m-d'): string {
$value = trim($value);
if ($value === '') {
return '';
}
$date = DateTime::createFromFormat('!'.$format, $value);
if (!$date) {
// tenta converter qualquer formato válido
$date = date_create($value);
}
return $date ? $date->format($format) : '';
}
public static function datetime(string $value, string $format = 'Y-m-d H:i:s'): string {
$value = trim($value);
if ($value === '') {
return '';
}
$date = DateTime::createFromFormat('!'.$format, $value);
if (!$date) {
$date = date_create($value);
}
return $date ? $date->format($format) : '';
}
}

8
app/Common/Services/DB.php Executable file
View File

@@ -0,0 +1,8 @@
<?php
namespace Zampet\Services;
use AxiumPHP\Core\Database;
class DB extends Database {
}

View File

@@ -0,0 +1,172 @@
<?php
namespace Zampet\Services;
use DateTime;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Zampet\Config\Config;
class JWTService {
private static string $issuer;
private static int $expiration;
private static string $algorithm;
private static string $secretKey;
/**
* Inicializa as configurações para a geração de tokens JWT.
*
* Este método estático carrega as informações necessárias para a criação e validação de tokens
* JWT (JSON Web Tokens) a partir de uma classe de configuração (`Config`). Ele busca a chave secreta,
* o algoritmo de criptografia, o emissor e o tempo de expiração definidos para a aplicação
* e os armazena como propriedades estáticas da classe.
*
* Este método deve ser chamado antes de qualquer operação que envolva a geração ou
* verificação de tokens, garantindo que as configurações estejam prontas para uso.
*
* @return void
*/
public static function init(): void {
$Config = Config::getInstance();
self::$issuer = $Config->get(key: 'JWT_ISSUER');
self::$algorithm = $Config->get(key: 'JWT_ALGO');
self::$secretKey = $Config->get(key: 'JWT_SECRET');
self::$expiration = $Config->get(key: 'JWT_EXPIRATION');
}
/**
* Gera um token JWT com base em um payload e nas configurações da aplicação.
*
* Este método cria um token JWT válido a partir de um array de dados fornecido (`$payload`),
* adicionando automaticamente metadados essenciais como a data de emissão, a data de expiração e o emissor.
*
* O processo detalhado inclui:
* 1. **Inicialização:** Chama o método `init()` para garantir que todas as configurações do JWT
* (chave secreta, algoritmo, etc.) estejam carregadas.
* 2. **Definição dos Timestamps:** Calcula o timestamp de emissão (`iat`) e o de expiração (`exp`)
* com base no tempo de expiração definido nas configurações.
* 3. **Montagem do Payload:** Adiciona as chaves `iat`, `exp` e `iss` (emissor) ao array `$payload`.
* 4. **Codificação do Token:** Utiliza a biblioteca `JWT::encode()` para codificar o payload
* com a chave secreta e o algoritmo definidos nas configurações.
*
* @param array $payload Um array associativo contendo os dados personalizados que serão incluídos no token.
* @return string O token JWT gerado como uma string.
*/
public static function generateToken(array $payload): string {
// Carrega as informações do arquivo de configuração
self::init();
// Define data de emissão e calcula a data de expiração do token
$expiration = self::$expiration;
$createdAt = (new DateTime())->getTimestamp();
$expireAt = (new DateTime())->modify(modifier: "+{$expiration} seconds")->getTimestamp();
// Adiciona data de criação ao payload
$payload['iat'] = $createdAt;
// Adiciona a expiração ao payload
$payload['exp'] = $expireAt;
// Adiciona o emissor ao payload
$payload['iss'] = self::$issuer;
return JWT::encode(payload: $payload, key: self::$secretKey, alg: self::$algorithm);
}
/**
* Valida um token JWT e retorna o objeto de payload decodificado.
*
* Este método estático decodifica e valida um token JWT fornecido, utilizando
* a chave secreta e o algoritmo de criptografia configurados na classe.
*
* #### Como funciona:
* 1. Recebe o token (`$token`) que precisa ser validado.
* 2. Usa a biblioteca `JWT::decode()` para decodificar o token.
* 3. Para a decodificação, ele cria uma nova instância de `Key`, passando
* a chave secreta (`self::$secretKey`) e o algoritmo (`self::$algorithm`).
* Isso garante que o token só será decodificado se tiver sido assinado com a chave e algoritmo corretos.
* 4. Se o token for válido e a assinatura corresponder, a função retorna o payload do token
* como um objeto.
* 5. Se o token for inválido (por exemplo, assinatura incorreta, expirado ou formato incorreto),
* a biblioteca JWT lançará uma exceção, que a função que chama este método deve capturar.
*
* @param string $token O token JWT a ser validado e decodificado.
* @return object O objeto de payload decodificado do token.
*/
public static function validateToken(string $token): object {
// Inicializa as configurações se ainda não estiverem definidas
if (!isset(self::$secretKey)) {
self::init();
}
return JWT::decode(jwt: $token, keyOrKeyArray: new Key(keyMaterial: self::$secretKey, algorithm: self::$algorithm));
}
/**
* Decodifica um token JWT e retorna seu payload como um array associativo.
*
* Este método estático é responsável por decodificar e validar um token JWT. Antes de decodificar, ele garante que as configurações de chave secreta e algoritmo (`self::$secretKey` e `self::$algorithm`) já foram inicializadas chamando o método `init()` se necessário.
*
* #### Como Funciona:
* 1. **Inicialização:** Primeiro, verifica se a chave secreta (`self::$secretKey`) já foi carregada. Se não, ele chama `self::init()` para carregar as configurações de um arquivo externo.
* 2. **Decodificação:** Usa a biblioteca `Firebase\JWT\JWT::decode()` para decodificar o token fornecido. O método passa a chave secreta e o algoritmo para verificar a assinatura do token.
* 3. **Conversão:** O payload decodificado, que por padrão é um objeto, é convertido para uma string JSON e depois para um array associativo, o que facilita o acesso aos dados.
* 4. **Retorno:** Retorna o payload do token como um array associativo. Se o token for inválido (assinatura incorreta, expirado, etc.), a biblioteca JWT lançará uma exceção, que a função que chama este método deve capturar.
*
* @param string $token O token JWT a ser decodificado.
* @return array O payload do token decodificado como um array associativo.
*/
public static function decode(string $token): array {
// Inicializa as configurações se ainda não estiverem definidas
if (!isset(self::$secretKey)) {
self::init();
}
$decoded = JWT::decode(jwt: $token, keyOrKeyArray: new Key(keyMaterial: self::$secretKey, algorithm: self::$algorithm));
return json_decode(json: json_encode(value: $decoded), associative: true);
}
/**
* Extrai o token de autenticação do tipo 'Bearer' do cabeçalho da requisição HTTP.
*
* Este método estático é um utilitário projetado para buscar um token JWT (JSON Web Token)
* que é enviado no cabeçalho `Authorization`. Ele é robusto o suficiente para verificar
* o token em diferentes superglobais do PHP, dependendo da configuração do servidor web.
*
* #### Fluxo de Busca:
* 1. Primeiro, tenta obter o cabeçalho `Authorization` diretamente da superglobal `$_SERVER`.
* 2. Em seguida, tenta a chave `HTTP_AUTHORIZATION`, que é um nome de variável comum em alguns ambientes.
* 3. Por último, usa a função `apache_request_headers()` para tentar obter o cabeçalho, caso as
* superglobais não estejam preenchidas. Isso garante compatibilidade com servidores Apache onde o cabeçalho pode não ser exposto.
* 4. Se o cabeçalho for encontrado, o valor é sanitizado (removendo espaços em branco) e a busca continua.
* 5. Se nenhum cabeçalho de autenticação for encontrado, o método retorna `null`.
*
* #### Extração do Token:
* - Depois de encontrar o cabeçalho, o método usa uma expressão regular (`/Bearer\s(\S+)/`)
* para verificar se o valor do cabeçalho está no formato `Bearer <token>`.
* - Se o padrão corresponder, ele extrai e retorna a string do token.
* - Se o padrão não corresponder, o método retorna `null`.
*
* @return string|null O token de autenticação do tipo 'Bearer' como uma string, ou `null`
* se o cabeçalho não for encontrado ou não estiver no formato esperado.
*/
public static function getBearerToken(): ?string {
if (!isset(self::$secretKey)) {
self::init();
}
// Tenta pegar o header de todas as fontes possíveis
$headers = $_SERVER['Authorization'] ?? $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? (function_exists(function: 'apache_request_headers') ? apache_request_headers() : []);
// Se veio do apache_request_headers, normaliza chaves
if (is_array(value: $headers)) {
$headers = array_change_key_case(array: $headers, case: CASE_LOWER);
$headers = $headers['authorization'] ?? '';
}
$headers = trim(string: $headers);
if ($headers && preg_match(pattern: '/Bearer\s(\S+)/', subject: $headers, matches: $matches)) {
return $matches[1];
}
return null;
}
}

0
app/Module/Auth/blank_file Executable file
View File

View File

@@ -0,0 +1,158 @@
<?php
namespace Zampet\Module\Auth\v0\Controllers;
use Zampet\Helpers\Sanitizer;
use Zampet\Services\JWTService;
use AxiumPHP\Helpers\RequestHelper;
use Zampet\Module\Auth\v0\DTO\UsuarioDTO;
use Zampet\Module\Auth\v0\Services\AuthService;
class AuthController {
/**
* Lida com a requisição de registro (criação) de um novo usuário.
*
* Este método atua como um **controlador** para o endpoint de registro. Ele recebe os dados do formulário, realiza uma validação de presença básica nos campos obrigatórios, sanitiza os dados e delega a lógica de negócio e persistência para o `AuthService`. A resposta final é formatada e enviada como JSON.
*
* #### Fluxo de Operação:
* 1. **Coleta de Dados:** Obtém os dados do formulário (`fullName`, `email`, `password`) via requisição POST.
* 2. **Validação de Presença:** Itera sobre os campos obrigatórios (`fullName`, `email`, `password`). Se qualquer um estiver vazio, o método envia uma resposta JSON com o código **400 Bad Request** e encerra a execução.
* 3. **Sanitização e DTO:** Os dados são sanitizados (string, email) e encapsulados em um **`UsuarioDTO`**.
* 4. **Delegação ao Serviço:** O método `register` do `AuthService` é chamado com o DTO. O serviço é responsável por validações de complexidade, checagem de duplicidade, criptografia de senha e persistência no banco de dados.
* 5. **Envio da Resposta JSON:** O resultado do serviço (`serviceResult`) é formatado e enviado de volta ao cliente. O código de resposta HTTP e o status JSON são extraídos do array retornado pelo serviço.
*
* @return void Este método não retorna um valor, pois seu objetivo é enviar uma resposta JSON e encerrar a execução do script.
*/
public function register(): void {
// Receber dados do formulário de registro
$form = RequestHelper::getFilteredInput(form_type: INPUT_POST);
// Dados Obrigatórios
$required_fields = [
'fullName',
'email',
'cpf',
'birthDate',
'password'
];
// Verificar se todos os campos obrigatórios estão presentes
foreach ($required_fields as $field) {
if (empty($form[$field])) {
RequestHelper::sendJsonResponse(
response_code: 400,
status: 'error',
message: "Field '{$field}' is required"
);
exit;
}
}
// Chama o serviço de autenticação para registrar o usuário
$serviceResult = (new AuthService())->register(usuarioDTO:
new UsuarioDTO(data: [
'nome_completo' => Sanitizer::string(value: $form['fullName']),
'email' => Sanitizer::email(value: $form['email']),
'senha' => Sanitizer::string(value: $form['password']),
'documentcpf' => Sanitizer::document(value: $form['cpf']),
'data_nascimento' => Sanitizer::string(value: $form['birthDate'])
])
);
// Enviar a resposta JSON
RequestHelper::sendJsonResponse(
response_code: $serviceResult['response_code'],
status: $serviceResult['status'],
message: $serviceResult['message'],
output: $serviceResult['output'] ?? []
);
exit;
}
/**
* Lida com a requisição de login de um usuário via POST.
*
* Este método atua como um **controlador** para o endpoint de login. Ele processa as credenciais enviadas pelo usuário, realiza uma validação de presença básica e delega o processo de autenticação a um serviço. A resposta da autenticação é então formatada e enviada de volta ao cliente como um JSON.
*
* #### Fluxo de Operação:
* 1. **Coleta de Dados:** Obtém os dados do formulário (`email` e `password`) enviados via requisição POST.
* 2. **Validação de Presença:** Itera sobre os campos obrigatórios. Se qualquer 3campo estiver vazio, uma resposta JSON com o código **400 Bad Request** é enviada e a execução é encerrada.
* 3. **Delegação ao Serviço:** O método `login` do **`AuthService`** é chamado com o e-mail e a senha fornecidos. O serviço é responsável por toda a lógica de autenticação (busca, verificação de senha, revogação de tokens e geração de novo JWT).
* 4. **Envio da Resposta JSON:** O resultado do serviço (`serviceResult`) é formatado para uma resposta JSON padronizada usando **`RequestHelper::sendJsonResponse`**, incluindo o código HTTP, status, mensagem e os dados de saída (como o token JWT). A execução é encerrada.
*
* @return void Este método não retorna um valor, pois seu objetivo é enviar uma resposta JSON e encerrar a execução do script.
*/
public function login(): void {
// Receber dados do formulário de login
$form = RequestHelper::getFilteredInput(form_type: INPUT_POST);
// Dados Obrigatórios
$required_fields = [
'email',
'password'
];
// Verificar se todos os campos obrigatórios estão presentes
foreach ($required_fields as $field) {
if (empty($form[$field])) {
RequestHelper::sendJsonResponse(
response_code: 400,
status: 'error',
message: "Field '{$field}' is required"
);
exit;
}
}
// Chamar o serviço de autenticação para fazer login
$serviceResult = (new AuthService())->login(email: $form['email'], password: $form['password']);
// Enviar a resposta JSON
RequestHelper::sendJsonResponse(
response_code: $serviceResult['response_code'],
status: $serviceResult['status'],
message: $serviceResult['message'],
output: $serviceResult['output'] ?? []
);
exit;
}
/**
* Lida com a requisição de logout, revogando o token de autenticação do usuário.
*
* Este método atua como um **controlador** para o endpoint de logout. Ele extrai o token JWT do cabeçalho da requisição, delega a lógica de revogação para a camada de serviço e envia uma resposta JSON formatada de volta ao cliente.
*
* #### Fluxo de Operação:
* 1. **Obtenção do Token:** O token Bearer é extraído do cabeçalho da requisição usando `JWTService::getBearerToken()`.
* 2. **Delegação ao Serviço:** O método `logout` do **`AuthService`** é chamado com o token. O serviço é responsável por revogar o token no banco de dados e gerenciar a transação.
* 3. **Envio da Resposta JSON:** O resultado da operação do serviço (`serviceResult`) é formatado para uma resposta JSON padronizada usando **`RequestHelper::sendJsonResponse`**, garantindo que o código HTTP, status, mensagem e quaisquer dados de saída sejam comunicados ao cliente.
*
* @return void Este método não retorna um valor, pois seu objetivo é enviar uma resposta JSON e encerrar a execução do script.
*/
public function logout(): void {
// Chamar o serviço de autenticação para fazer logout
$serviceResult = (new AuthService())->logout(token: JWTService::getBearerToken());
// Enviar a resposta JSON
RequestHelper::sendJsonResponse(
response_code: $serviceResult['response_code'],
status: $serviceResult['status'],
message: $serviceResult['message'],
output: $serviceResult['output'] ?? []
);
exit;
}
public function getUserData(): void {
// Chamar o serviço de autenticação para obter os dados do usuário
$serviceResult = (new AuthService())->getUserData(token: JWTService::getBearerToken());
// Enviar a resposta JSON
RequestHelper::sendJsonResponse(
response_code: $serviceResult['response_code'],
status: $serviceResult['status'],
message: $serviceResult['message'],
output: $serviceResult['output'] ?? []
);
exit;
}
}

View File

@@ -0,0 +1,237 @@
<?php
namespace Zampet\Module\Auth\v0\DTO;
class UsuarioDTO {
private ?int $id = null;
private ?string $uuid = null;
private ?string $nome_completo = null;
private ?int $status_id = 1;
private ?string $documentcpf = null;
private ?string $documentcrmv = null;
private ?string $email = null;
private ?string $senha = null;
private ?string $telefone = null;
private ?string $data_nascimento = null;
private ?string $endereco_rua = null;
private ?string $endereco_numero = null;
private ?string $endereco_complemento = null;
private ?string $endereco_bairro = null;
private ?string $endereco_cidade = null;
private ?string $endereco_uf = null;
private ?string $endereco_cep = null;
private ?string $created_at = null;
private ?string $updated_at = null;
private ?string $deleted_at = null;
/**
* Construtor do DTO.
*
* Permite inicializar os atributos do objeto a partir de um array associativo.
*
* @param array<string, mixed> $data Array associativo opcional para popular os atributos.
*/
public function __construct(array $data = []) {
if (!empty($data)) {
$this->fromArray($data);
}
}
/**
* Popula o DTO a partir de um array.
*
* @param array<string, mixed> $data
* @return void
*/
public function fromArray(array $data): void {
$this->id = $data['id'] ?? $this->id;
$this->uuid = $data['uuid'] ?? $this->uuid;
$this->nome_completo = $data['nome_completo'] ?? $this->nome_completo;
$this->status_id = $data['status_id'] ?? $this->status_id;
$this->documentcpf = $data['documentcpf'] ?? $this->documentcpf;
$this->documentcrmv = $data['documentcrmv'] ?? $this->documentcrmv;
$this->email = $data['email'] ?? $this->email;
$this->senha = $data['senha'] ?? $this->senha;
$this->telefone = $data['telefone'] ?? $this->telefone;
$this->data_nascimento = $data['data_nascimento'] ?? $this->data_nascimento;
$this->endereco_rua = $data['endereco_rua'] ?? $this->endereco_rua;
$this->endereco_numero = $data['endereco_numero'] ?? $this->endereco_numero;
$this->endereco_complemento = $data['endereco_complemento'] ?? $this->endereco_complemento;
$this->endereco_bairro = $data['endereco_bairro'] ?? $this->endereco_bairro;
$this->endereco_cidade = $data['endereco_cidade'] ?? $this->endereco_cidade;
$this->endereco_uf = $data['endereco_uf'] ?? $this->endereco_uf;
$this->endereco_cep = $data['endereco_cep'] ?? $this->endereco_cep;
$this->created_at = $data['created_at'] ?? $this->created_at;
$this->updated_at = $data['updated_at'] ?? $this->updated_at;
$this->deleted_at = $data['deleted_at'] ?? $this->deleted_at;
}
/**
* Converte o DTO para um array associativo.
*
* @return array<string, mixed>
*/
public function toArray(): array {
return [
'id' => $this->id,
'uuid' => $this->uuid,
'nome_completo' => $this->nome_completo,
'status_id' => $this->status_id,
'documentcpf' => $this->documentcpf,
'documentcrmv' => $this->documentcrmv,
'email' => $this->email,
'senha' => $this->senha,
'telefone' => $this->telefone,
'data_nascimento' => $this->data_nascimento,
'endereco_rua' => $this->endereco_rua,
'endereco_numero' => $this->endereco_numero,
'endereco_complemento' => $this->endereco_complemento,
'endereco_bairro' => $this->endereco_bairro,
'endereco_cidade' => $this->endereco_cidade,
'endereco_uf' => $this->endereco_uf,
'endereco_cep' => $this->endereco_cep,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'deleted_at' => $this->deleted_at,
];
}
public function getId(): ?int {
return $this->id;
}
public function setId(?int $id): void {
$this->id = $id;
}
public function getUuid(): ?string {
return $this->uuid;
}
public function setUuid(?string $uuid): void {
$this->uuid = $uuid;
}
public function getNomeCompleto(): ?string {
return $this->nome_completo;
}
public function setNomeCompleto(?string $nome_completo): void {
$this->nome_completo = $nome_completo;
}
public function getStatusId(): ?int {
return $this->status_id;
}
public function setStatusId(?int $status_id): void {
$this->status_id = $status_id;
}
public function getDocumentCpf(): ?string {
return $this->documentcpf;
}
public function setDocumentCpf(?string $documentcpf): void {
$this->documentcpf = $documentcpf;
}
public function getDocumentCrmv(): ?string {
return $this->documentcrmv;
}
public function setDocumentCrmv(?string $documentcrmv): void {
$this->documentcrmv = $documentcrmv;
}
public function getEmail(): ?string {
return $this->email;
}
public function setEmail(?string $email): void {
$this->email = $email;
}
public function getSenha(): ?string {
return $this->senha;
}
public function setSenha(?string $senha): void {
$this->senha = $senha;
}
public function getTelefone(): ?string {
return $this->telefone;
}
public function setTelefone(?string $telefone): void {
$this->telefone = $telefone;
}
public function getDataNascimento(): ?string {
return $this->data_nascimento;
}
public function setDataNascimento(?string $data_nascimento): void {
$this->data_nascimento = $data_nascimento;
}
public function getEnderecoRua(): ?string {
return $this->endereco_rua;
}
public function setEnderecoRua(?string $endereco_rua): void {
$this->endereco_rua = $endereco_rua;
}
public function getEnderecoNumero(): ?string {
return $this->endereco_numero;
}
public function setEnderecoNumero(?string $endereco_numero): void {
$this->endereco_numero = $endereco_numero;
}
public function getEnderecoComplemento(): ?string {
return $this->endereco_complemento;
}
public function setEnderecoComplemento(?string $endereco_complemento): void {
$this->endereco_complemento = $endereco_complemento;
}
public function getEnderecoBairro(): ?string {
return $this->endereco_bairro;
}
public function setEnderecoBairro(?string $endereco_bairro): void {
$this->endereco_bairro = $endereco_bairro;
}
public function getEnderecoCidade(): ?string {
return $this->endereco_cidade;
}
public function setEnderecoCidade(?string $endereco_cidade): void {
$this->endereco_cidade = $endereco_cidade;
}
public function getEnderecoUf(): ?string {
return $this->endereco_uf;
}
public function setEnderecoUf(?string $endereco_uf): void {
$this->endereco_uf = $endereco_uf;
}
public function getEnderecoCep(): ?string {
return $this->endereco_cep;
}
public function setEnderecoCep(?string $endereco_cep): void {
$this->endereco_cep = $endereco_cep;
}
public function getCreatedAt(): ?string {
return $this->created_at;
}
public function setCreatedAt(?string $created_at): void {
$this->created_at = $created_at;
}
public function getUpdatedAt(): ?string {
return $this->updated_at;
}
public function setUpdatedAt(?string $updated_at): void {
$this->updated_at = $updated_at;
}
public function getDeletedAt(): ?string {
return $this->deleted_at;
}
public function setDeletedAt(?string $deleted_at): void {
$this->deleted_at = $deleted_at;
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Zampet\Module\Auth\v0\Middlewares;
use Exception;
use Zampet\Services\JWTService;
use Zampet\Module\Auth\v0\Services\AuthService;
class AuthMiddleware {
/**
* Gerencia o processo de autenticação de uma requisição de API via token Bearer.
*
* Este método estático atua como um **middleware de autenticação**, verificando a presença e a validade de um token JWT em uma requisição HTTP.
*
* #### Fluxo de Operação:
* 1. **Obtenção do Token:** Tenta extrair o token Bearer do cabeçalho da requisição usando `JWTService::getBearerToken()`.
* 2. **Verificação de Presença:** Se o token não for encontrado, uma `Exception` é lançada com o código `401 Unauthorized`.
* 3. **Validação do Token:** Chama `AuthService::validateToken()` para verificar a validade do token (assinatura, expiração e status no banco de dados).
* 4. **Conclusão:**
* * Se a validação for bem-sucedida, o método retorna `true`, permitindo que o processamento da rota continue.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` lançada durante o processo (token ausente, falha de validação JWT, token expirado) é capturada.
* - O método retorna um array de erro formatado, contendo o código HTTP e a mensagem de erro da exceção, o que é útil para a camada de controle enviar uma resposta JSON padronizada ao cliente.
*
* @return array|bool Retorna `true` se o token for válido; caso contrário, retorna um array associativo com o status da operação, o código de resposta HTTP e uma mensagem de erro.
*/
public static function handle(): array|bool {
// Recebe o token JWT do cabeçalho Authorization
$bearerToken = JWTService::getBearerToken();
try {
// Verifica se o token está presente
if (!$bearerToken) {
throw new Exception(message: "Token not provided", code: 401);
}
// Verifica se o token é válido
$validateTokenService = (new AuthService())->validateToken(token: $bearerToken);
if ($validateTokenService['status'] !== 'success') {
throw new Exception(message: $validateTokenService['message'], code: 401);
}
return true;
} catch(Exception $e) {
return [
'response_code' => $e->getCode(),
'status' => 'error',
'message' => $e->getMessage()
];
}
}
}

View File

@@ -0,0 +1,396 @@
<?php
namespace Zampet\Module\Auth\v0\Models;
use DateTime;
use Exception;
use Zampet\Services\DB;
use Zampet\Module\Auth\v0\DTO\UsuarioDTO;
class UsuarioModel {
protected string $usuarioTable = 'usuario';
protected string $usuarioTokenTable = 'usuario_token';
/**
* Cria e armazena um novo usuário no banco de dados.
*
* Este método é responsável por inserir os dados completos de um novo usuário, incluindo informações pessoais, documentos, credenciais e endereço, na tabela `{$this->usuarioTable}`. A operação é atômica e utiliza uma transação de banco de dados para garantir a consistência dos dados.
*
* #### Fluxo de Operação:
* 1. **Início da Transação:** Inicia uma transação no banco de dados (`DB::beginTransaction()`).
* 2. **Execução da Query:** Prepara e executa a instrução `INSERT` com todos os campos preenchidos a partir do `UsuarioDTO`.
* 3. **Confirmação:** Se a inserção for bem-sucedida, a transação é confirmada (`DB::commit()`).
* 4. **Retorno de Sucesso:** Retorna um array com `status` 'success', o código `201 Created`, uma mensagem de êxito e os dados do usuário criado (ID e UUID).
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo faz com que a transação seja revertida (`DB::rollBack()`).
* - Retorna um array com `status` 'error', código `500 Internal Server Error` e uma mensagem genérica de erro.
*
* @param UsuarioDTO $usuarioDTO O objeto DTO contendo os dados do usuário a ser criado.
* @return array Um array associativo com o status da operação, um código de resposta HTTP e uma mensagem.
*/
public function store(UsuarioDTO $usuarioDTO): array {
try {
$sql =
"INSERT INTO {$this->usuarioTable} (
uuid,
nome_completo,
status_id,
documentcpf,
documentcrmv,
email,
senha,
telefone,
data_nascimento,
endereco_rua,
endereco_numero,
endereco_complemento,
endereco_bairro,
endereco_cidade,
endereco_uf,
endereco_cep,
created_at,
updated_at,
deleted_at
) VALUES (
:uuid,
:nome_completo,
:status_id,
:documentcpf,
:documentcrmv,
:email,
:senha,
:telefone,
:data_nascimento,
:endereco_rua,
:endereco_numero,
:endereco_complemento,
:endereco_bairro,
:endereco_cidade,
:endereco_uf,
:endereco_cep,
:created_at,
:updated_at,
:deleted_at
)";
$params = [
":uuid" => $usuarioDTO->getUuid(),
":nome_completo" => $usuarioDTO->getNomeCompleto(),
":status_id" => $usuarioDTO->getStatusId(),
":documentcpf" => $usuarioDTO->getDocumentCpf(),
":documentcrmv" => $usuarioDTO->getDocumentCrmv(),
":email" => $usuarioDTO->getEmail(),
":senha" => $usuarioDTO->getSenha(),
":telefone" => $usuarioDTO->getTelefone(),
":data_nascimento" => $usuarioDTO->getDataNascimento(),
":endereco_rua" => $usuarioDTO->getEnderecoRua(),
":endereco_numero" => $usuarioDTO->getEnderecoNumero(),
":endereco_complemento" => $usuarioDTO->getEnderecoComplemento(),
":endereco_bairro" => $usuarioDTO->getEnderecoBairro(),
":endereco_cidade" => $usuarioDTO->getEnderecoCidade(),
":endereco_uf" => $usuarioDTO->getEnderecoUf(),
":endereco_cep" => $usuarioDTO->getEnderecoCep(),
":created_at" => $usuarioDTO->getCreatedAt(),
":updated_at" => $usuarioDTO->getUpdatedAt(),
":deleted_at" => $usuarioDTO->getDeletedAt()
];
// Executa a query de inserção
DB::execute(sql: $sql, params: $params);
return [
'response_code' => 201,
'status' => 'success',
'message' => 'Usuário registrado com sucesso.',
'output' => [
'data' => [
'user_id' => DB::lastInsertId(),
'uuid' => $usuarioDTO->getUuid()
]
]
];
} catch (Exception $e) {
return [
'response_code' => 500,
'status' => 'error',
'message' => 'Ocorreu um erro ao registrar o usuário. Tente novamente mais tarde.',
'output' => ['errors' => $e->getMessage()]
];
}
}
/**
* Busca um único usuário ativo no banco de dados por um identificador específico.
*
* Este método recupera todos os detalhes de um usuário na tabela `{$this->usuarioTable}`. A busca é flexível e permite usar qualquer coluna como identificador (`$identifier`), mas é estritamente restrita a registros que não foram logicamente excluídos (`deleted_at IS NULL`).
*
* @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'uuid', 'email', 'documentcpf').
* @param mixed $value O valor correspondente ao identificador que está sendo buscado.
* @return array Um array associativo contendo o status da operação, um código de resposta HTTP e os dados do usuário, se encontrado.
*/
public function getUsuarioByIdentifier(string $identifier, mixed $value): array {
try {
$sql =
"SELECT
id,
uuid,
nome_completo,
status_id,
documentcpf,
documentcrmv,
email,
senha,
telefone,
data_nascimento,
endereco_rua,
endereco_numero,
endereco_complemento,
endereco_bairro,
endereco_cidade,
endereco_uf,
endereco_cep,
created_at,
updated_at,
deleted_at
FROM {$this->usuarioTable}
WHERE {$identifier} = :value
AND deleted_at IS NULL";
$params = [
':value' => $value
];
$result = DB::fetchOne(sql: $sql, params: $params);
if (!$result) {
return [
'response_code' => 404,
'status' => 'fail',
'message' => 'Usuário não encontrado.',
'output' => [
'data' => []
]
];
}
return [
'response_code' => 200,
'status' => 'success',
'output' => ['data' => $result]
];
} catch(Exception $e) {
return [
'response_code' => 500,
'status' => 'error',
'message' => 'Ocorreu um erro ao buscar usuário. Tente novamente mais tarde.',
];
}
}
/**
* Armazena um novo token de autenticação no banco de dados para um usuário específico.
*
* Este método é responsável por registrar um token na tabela `{$this->usuarioTokenTable}`. Ele associa o token a um ID de usuário, define o status inicial como não revogado (`revoked = 0`) e registra o timestamp de criação.
*
* #### Fluxo de Operação:
* 1. **Início da Transação:** O método inicia uma transação no banco de dados (`DB::beginTransaction()`).
* 2. **Execução da Query:** Prepara e executa a instrução `INSERT` com o ID do usuário, a string do token, o status inicial de revogação e o timestamp atual.
* 3. **Confirmação:** Se a inserção for bem-sucedida, a transação é confirmada (`DB::commit()`).
* 4. **Retorno de Sucesso:** Retorna um array com `status` 'success', o código `201 Created` e uma mensagem de êxito.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo faz com que a transação seja revertida (`DB::rollback()`).
* - Retorna um array com `status` 'error', o código `500 Internal Server Error` e uma mensagem genérica de erro.
*
* @param int $user_id O ID do usuário ao qual o token pertence.
* @param string $token A string do token de autenticação (JWT ou similar).
* @return array Um array associativo contendo o status da operação ("success" ou "error"), um código de resposta HTTP e uma mensagem.
*/
public function storeToken(int $user_id, string $token): array {
try {
$sql =
"INSERT INTO {$this->usuarioTokenTable} (
usuario_id,
token,
revoked,
created_at
) VALUES (
:usuario_id,
:token,
:revoked,
:created_at
)";
$params = [
":usuario_id" => $user_id,
":token" => $token,
":revoked" => 0,
":created_at" => (new DateTime())->format(format: 'Y-m-d H:i:s')
];
DB::execute(sql: $sql, params: $params);
return [
'response_code' => 201,
'status' => 'success',
'message' => 'Token registrado com sucesso.'
];
} catch (Exception $e) {
return [
'response_code' => 500,
'status' => 'error',
'message' => 'Erro ao registrar token. Tente novamente mais tarde.'
];
}
}
/**
* Revoga todos os tokens de autenticação ativos de um usuário específico.
*
* Este método atualiza a coluna `revoked` para `1` (revogado) para todos os tokens não revogados (`revoked = 0`) associados a um `user_id` específico na tabela `{$this->usuarioTokenTable}`. Isso invalida todas as sessões ativas do usuário, forçando-o a fazer login novamente.
*
* #### Fluxo de Operação:
* 1. **Execução da Query:** Prepara e executa a instrução `UPDATE` para revogar os tokens.
* 2. **Confirmação:** Se a atualização for bem-sucedida, retorna um array com `status` 'success' e código `200 OK`.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo reverte qualquer transação de banco de dados e retorna um array com `status` 'error' e código `500 Internal Server Error`, com uma mensagem de erro genérica.
*
* @param int $user_id O ID do usuário para o qual os tokens serão revogados.
* @return array Um array associativo contendo o status da operação ("success" ou "error"), um código de resposta HTTP e uma mensagem descritiva.
*/
public function revokeOldTokens(int $user_id): array {
try {
$sql =
"UPDATE {$this->usuarioTokenTable} SET
revoked = 1,
updated_at = CURRENT_TIMESTAMP
WHERE usuario_id = :usuario_id
AND revoked = 0";
$params = [
":usuario_id" => $user_id
];
DB::execute(sql: $sql, params: $params);
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Old tokens revoked successfully.'
];
} catch (Exception $e) {
return [
'response_code' => 500,
'status' => 'error',
'message' => 'Error revoking old tokens. Please try again later.'
];
}
}
/**
* Busca um token de usuário no banco de dados por um identificador específico.
*
* Este método recupera os dados de um único token de autenticação da tabela `{$this->usuarioTokenTable}` de forma flexível, permitindo a busca por qualquer coluna (`$identifier`) e o valor correspondente.
*
* #### Fluxo de Operação:
* 1. **Execução da Query**: Monta e executa uma consulta `SELECT` que usa o `$identifier` de forma dinâmica na cláusula `WHERE` e um parâmetro nomeado (`:value`) para o valor.
* 2. **Verificação de Resultados**:
* - Se o token for encontrado, retorna um array com **`status` 'success'**, código **200 OK** e os dados do token.
* - Se o token **não for encontrado**, retorna um array com **`status` 'fail'** e código **404 Not Found**.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante a consulta retorna um array com **`status` 'error'** e código **500 Internal Server Error**, com uma mensagem genérica de erro.
*
* @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'id', 'token').
* @param mixed $value O valor correspondente ao identificador que está sendo buscado.
* @return array Um array associativo contendo o status da operação, um código de resposta HTTP e os dados do token, se encontrado.
*/
public function getTokenByidentifier(string $identifier, mixed $value): array {
try {
$sql =
"SELECT
id,
usuario_id,
token,
revoked,
created_at,
updated_at
FROM {$this->usuarioTokenTable}
WHERE {$identifier} = :value";
$params = [
':value' => $value
];
$result = DB::fetchOne(sql: $sql, params: $params);
if (!$result) {
return [
'response_code' => 404,
'status' => 'fail',
'message' => 'Token não encontrado.',
'output' => [
'data' => []
]
];
}
return [
'response_code' => 200,
'status' => 'success',
'output' => ['data' => $result]
];
} catch(Exception $e) {
return [
'response_code' => 500,
'status' => 'error',
'message' => 'Ocorreu um erro ao buscar token. Tente novamente mais tarde.',
];
}
}
/**
* Revoga um token de autenticação de usuário no banco de dados.
*
* Este método marca um token específico como revogado, atualizando a coluna `revoked` para `1`. Ele garante que apenas tokens que ainda não foram revogados (`revoked = 0`) sejam atualizados.
*
* #### Fluxo de Operação:
* 1. **Execução da Query**: Prepara e executa a instrução `UPDATE` para definir `revoked = 1` para o token correspondente.
* 2. **Confirmação**: Se a atualização for bem-sucedida, retorna um array com **`status` 'success'** e código **200 OK**.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo retorna um array com **`status` 'error'** e código **500 Internal Server Error**, com uma mensagem de erro genérica.
*
* @param string $token O token de autenticação a ser revogado.
* @return array Um array associativo contendo o status da operação ("success" ou "error"), um código de resposta HTTP e uma mensagem.
*/
public function revokeToken(string $token): array {
try {
$sql =
"UPDATE {$this->usuarioTokenTable} SET
revoked = 1,
updated_at = CURRENT_TIMESTAMP
WHERE token = :token
AND revoked = 0";
$params = [
":token" => $token
];
DB::execute(sql: $sql, params: $params);
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Token revoked successfully.'
];
} catch (Exception $e) {
return [
'response_code' => 500,
'status' => 'error',
'message' => 'Error revoking token. Please try again later.'
];
}
}
}

View File

@@ -0,0 +1,30 @@
<?php
use AxiumPHP\Core\Router;
use Zampet\Module\Auth\v0\Controllers\AuthController;
use Zampet\Module\Auth\v0\Middlewares\AuthMiddleware;
Router::group(
prefix: '/auth',
callback: function () {
// Criar novo usuário
Router::POST(
uri: '/register',
handler: [AuthController::class, 'register']
);
// Login no Sistema
Router::POST(
uri: '/login',
handler: [AuthController::class, 'login']
);
// Logout do Sistema
Router::POST(
uri: '/logout',
handler: [AuthController::class, 'logout'],
middlewares: [
AuthMiddleware::class.'::handle'
]
);
}
);

View File

@@ -0,0 +1,12 @@
<?php
use AxiumPHP\Core\Router;
Router::group(
prefix: '/v0',
callback: function() {
require 'AuthRoutes.php';
},
middlewares: [
]
);

View File

@@ -0,0 +1,346 @@
<?php
namespace Zampet\Module\Auth\v0\Services;
use DateTime;
use Exception;
use Ramsey\Uuid\Uuid;
use Zampet\Services\DB;
use Zampet\Services\JWTService;
use Zampet\Helpers\DataValidator;
use Zampet\Module\Auth\v0\DTO\UsuarioDTO;
use Zampet\Module\Auth\v0\Models\UsuarioModel;
class AuthService {
/**
* Registra um novo usuário no sistema após realizar validações rigorosas.
*
* Este método orquestra o processo de criação de um novo usuário, incluindo validação de dados, checagem de duplicidade de e-mail e persistência no banco de dados. A operação é atômica e utiliza uma transação de banco de dados (`DB::beginTransaction()`).
*
* #### Fluxo de Operação:
* 1. **Início da Transação:** Inicia uma transação no banco de dados.
* 2. **Validação de Dados:** Um `DataValidator` é usado para validar campos obrigatórios (`nome_completo`, `email`, `senha`) e a complexidade da senha (mínimo de 8 caracteres, maiúsculas, números e símbolos).
* 3. **Checagem de Duplicidade:** O método `getUsuarioByIdentifier` é chamado para verificar se o e-mail já está em uso por outro usuário.
* 4. **Encerramento em Caso de Falha na Validação/Duplicidade:** Se houver erros de validação (código **400**) ou se o e-mail já estiver em uso (código **409**), a transação é revertida (`DB::rollBack()`) e o erro é retornado.
* 5. **Preparação Final dos Dados:** Se a validação passar, o **UUID** do usuário e o timestamp de criação são definidos. A senha é criptografada usando **`PASSWORD_ARGON2ID`** antes de ser persistida.
* 6. **Persistência:** O método `store` do `UsuarioModel` é chamado para inserir o novo registro.
* 7. **Confirmação:** Se a persistência for bem-sucedida (código **201**), a transação é confirmada (`DB::commit()`) e o resultado de sucesso é retornado.
* 8. **Tratamento de Erro de Persistência:** Se o `store` falhar por um motivo inesperado (código **500**), a transação é revertida.
*
* @param UsuarioDTO $usuarioDTO O objeto DTO contendo os dados do novo usuário, incluindo nome, email e senha.
* @return array Um array associativo contendo o código HTTP, status da operação, mensagem e dados de saída.
*/
public function register(UsuarioDTO $usuarioDTO): array {
// Inicia conexão com o banco de dados
DB::beginTransaction();
// Valida os dados do DTO
$dataValidador = new DataValidator(inputs: $usuarioDTO->toArray());
$dataValidador->validate(field: 'nome_completo', rule: 'notEmpty', message: 'Nome completo é obrigatório');
$dataValidador->validate(field: 'email', rule: 'notEmpty', message: 'Email é obrigatório');
$dataValidador->validate(field: 'email', rule: 'validateEmail', message: 'Email inválido');
$dataValidador->validate(field: 'senha', rule: 'notEmpty', message: 'Senha é obrigatória');
$dataValidador->validate(field: 'documentcpf', rule: 'notEmpty', message: 'CPF é obrigatório');
$dataValidador->validate(field: 'documentcpf', rule: 'validateCpf', message: 'CPF informado é inválido.');
$dataValidador->validate(field: 'senha', rule: 'validatePassword', message: 'Senha inválida', additionalParams: [
[
'minLength' => 8,
'upper' => true,
'digit' => true,
'symbol' => true
]
]
);
// Se houver erros de validação, retornar resposta de erro
if(!$dataValidador->passes()) {
DB::rollBack();
return [
'response_code' => 400,
'status' => 'error',
'message' => 'Validation errors',
'output' => [
'errors' => $dataValidador->getErrors()
]
];
}
// Verifica se o email já está em uso
$existingEmailUser = (new UsuarioModel())->getUsuarioByIdentifier(identifier: 'email', value: $usuarioDTO->getEmail());
if($existingEmailUser['status'] === 'success' && !empty($existingEmailUser['output']['data'])) {
DB::rollBack();
return [
'response_code' => 409,
'status' => 'error',
'message' => 'E-mail já está em uso por outro usuário.',
'output' => []
];
}
// Verifica se o CPF já está em uso
$existingCpfUser = (new UsuarioModel())->getUsuarioByIdentifier(identifier: 'documentcpf', value: $usuarioDTO->getDocumentCpf());
if($existingCpfUser['status'] === 'success' && !empty($existingCpfUser['output']['data'])) {
DB::rollBack();
return [
'response_code' => 409,
'status' => 'error',
'message' => 'CPF já está em uso por outro usuário.',
'output' => []
];
}
// Define dados adicionais
$usuarioDTO->setUuid(uuid: Uuid::uuid7()->toString());
$usuarioDTO->setCreatedAt(created_at: (new DateTime())->format(format: 'Y-m-d H:i:s'));
$usuarioDTO->setSenha(senha: password_hash(password: $usuarioDTO->getSenha(), algo: PASSWORD_ARGON2ID));
// Persiste o novo usuário no banco de dados
$storeUsuario = (new UsuarioModel())->store(usuarioDTO: $usuarioDTO);
if($storeUsuario['status'] !== 'success') {
DB::rollBack();
return [
'response_code' => 500,
'status' => 'error',
'message' => 'Erro ao registrar usuário. Tente novamente mais tarde.',
'output' => $storeUsuario['output'] ?? []
];
}
DB::commit();
return [
'response_code' => $storeUsuario['response_code'],
'status' => $storeUsuario['status'],
'message' => $storeUsuario['message'],
'output' => $storeUsuario['output']
];
}
/**
* Realiza a autenticação de um usuário por e-mail e senha, gerando e registrando um token JWT.
*
* Este método gerencia todo o fluxo de login, incluindo a verificação de credenciais, revogação de tokens antigos, geração de um novo JWT e persistência desse novo token no banco de dados. A operação é atômica e utiliza uma transação de banco de dados para garantir a consistência dos dados.
*
* #### Fluxo de Operação:
* 1. **Início da Transação:** Inicia uma transação no banco de dados (`DB::beginTransaction()`).
* 2. **Consulta do Usuário:** Busca o usuário pelo e-mail. Se o usuário não for encontrado, uma `Exception` é lançada com o código `401 Unauthorized`.
* 3. **Validação da Senha:** Verifica a senha fornecida contra o hash armazenado. Se a senha for inválida, uma `Exception` é lançada com o código `401 Unauthorized`.
* 4. **Revogação de Tokens Antigos:** Chama o método `revokeOldTokens` do `UsuarioModel` para invalidar todas as sessões anteriores do usuário.
* 5. **Geração do JWT:** Monta o payload do JWT com os dados essenciais do usuário e os timestamps `iat` (emitido em) e `exp` (expira em), gerando o novo token.
* 6. **Registro do Token:** Armazena o novo token no banco de dados usando `storeToken`. Se a operação falhar, uma `Exception` é lançada com o código `500 Internal Server Error`.
* 7. **Confirmação e Retorno:** Se todas as etapas forem bem-sucedidas, a transação é confirmada (`DB::commit()`), e o token gerado é retornado com `status` 'success' e código `200 OK`.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo faz com que a transação seja revertida (`DB::rollBack()`).
* - Retorna um array com `status` 'error', o código de erro da exceção (ou 500 como fallback) e uma mensagem descritiva.
*
* @param string $email O e-mail do usuário para autenticação.
* @param string $password A senha em texto plano fornecida pelo usuário.
* @return array Um array associativo contendo o status da operação, o código de resposta HTTP e o token JWT em caso de sucesso.
*/
public function login(string $email, string $password): array {
try {
// Inicia Transação no Banco de dados
DB::beginTransaction();
// Consulta as informações do usuário pelo email
$userResult = (new UsuarioModel())->getUsuarioByIdentifier(identifier: 'email', value: $email);
if($userResult['status'] !== 'success' || empty($userResult['output']['data'])) {
throw new Exception(message: 'E-mail ou Senha incorretos.', code: 401);
}
// Valida a senha fornecida com a armazenada
if(!password_verify(password: $password, hash: $userResult['output']['data']['senha'])) {
throw new Exception(message: 'E-mail ou Senha incorretos.', code: 401);
}
// Revoga os tokens antigos do usuário
$revokeOldTokens = (new UsuarioModel())->revokeOldTokens(user_id: $userResult['output']['data']['id']);
if($revokeOldTokens['status'] !== 'success') {
throw new Exception(message: 'Erro ao revogar tokens antigos.', code: 500);
}
// Prepara o Payload do JWT
$jwtPayload = [
'user_uuid' => $userResult['output']['data']['uuid'],
'email' => $userResult['output']['data']['email'],
'nome_completo' => $userResult['output']['data']['nome_completo'],
'iat' => (new DateTime())->getTimestamp(),
'exp' => (new DateTime())->modify(modifier: '+8 hour')->getTimestamp()
];
$token = JWTService::generateToken(payload: $jwtPayload);
// Armazena o token no banco de dados
$storeToken = (new UsuarioModel())->storeToken(user_id: $userResult['output']['data']['id'], token: $token);
if($storeToken['status'] !== 'success') {
throw new Exception(message: 'Erro ao armazenar token de autenticação.', code: 500);
}
DB::commit();
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Login successful.',
'output' => [
'data' => [
'token' => $token
]
]
];
} catch(Exception $e) {
DB::rollBack();
return [
'response_code' => $e->getCode() ?: 500,
'status' => 'error',
'message' => $e->getMessage() ?: 'An unexpected error occurred during login.'
];
}
}
/**
* Finaliza a sessão do usuário revogando um token JWT específico.
*
* Este método é o responsável por orquestrar o processo de logout seguro, garantindo que o token de autenticação seja validado e, em seguida, invalidado no banco de dados.
*
* #### Fluxo de Operação:
* 1. **Validação do Token:** O método chama `validateToken` para verificar se o token existe e ainda é válido (não expirado ou revogado no DB). Se a validação falhar, uma `Exception` é lançada com o código de erro apropriado.
* 2. **Revogação no Banco de Dados:** Tenta revogar o token usando `UsuarioModel::revokeToken()`. Se a revogação falhar (ex: erro de banco de dados), uma `Exception` é lançada.
* 3. **Retorno de Sucesso:** Se ambas as operações (validação e revogação) forem bem-sucedidas, retorna um array com **`status` 'success'**, código **200 OK** e uma mensagem de êxito.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` lançada durante o processo é capturada, e o método retorna um array de erro formatado, contendo o código HTTP e a mensagem da exceção.
*
* @param string $token O token JWT a ser revogado.
* @return array Um array associativo contendo o status da operação ("success" ou "error"), o código de resposta HTTP e uma mensagem.
*/
public function logout(string $token): array {
try {
// Validar se o token existe e não está revogado
$validateToken = $this->validateToken(token: $token);
if($validateToken['status'] !== 'success') {
throw new Exception(message: $validateToken['message'], code: $validateToken['response_code']);
}
// Revoga o token fornecido
$revokeToken = (new UsuarioModel())->revokeToken(token: $token);
if($revokeToken['status'] !== 'success') {
throw new Exception(message: 'Erro ao revogar token.', code: $revokeToken['response_code']);
}
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Logout successful.',
'output' => []
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?: 500,
'status' => 'error',
'message' => $e->getMessage() ?: 'An unexpected error occurred during logout.'
];
}
}
/**
* Valida um token de autenticação verificando sua presença no banco de dados e seu status de expiração.
*
* Este método realiza uma validação de duas etapas: 1) Checa se o token existe e não está revogado no banco de dados, e 2) verifica se o timestamp de expiração (exp) no payload do token já passou. Se o token estiver expirado, ele é revogado no banco de dados.
*
* #### Fluxo de Operação:
* 1. **Início da Transação:** Inicia uma transação no banco de dados (`DB::beginTransaction()`) para garantir que a revogação, se necessária, seja atômica.
* 2. **Busca no DB:** Tenta encontrar o token usando o `UsuarioModel`. Se a busca falhar (token não encontrado ou revogado), uma `Exception` é lançada com o código `401 Unauthorized`.
* 3. **Decodificação:** O token é decodificado usando `JWTService::decode()` para acessar o payload e o timestamp de expiração (`exp`).
* 4. **Verificação de Expiração:** Compara o timestamp atual com o `exp` do payload.
* * **Se Expirado:** O método tenta revogar o token no DB. Se a revogação for bem-sucedida, a transação é confirmada e o método retorna um erro com código `401`. Se a revogação falhar, a transação é revertida e um erro `500` é retornado.
* * **Se Válido:** A transação é confirmada e o método retorna `status` 'success' com o código `200 OK` e os dados do payload.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` lançada durante o processo (falha de busca, falha de decodificação JWT, falha de revogação) é capturada. Se a falha ocorrer após o início da transação, um `DB::rollBack()` é implícito ou explicitamente chamado antes do retorno.
* - Retorna um array com `status` 'error', o código de erro (ou `500` como fallback) e uma mensagem descritiva.
*
* @param string $token O token JWT a ser validado.
* @return array Um array associativo contendo o status da operação, um código de resposta HTTP e os dados do token, se válido.
*/
public function validateToken(string $token): array {
// Inicia transação no banco de dados
DB::beginTransaction();
try {
$dbTokenData = (new UsuarioModel())->getTokenByidentifier(identifier: 'token', value: $token);
if($dbTokenData['status'] !== 'success' || empty($dbTokenData['output']['data'])) {
throw new Exception(message: 'Token inválido ou expirado.', code: 401);
}
// Decodifica o token para verificar sua validade
$decodedToken = JWTService::decode(token: $dbTokenData['output']['data']['token']);
// Verifica se o token expirou
$currentTimestamp = (new DateTime())->getTimestamp();
switch (true) {
case $currentTimestamp > $decodedToken['exp']:
// Revoga o token expirado
$revokeToken = (new UsuarioModel())->revokeToken(token: $dbTokenData['output']['data']['token']);
if($revokeToken['status'] !== 'success') {
DB::rollBack();
throw new Exception(message: 'Erro ao revogar token expirado.', code: 500);
}
DB::commit();
return [
'response_code' => 401,
'status' => 'error',
'message' => 'Token expirado.',
'output' => []
];
case $dbTokenData['output']['data']['revoked'] === 1:
DB::commit();
return [
'response_code' => 401,
'status' => 'error',
'message' => 'Token inválido ou expirado.',
'output' => []
];
}
DB::commit();
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Token is valid.',
'output' => ['token_data' => $decodedToken]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?: 500,
'status' => 'error',
'message' => $e->getMessage() ?: 'An unexpected error occurred during token validation.'
];
}
}
public function getUserData(string $token): array {
try {
// Validar se o token existe e não está revogado
$validateToken = $this->validateToken(token: $token);
if($validateToken['status'] !== 'success') {
throw new Exception(message: $validateToken['message'], code: $validateToken['response_code']);
}
return [
'response_code' => 200,
'status' => 'success',
'message' => 'User data retrieved successfully.',
'output' => [
'data' => $validateToken['output']['token_data']
]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?: 500,
'status' => 'error',
'message' => $e->getMessage() ?: 'An unexpected error occurred while retrieving user data.'
];
}
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace Zampet\Module\Auth;
use DateTime;
use Exception;
// Caminho do manifest.json
$manifestPath = realpath(path: __DIR__ . "/manifest.json");
if (!$manifestPath || !file_exists(filename: $manifestPath)) {
throw new Exception(message: "Arquivo 'manifest.json' não encontrado.");
}
// Lê e decodifica
$manifest = json_decode(json: file_get_contents(filename: $manifestPath), associative: true);
// Verifica estrutura
if (!isset($manifest['apis']) || !is_array(value: $manifest['apis'])) {
throw new Exception(message: "Estrutura de 'apis' inválida no manifest.");
}
$now = (new DateTime())->format(format: 'Y-m-d');
$newApis = [];
$updated = false;
// Checa se tem versão esperando que começa hoje
foreach ($manifest['apis'] as $api) {
if ($api['status'] === 'planned' && $api['start_date'] === $now) {
// Promote a nova
$api['status'] = 'active';
$newApis[] = $api;
$updated = true;
} elseif ($api['status'] === 'active' && $api['start_date'] !== $now) {
// Ignora versão antiga ativa
continue;
} else {
$newApis[] = $api;
}
}
// Atualiza manifest se tiver mudança
if ($updated) {
$manifest['apis'] = $newApis;
file_put_contents(filename: $manifestPath, data: json_encode(value: $manifest, flags: JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
// Carrega a versão ativa
$loaded = false;
foreach ($manifest['apis'] as $api) {
$startDate = new DateTime(datetime: $api['start_date']);
if ($api['status'] === 'active' && $startDate <= new DateTime()) {
$routeFile = MODULE_PATH . "/{$manifest['dirName']}/{$api['version']}/Routes/Routes.php";
if (file_exists(filename: $routeFile)) {
require_once realpath(path: $routeFile);
$loaded = true;
break;
} else {
throw new Exception(message: "Arquivo de rotas não encontrado para a versão '{$api['version']}'.");
}
}
}
// Verifica se carregou
if (!$loaded) {
throw new Exception(message: "Nenhuma versão ativa da API foi carregada.");
}

View File

@@ -0,0 +1,16 @@
{
"name": "Autenticação",
"dirName": "Auth",
"slug": "auth",
"description": "Gerencia login, logout, sessão e permissões de acesso.",
"uuid": "0199abc8-026a-75b0-9b25-0f9456a44c70",
"version": "1.0",
"dependencies": [],
"apis": [
{
"version": "v0",
"status": "active",
"start_date": "2025-08-01"
}
]
}

0
app/Module/Tutor/blank_file Executable file
View File

View File

@@ -0,0 +1,322 @@
<?php
namespace Zampet\Module\Tutor\v0\Controllers;
use Zampet\Helpers\Sanitizer;
use Zampet\Services\JWTService;
use AxiumPHP\Helpers\RequestHelper;
use Zampet\Module\Tutor\v0\DTO\PetDTO;
use Zampet\Module\Tutor\v0\DTO\PetRacaDTO;
use Zampet\Module\Tutor\v0\Services\PetService;
class PetController {
/**
* Lida com a requisição para listar todas as espécies de pets disponíveis.
*
* Este método atua como um **controlador** para um endpoint de API. Ele delega a busca da lista completa de espécies para o `PetService` e, em seguida, formata e envia a resposta ao cliente em JSON.
*
* #### Fluxo de Operação:
* 1. **Delegação ao Serviço:** Uma instância de `PetService` é criada, e o método `getAllEspecies` é chamado para obter a lista de espécies. O serviço é responsável pela lógica de busca no modelo.
* 2. **Envio da Resposta JSON:** O resultado retornado pelo serviço (`especiesPets`) é formatado para uma resposta JSON padronizada usando **`RequestHelper::sendJsonResponse`**. Os dados da resposta (código HTTP, status, mensagem e lista de espécies) são comunicados ao cliente.
*
* @return void Este método não retorna um valor, pois seu objetivo é enviar uma resposta JSON e encerrar a execução do script.
*/
public function getAllEspecies(): void {
// Lista as espécies de pets
$especiesPets = (new PetService())->getAllEspecies();
// Retorna a resposta em JSON
RequestHelper::sendJsonResponse(
response_code: $especiesPets['response_code'],
status: $especiesPets['status'],
message: $especiesPets['message'],
output: $especiesPets['output'] ?? []
);
exit;
}
/**
* Lista todos os pets associados ao usuário atualmente autenticado.
*
* Este método atua como um **controlador** para um endpoint de API, responsável por identificar o usuário autenticado, buscar a lista de seus pets e retornar os dados em formato JSON padronizado.
*
* #### Fluxo de Operação:
* 1. **Obtenção da Identidade do Usuário:** O método primeiro extrai e decodifica o token JWT presente no cabeçalho `Authorization` usando `JWTService::getBearerToken()` e `JWTService::decode()`. O UUID do usuário (tutor) é obtido diretamente do payload decodificado.
* 2. **Delegação ao Serviço:** O método `listPetsByTutorUuid` do **`PetService`** é chamado, passando o UUID do usuário para buscar todos os pets associados.
* 3. **Envio da Resposta JSON:** O resultado retornado pelo serviço (`petsTutor`) é formatado e enviado de volta ao cliente usando **`RequestHelper::sendJsonResponse`**.
* * O código HTTP, status e mensagem são extraídos do array de resposta do serviço, garantindo que a resposta reflita o sucesso ou falha da operação de busca.
*
* @return void Este método não retorna um valor, pois seu objetivo é enviar uma resposta JSON e encerrar a execução do script.
*/
public function listMyPets(): void {
// Recebe o token JWT do cabeçalho Authorization
$decodedToken = JWTService::decode(token: JWTService::getBearerToken());
// Inicia o serviço de Pet
$petsTutor = (new PetService())->listPetsByTutorUuid(uuid: $decodedToken['user_uuid']);
// Retorna a resposta em JSON
RequestHelper::sendJsonResponse(
response_code: $petsTutor['response_code'],
status: $petsTutor['status'],
message: $petsTutor['message'],
output: $petsTutor['output'] ?? []
);
exit;
}
/**
* Lida com a requisição de criação de uma nova raça de pet.
*
* Este método atua como um **controlador** de API, responsável por receber os dados da nova raça (descrição) e o UUID da espécie, validar as entradas e orquestrar a criação do novo registro na camada de serviço.
*
* #### Fluxo de Operação:
* 1. **Validação de Presença:** Verifica se os campos obrigatórios (`especie_uuid` e `descricao`) estão presentes. Se não estiverem, retorna uma resposta JSON com o código **400 Bad Request** e encerra a execução.
* 2. **Busca e Validação da Espécie:** Chama `PetService::getEspecieByIdentifier` para confirmar a existência da espécie pelo UUID e obter seu ID interno. Se a espécie não for encontrada, retorna um erro `400`.
* 3. **Preparação do DTO:** Cria e popula um **`PetRacaDTO`** com o ID interno da espécie e a descrição sanitizada.
* 4. **Delegação ao Serviço:** O método `storeRaca` do **`PetService`** é chamado para executar a lógica de persistência e validação de negócio.
* 5. **Envio da Resposta JSON:** O resultado do serviço (`serviceResult`) é formatado e enviado de volta ao cliente, comunicando o status (`201 Created` em caso de sucesso) e a mensagem da operação.
*
* @return void Este método não retorna um valor, pois seu objetivo é enviar uma resposta JSON e encerrar a execução do script.
*/
public function storeRaca(): void {
// Receber dados do formulário de registro
$form = RequestHelper::getFilteredInput(form_type: INPUT_POST);
// Campos Obrigatórios
$required_fields = [
'especie_uuid',
'descricao'
];
// Verificar se todos os campos obrigatórios estão presentes
foreach ($required_fields as $field) {
if (empty($form[$field])) {
RequestHelper::sendJsonResponse(
response_code: 400,
status: 'error',
message: "Field '{$field}' is required"
);
exit;
}
}
// Consulta informações da espécie com base no UUID fornecido
$getEspecie = (new PetService())->getEspecieByIdentifier(
identifier: 'uuid',
value: $form['especie_uuid']
);
if($getEspecie['status'] !== 'success') {
RequestHelper::sendJsonResponse(
response_code: 400,
status: 'error',
message: "Espécie não encontrada",
output: ['data' => $form]
);
exit;
}
$especieData = $getEspecie['output']['data'];
// Chama o serviço de criar raça
$serviceResult = (new PetService())->storeRaca(
petRacaDTO: new PetRacaDTO(data: [
'especie_id' => $especieData['id'],
'descricao' => Sanitizer::string(value: $form['descricao'] ?? '')
])
);
// Enviar a resposta JSON
RequestHelper::sendJsonResponse(
response_code: $serviceResult['response_code'],
status: $serviceResult['status'],
message: $serviceResult['message'],
output: $serviceResult['output'] ?? []
);
exit;
}
/**
* Lida com a requisição de criação de um novo pet (animal de estimação).
*
* Este método atua como um **controlador** de API, responsável por receber os dados do formulário, realizar validações de presença e delegar a lógica de negócio (criação do pet e associação ao tutor) para o `PetService`.
*
* #### Fluxo de Operação:
* 1. **Validação de Presença:** Verifica se os campos obrigatórios (`name`, `raca_uuid`, `birthDate`, `sexo`) estão presentes no formulário. Se algum estiver faltando, retorna um erro **400 Bad Request** e encerra a execução.
* 2. **Busca e Validação da Raça/Espécie:** Chama `PetService::getEspecieByIdentifier` (apesar do nome, a lógica sugere que este método deve retornar a raça e a espécie) para validar o `raca_uuid` fornecido e obter os IDs internos. Se a raça não for encontrada ou a busca falhar, retorna um erro **400**.
* 3. **Preparação do DTO:** Cria e popula um **`PetDTO`** com os dados sanitizados e os IDs internos da raça e espécie.
* 4. **Delegação ao Serviço:** O método `storePet` do **`PetService`** é chamado para executar a lógica de persistência e associação do pet ao tutor autenticado.
* 5. **Envio da Resposta JSON:** O resultado do serviço (`serviceResult`) é formatado e enviado de volta ao cliente, comunicando o status (código **201 Created** em caso de sucesso) e a mensagem da operação.
*
* @return void Este método não retorna um valor, pois seu objetivo é enviar uma resposta JSON e encerrar a execução do script.
*/
public function storePet(): void {
// Receber dados do formulário de registro
$form = RequestHelper::getFilteredInput(form_type: INPUT_POST);
// Campos Obrigatórios
$required_fields = [
'name',
'raca_uuid',
'birthDate',
'sexo',
];
// Verificar se todos os campos obrigatórios estão presentes
foreach ($required_fields as $field) {
if (empty($form[$field])) {
RequestHelper::sendJsonResponse(
response_code: 400,
status: 'error',
message: "Field '{$field}' is required"
);
exit;
}
}
// Consulta informações da raça com base no UUID fornecido
$getRaca = (new PetService())->getRacaByIdentifier(
identifier: 'uuid',
value: $form['raca_uuid']
);
if($getRaca['status'] !== 'success') {
RequestHelper::sendJsonResponse(
response_code: 400,
status: 'error',
message: "Raça não encontrada"
);
exit;
}
// Chama o serviço de criar pet
$serviceResult = (new PetService())->storePet(
petDTO: new PetDTO(data: [
"nome" => Sanitizer::string(value: $form['name'] ?? ''),
"data_nascimento" => Sanitizer::string(value: $form['birthDate'] ?? ''),
"sexo" => Sanitizer::string(value: $form['sexo'] ?? ''),
"raca_id" => $getRaca['output']['data']['id'],
"especie_id" => $getRaca['output']['data']['especie_id'],
"registro_geral_animal" => Sanitizer::string(value: $form['registro_geral_animal'] ?? NULL),
])
);
// Enviar a resposta JSON
RequestHelper::sendJsonResponse(
response_code: $serviceResult['response_code'],
status: $serviceResult['status'],
message: $serviceResult['message'],
output: $serviceResult['output'] ?? []
);
exit;
}
/**
* Lida com a requisição para obter detalhes de um pet específico por seu UUID.
*
* Este método atua como um **controlador** para um endpoint de API. Ele é responsável por delegar a busca dos dados do pet para o serviço e formatar a resposta para o cliente em JSON.
*
* #### Fluxo de Operação:
* 1. **Busca de Detalhes:** O método chama `PetService::getPetByIdentifier()`, usando o UUID fornecido como identificador. O serviço lida com a lógica de consulta no modelo.
* 2. **Envio da Resposta JSON:** O resultado retornado pelo serviço (`petDetails`) é formatado e enviado de volta ao cliente usando **`RequestHelper::sendJsonResponse`**. O código HTTP, status e mensagem são extraídos do array de resposta do serviço, comunicando o resultado da busca (sucesso ou falha).
*
* @param string $uuid O UUID do pet cujos detalhes devem ser buscados.
* @return void Este método não retorna um valor, pois seu objetivo é enviar uma resposta JSON e encerrar a execução do script.
*/
public function getPetDetails(string $uuid): void {
// Consulta detalhes do pet com base no UUID fornecido
$petDetails = (new PetService())->getPetByIdentifier(
identifier: 'uuid',
value: $uuid
);
if($petDetails['status'] !== 'success') {
RequestHelper::sendJsonResponse(
response_code: $petDetails['response_code'],
status: $petDetails['status'],
message: $petDetails['message'],
output: $petDetails['output'] ?? []
);
exit;
}
$pet = $petDetails['output']['data'];
$pet['vacinas'] = [
[
"id" => 1,
"nome" => "Antirrábica",
"fabricante" => "Zoetis",
"lote" => "ZOE-2025-001",
"validade" => "2026-02-15",
"data_aplicacao" => "2025-02-10",
"data_reforco" => "2026-02-10",
"veterinario" => "Dr. Carlos Mendes",
"created_at" => "2025-02-10 14:30:00",
"updated_at" => null,
"deleted_at" => null
],
[
"id" => 2,
"nome" => "V10",
"fabricante" => "Boehringer Ingelheim",
"lote" => "BIO-2025-044",
"validade" => "2026-06-20",
"data_aplicacao" => "2025-03-05",
"data_reforco" => "2026-03-05",
"veterinario" => "Dra. Fernanda Lopes",
"created_at" => "2025-03-05 09:10:00",
"updated_at" => null,
"deleted_at" => null
],
[
"id" => 3,
"nome" => "Giárdia",
"fabricante" => "Ceva",
"lote" => "CEV-2025-321",
"validade" => "2026-08-01",
"data_aplicacao" => "2025-04-15",
"data_reforco" => "2025-05-15",
"veterinario" => "Dr. Rafael Silva",
"created_at" => "2025-04-15 11:45:00",
"updated_at" => null,
"deleted_at" => null
],
[
"id" => 4,
"nome" => "Leptospirose",
"fabricante" => "MSD",
"lote" => "MSD-2025-112",
"validade" => "2026-09-30",
"data_aplicacao" => "2025-05-20",
"data_reforco" => "2026-05-20",
"veterinario" => "Dra. Ana Paula",
"created_at" => "2025-05-20 10:00:00",
"updated_at" => null,
"deleted_at" => null
],
[
"id" => 5,
"nome" => "Cinomose",
"fabricante" => "Vanguard",
"lote" => "VAN-2025-210",
"validade" => "2026-11-12",
"data_aplicacao" => "2025-06-10",
"data_reforco" => "2026-06-10",
"veterinario" => "Dr. Eduardo Gomes",
"created_at" => "2025-06-10 15:20:00",
"updated_at" => null,
"deleted_at" => null
]
];
// Retorna a resposta em JSON
RequestHelper::sendJsonResponse(
response_code: $petDetails['response_code'],
status: $petDetails['status'],
message: $petDetails['message'],
output: ['data' => $pet] ?? []
);
exit;
}
public function updatePet(string $uuid, array $params): void {
}
}

View File

@@ -0,0 +1,177 @@
<?php
namespace Zampet\Module\Tutor\v0\DTO;
class PetDTO {
private ?int $id = null;
private ?string $uuid = null;
private ?string $nome = null;
private ?int $especie_id = null;
private ?int $raca_id = null;
private ?string $caminho_foto = null;
private ?string $registro_geral_animal = null;
private ?string $photo_path = null;
private ?string $data_nascimento = null;
private ?string $data_obito = null;
private ?string $sexo = null;
private ?string $created_at = null;
private ?string $updated_at = null;
private ?string $deleted_at = null;
/**
* Construtor do DTO.
*
* Permite inicializar os atributos do objeto a partir de um array associativo.
*
* @param array<string, mixed> $data Array associativo opcional para popular os atributos.
*/
public function __construct(array $data = []) {
if (!empty($data)) {
$this->fromArray($data);
}
}
/**
* Popula o DTO a partir de um array.
*
* @param array<string, mixed> $data
* @return void
*/
public function fromArray(array $data): void {
$this->id = $data['id'] ?? $this->id;
$this->uuid = $data['uuid'] ?? $this->uuid;
$this->nome = $data['nome'] ?? $this->nome;
$this->especie_id = $data['especie_id'] ?? $this->especie_id;
$this->raca_id = $data['raca_id'] ?? $this->raca_id;
$this->caminho_foto = $data['caminho_foto'] ?? $this->caminho_foto;
$this->registro_geral_animal = $data['registro_geral_animal'] ?? $this->registro_geral_animal;
$this->photo_path = $data['photo_path'] ?? $this->photo_path;
$this->data_nascimento = $data['data_nascimento'] ?? $this->data_nascimento;
$this->data_obito = $data['data_obito'] ?? $this->data_obito;
$this->sexo = $data['sexo'] ?? $this->sexo;
$this->created_at = $data['created_at'] ?? $this->created_at;
$this->updated_at = $data['updated_at'] ?? $this->updated_at;
$this->deleted_at = $data['deleted_at'] ?? $this->deleted_at;
}
/**
* Converte o DTO para um array associativo.
*
* @return array<string, mixed>
*/
public function toArray(): array {
return [
'id' => $this->id,
'uuid' => $this->uuid,
'nome' => $this->nome,
'especie_id' => $this->especie_id,
'raca_id' => $this->raca_id,
'caminho_foto' => $this->caminho_foto,
'registro_geral_animal' => $this->registro_geral_animal,
'photo_path' => $this->photo_path,
'data_nascimento' => $this->data_nascimento,
'data_obito' => $this->data_obito,
'sexo' => $this->sexo,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'deleted_at' => $this->deleted_at,
];
}
public function getId(): ?int {
return $this->id;
}
public function setId(?int $id): void {
$this->id = $id;
}
public function getUuid(): ?string {
return $this->uuid;
}
public function setUuid(?string $uuid): void {
$this->uuid = $uuid;
}
public function getNome(): ?string {
return $this->nome;
}
public function setNome(?string $nome): void {
$this->nome = $nome;
}
public function getEspecieId(): ?int {
return $this->especie_id;
}
public function setEspecieId(?int $especie_id): void {
$this->especie_id = $especie_id;
}
public function getRacaId(): ?int {
return $this->raca_id;
}
public function setRacaId(?int $raca_id): void {
$this->raca_id = $raca_id;
}
public function getCaminhoFoto(): ?string {
return $this->caminho_foto;
}
public function setCaminhoFoto(?string $caminho_foto): void {
$this->caminho_foto = $caminho_foto;
}
public function getRegistroGeralAnimal(): ?string {
return $this->registro_geral_animal;
}
public function setRegistroGeralAnimal(?string $registro_geral_animal): void {
$this->registro_geral_animal = $registro_geral_animal;
}
public function getPhotoPath(): ?string {
return $this->photo_path;
}
public function setPhotoPath(?string $photo_path): void {
$this->photo_path = $photo_path;
}
public function getDataNascimento(): ?string {
return $this->data_nascimento;
}
public function setDataNascimento(?string $data_nascimento): void {
$this->data_nascimento = $data_nascimento;
}
public function getDataObito(): ?string {
return $this->data_obito;
}
public function setDataObito(?string $data_obito): void {
$this->data_obito = $data_obito;
}
public function getSexo(): ?string {
return $this->sexo;
}
public function setSexo(?string $sexo): void {
$this->sexo = $sexo;
}
public function getCreatedAt(): ?string {
return $this->created_at;
}
public function setCreatedAt(?string $created_at): void {
$this->created_at = $created_at;
}
public function getUpdatedAt(): ?string {
return $this->updated_at;
}
public function setUpdatedAt(?string $updated_at): void {
$this->updated_at = $updated_at;
}
public function getDeletedAt(): ?string {
return $this->deleted_at;
}
public function setDeletedAt(?string $deleted_at): void {
$this->deleted_at = $deleted_at;
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace Zampet\Module\Tutor\v0\DTO;
class PetRacaDTO {
public ?int $id = null;
public ?string $uuid = null;
public ?int $especie_id = null;
public ?string $descricao = null;
/**
* Construtor do DTO.
*
* Permite inicializar os atributos do objeto a partir de um array associativo.
*
* @param array<string, mixed> $data Array associativo opcional para popular os atributos.
*/
public function __construct(array $data = []) {
if (!empty($data)) {
$this->fromArray($data);
}
}
/**
* Popula o DTO a partir de um array.
*
* @param array<string, mixed> $data
* @return void
*/
public function fromArray(array $data): void {
$this->id = $data['id'] ?? $this->id;
$this->uuid = $data['uuid'] ?? $this->uuid;
$this->especie_id = $data['especie_id'] ?? $this->especie_id;
$this->descricao = $data['descricao'] ?? $this->descricao;
}
/**
* Converte o DTO para um array associativo.
*
* @return array<string, mixed>
*/
public function toArray(): array {
return [
'id' => $this->id,
'uuid' => $this->uuid,
'especie_id' => $this->especie_id,
'descricao' => $this->descricao,
];
}
public function getId(): ?int {
return $this->id;
}
public function setId(?int $id): void {
$this->id = $id;
}
public function getUuid(): ?string {
return $this->uuid;
}
public function setUuid(?string $uuid): void {
$this->uuid = $uuid;
}
public function getEspecieId(): ?int {
return $this->especie_id;
}
public function setEspecieId(?int $especie_id): void {
$this->especie_id = $especie_id;
}
public function getDescricao(): ?string {
return $this->descricao;
}
public function setDescricao(?string $descricao): void {
$this->descricao = $descricao;
}
}

View File

@@ -0,0 +1,651 @@
<?php
namespace Zampet\Module\Tutor\v0\Models;
use Exception;
use Zampet\Services\DB;
use Zampet\Module\Tutor\v0\DTO\PetDTO;
use Zampet\Module\Tutor\v0\DTO\PetRacaDTO;
use Zampet\Module\Auth\v0\Models\UsuarioModel;
class PetModel {
protected string $petTable = 'pet';
protected string $usuarioPet = 'usuario_pet';
protected string $petAuxRacaTable = 'pet_aux_raca';
protected string $petAuxEspecieTable = 'pet_aux_especie';
/**
* Lista todos os pets associados a um tutor, buscando o tutor por um identificador flexível.
*
* Este método busca primeiro o registro do tutor usando um identificador (UUID, ID, e-mail, etc.) e, em seguida, usa o ID interno do tutor para consultar todos os pets a ele associados.
*
* #### Fluxo de Operação:
* 1. **Busca do Tutor:** Utiliza `UsuarioModel::getUsuarioByIdentifier` para buscar os dados do tutor. Se o tutor não for encontrado, lança uma `Exception` com o código 404.
* 2. **Consulta dos Pets:** Executa uma consulta `SELECT` que une as tabelas de associação (`up`), pets (`p`), raças (`par`) e espécies (`pae`) para obter uma lista detalhada de todos os pets vinculados ao `usuario_id` do tutor.
* 3. **Verificação de Pets:**
* * Se a consulta não retornar pets (`empty($petsUsuario)`), retorna um array com `status` 'fail' e uma mensagem informativa.
* * Se pets forem encontrados, retorna um array com `status` 'success', código `200 OK` e os dados dos pets na chave `output`.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` lançada durante o processo (como tutor não encontrado ou erro de consulta) é capturada.
* - Retorna um array com `status` 'error', o código de resposta HTTP da exceção e uma mensagem descritiva.
*
* @param string $identifier A coluna da tabela de usuário a ser usada para identificar o tutor (ex: 'uuid', 'id').
* @param mixed $value O valor correspondente ao identificador do tutor.
* @return array Um array associativo contendo o status da operação, o código HTTP e os dados dos pets (`output['data']`), ou uma mensagem de erro.
*/
public function listPetsByTutorIdentifier(string $identifier, mixed $value): array {
try {
$usuarioData = (new UsuarioModel())->getUsuarioByIdentifier(identifier: $identifier, value: $value);
if(!$usuarioData) {
throw new Exception(message: 'Tutor not found', code: 404);
}
$usuarioData = $usuarioData['output']['data'];
$sql =
"SELECT
p.id,
p.uuid,
p.nome,
p.especie_id,
pae.descricao AS especie,
p.raca_id,
par.descricao AS raca,
p.caminho_foto,
p.registro_geral_animal,
p.photo_path,
p.data_nascimento,
p.data_obito,
p.sexo,
p.created_at,
p.updated_at,
p.deleted_at
FROM {$this->usuarioPet} up
LEFT JOIN {$this->petTable} p ON up.pet_id = p.id
LEFT JOIN {$this->petAuxRacaTable} par ON p.raca_id = par.id
LEFT JOIN {$this->petAuxEspecieTable} pae ON p.especie_id = pae.id
WHERE up.usuario_id = :usuario_id";
$params = [
':usuario_id' => $usuarioData['id'],
];
$petsUsuario = DB::fetchAll(sql: $sql, params: $params);
if(empty($petsUsuario)) {
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Você não possui pets cadastrados.',
'output' => [
'data' => []
]
];
}
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Pets encontrados com sucesso.',
'output' => [
'data' => $petsUsuario
]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?? 500,
'status' => 'error',
'message' => $e->getMessage(),
];
}
}
/**
* Lista todas as espécies de pets disponíveis no sistema auxiliar.
*
* Este método consulta a tabela de espécies (`{$this->petAuxEspecieTable}`) e retorna uma lista de todos os registros (ID e descrição).
*
* #### Fluxo de Operação:
* 1. **Execução da Consulta:** Executa uma consulta `SELECT` para buscar todos os IDs e descrições da tabela auxiliar de espécies.
* 2. **Verificação de Resultados:**
* - Se a consulta for bem-sucedida, mas **não retornar nenhuma espécie** (`empty($stmt)`), retorna um array com `status` 'fail' e código `200 OK`.
* - Se a consulta for bem-sucedida e **retornar espécies**, retorna um array com `status` 'success', código `200 OK` e os dados encontrados na chave `output`.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante a consulta retorna um array com `status` 'error', o código de erro da exceção e uma mensagem detalhada de erro.
*
* @return array Um array associativo contendo o status da operação, o código de resposta HTTP e os dados das espécies (`output['data']`).
*/
public function getAllEspecies(): array {
try {
$sql =
"SELECT
id,
uuid,
descricao
FROM {$this->petAuxEspecieTable}";
$result = DB::fetchAll(sql: $sql);
if(empty($result)) {
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Nenhuma espécie encontrada.',
'output' => [
'data' => []
]
];
}
$preparedResult = [];
foreach($result as $key => $especie) {
$sql =
"SELECT
uuid,
descricao
FROM {$this->petAuxRacaTable}
WHERE especie_id = :especie_id";
$params = [
':especie_id' => $especie['id']
];
$result = DB::fetchAll(sql: $sql, params: $params);
$preparedResult[] = [
'uuid' => $especie['uuid'],
'descricao' => $especie['descricao'],
'racas' => $result
];
}
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Espécies encontradas com sucesso.',
'output' => [
'data' => $preparedResult
]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?? 500,
'status' => 'error',
'message' => $e->getMessage(),
];
}
}
/**
* Busca uma única espécie de pet no banco de dados por um identificador específico.
*
* Este método recupera os dados de uma espécie da tabela auxiliar `{$this->petAuxEspecieTable}` de forma flexível, permitindo a busca por qualquer coluna (como 'id' ou 'uuid') e o valor correspondente.
*
* #### Fluxo de Operação:
* 1. **Execução da Consulta:** O método monta e executa uma consulta `SELECT` que usa o `$identifier` de forma dinâmica na cláusula `WHERE` e um parâmetro nomeado seguro (`:value`) para o valor. A consulta utiliza `DB::fetchOne`, indicando que apenas um registro é esperado.
* 2. **Verificação de Resultados:**
* - Se a consulta for bem-sucedida e **nenhuma espécie for encontrada** (`empty($result)`), retorna um array com **`status` 'fail'** e código **200 OK**.
* - Se uma espécie for encontrada, retorna um array com **`status` 'success'**, código **200 OK** e os dados do registro na chave `output`.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante a consulta retorna um array com **`status` 'error'**, o código de erro da exceção e uma mensagem de erro detalhada.
*
* @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'id', 'uuid', 'descricao').
* @param mixed $value O valor correspondente ao identificador que está sendo buscado.
* @return array Um array associativo contendo o status da operação, um código de resposta HTTP e os dados da espécie, se encontrada.
*/
public function getEspecieByIdentifier(string $identifier, mixed $value): array {
try {
$sql =
"SELECT
id,
uuid,
descricao
FROM {$this->petAuxEspecieTable}
WHERE {$identifier} = :value
ORDER BY descricao ASC";
$params = [
':value' => $value
];
$result = DB::fetchOne(sql: $sql, params: $params);
if(empty($result)) {
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Espécie não encontrada.',
'output' => [
'data' => []
]
];
}
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Espécie encontrada com sucesso.',
'output' => [
'data' => $result
]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?? 500,
'status' => 'error',
'message' => $e->getMessage(),
];
}
}
/**
* Busca uma única raça de pet no banco de dados por um identificador específico.
*
* Este método recupera os dados de uma raça da tabela auxiliar `{$this->petAuxRacaTable}` de forma flexível, permitindo a busca por qualquer coluna como identificador (`$identifier`) e o valor correspondente. A consulta retorna apenas um registro e é ordenada pela descrição.
*
* #### Fluxo de Operação:
* 1. **Execução da Consulta:** O método monta e executa uma consulta `SELECT` que usa o `$identifier` de forma dinâmica na cláusula `WHERE` e um parâmetro nomeado seguro (`:value`) para o valor.
* 2. **Verificação de Resultados:**
* - Se uma raça for encontrada, retorna um array com **`status` 'success'**, código **200 OK** e os dados da raça na chave `output`.
* - Se a consulta for bem-sucedida, mas **nenhuma raça for encontrada** (`empty($result)`), retorna um array com **`status` 'success'** (indicando que a consulta foi bem-sucedida) e uma mensagem de "Raça não encontrada.".
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante a consulta retorna um array com **`status` 'error'**, o código de erro da exceção (ou `500` como fallback) e uma mensagem de erro detalhada.
*
* @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'uuid', 'id', 'descricao').
* @param mixed $value O valor correspondente ao identificador que está sendo buscado.
* @return array Um array associativo contendo o status da operação, um código de resposta HTTP e os dados da raça, se encontrada.
*/
public function getRacaByIdentifier(string $identifier, mixed $value): array {
try {
$sql =
"SELECT
id,
uuid,
especie_id,
descricao
FROM {$this->petAuxRacaTable}
WHERE {$identifier} = :value
ORDER BY descricao ASC";
$params = [
':value' => $value
];
$result = DB::fetchOne(sql: $sql, params: $params);
if(empty($result)) {
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Raça não encontrada.',
'output' => [
'data' => []
]
];
}
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Raça encontrada com sucesso.',
'output' => [
'data' => $result
]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?? 500,
'status' => 'error',
'message' => $e->getMessage(),
];
}
}
/**
* Busca todas as raças de pets associadas a uma espécie específica.
*
* Este método primeiro consulta a espécie usando um identificador (UUID, ID, etc.) e, em seguida, usa o ID interno da espécie para listar todas as raças vinculadas a ela na tabela auxiliar `{$this->petAuxRacaTable}`.
*
* #### Fluxo de Operação:
* 1. **Busca da Espécie:** O método chama `getEspecieByIdentifier` para encontrar o ID da espécie. Se a busca falhar, o resultado de erro é retornado imediatamente.
* 2. **Consulta das Raças:** Executa uma consulta `SELECT` que busca todas as raças onde `especie_id` corresponde ao ID da espécie encontrado. Os resultados são ordenados alfabeticamente pela descrição.
* 3. **Verificação de Resultados:**
* - Se a consulta for bem-sucedida, mas **nenhuma raça for encontrada**, retorna um array com **`status` 'fail'** e código **200 OK**.
* - Se raças forem encontradas, retorna um array com **`status` 'success'**, código **200 OK** e os dados encontrados na chave `output`.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo (além daquelas tratadas na busca inicial) retorna um array com **`status` 'error'**, o código de erro da exceção e uma mensagem de erro detalhada.
*
* @param string $identifier A coluna da tabela auxiliar de espécies a ser usada para identificar a espécie (ex: 'uuid', 'id').
* @param mixed $value O valor correspondente ao identificador da espécie.
* @return array Um array associativo contendo o status da operação, o código de resposta HTTP e os dados das raças (`output['data']`).
*/
public function getRacasByEspecieIdentifier(string $identifier, mixed $value): array {
try {
$especieData = $this->getEspecieByIdentifier(identifier: $identifier, value: $value);
if($especieData['status'] !== 'success') {
return $especieData;
}
$especieData = $especieData['output']['data'];
$sql =
"SELECT
id,
uuid,
descricao
FROM {$this->petAuxRacaTable}
WHERE especie_id = :especie_id
ORDER BY descricao ASC";
$params = [
':especie_id' => $especieData['id']
];
$racas = DB::fetchAll(sql: $sql, params: $params);
if(empty($racas)) {
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Nenhuma raça encontrada para a espécie informada.',
'output' => [
'data' => []
]
];
}
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Raças encontradas com sucesso.',
'output' => [
'data' => $racas
]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?? 500,
'status' => 'error',
'message' => $e->getMessage(),
];
}
}
/**
* Armazena uma nova raça de pet no banco de dados.
*
* Este método insere os dados de uma nova raça na tabela auxiliar `{$this->petAuxRacaTable}`. A raça é associada a uma espécie existente. A operação utiliza um bloco `try-catch` para garantir o tratamento de erros.
*
* #### Fluxo de Operação:
* 1. **Execução da Query:** Prepara e executa a instrução `INSERT` para inserir o novo registro com o UUID, ID da espécie e a descrição.
* 2. **Confirmação e Retorno de Sucesso:** Se a inserção for bem-sucedida, retorna um array com **`status` 'success'**, código **201 Created** e uma mensagem de êxito.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo retorna um array com **`status` 'error'**, o código de erro da exceção e uma mensagem detalhada de erro.
*
* @param PetRacaDTO $petRacaDTO O objeto DTO contendo o UUID, o ID da espécie (`especie_id`) e a descrição da raça a ser criada.
* @return array Um array associativo contendo o status da operação, o código de resposta HTTP e uma mensagem.
*/
public function storeRaca(PetRacaDTO $petRacaDTO): array {
try {
$sql =
"INSERT INTO {$this->petAuxRacaTable} (
uuid,
especie_id,
descricao
) VALUES (
:uuid,
:especie_id,
:descricao
)";
$params = [
':uuid' => $petRacaDTO->getUuid(),
':especie_id' => $petRacaDTO->getEspecieId(),
':descricao' => $petRacaDTO->getDescricao()
];
DB::execute(sql: $sql, params: $params);
return [
'response_code' => 201,
'status' => 'success',
'message' => 'Raça armazenada com sucesso.',
'output' => [
'data' => [
'id' => DB::lastInsertId(),
'uuid' => $petRacaDTO->getUuid(),
'especie_id' => $petRacaDTO->getEspecieId(),
'descricao' => $petRacaDTO->getDescricao()
]
]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?? 500,
'status' => 'error',
'message' => $e->getMessage(),
];
}
}
/**
* Armazena um novo pet no banco de dados.
*
* Este método insere os dados de um novo pet na tabela `{$this->petTable}`, utilizando um objeto `PetDTO` que encapsula todas as informações relevantes. A operação é atômica e utiliza um bloco `try-catch` para garantir o tratamento de erros.
*
* #### Fluxo de Operação:
* 1. **Execução da Query:** Prepara e executa a instrução `INSERT` para inserir o novo registro com todos os detalhes do pet (UUID, nome, espécie, raça, dados de saúde, foto, etc.).
* 2. **Confirmação e Retorno de Sucesso:** Se a inserção for bem-sucedida, retorna um array com **`status` 'success'**, código **201 Created** e uma mensagem de êxito. Os dados do pet recém-criado, incluindo o ID gerado pelo banco, são retornados na chave `output`.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo retorna um array com **`status` 'error'**, o código de erro da exceção e uma mensagem detalhada de erro.
*
* @param PetDTO $petDTO O objeto DTO contendo os dados do pet a ser criado.
* @return array Um array associativo contendo o status da operação, o código de resposta HTTP e os dados do pet, se a operação for bem-sucedida.
*/
public function storePet(PetDTO $petDTO): array {
try {
$sql =
"INSERT INTO {$this->petTable} (
uuid,
nome,
especie_id,
raca_id,
caminho_foto,
registro_geral_animal,
photo_path,
data_nascimento,
data_obito,
sexo,
created_at,
updated_at,
deleted_at
) VALUES (
:uuid,
:nome,
:especie_id,
:raca_id,
:caminho_foto,
:registro_geral_animal,
:photo_path,
:data_nascimento,
:data_obito,
:sexo,
:created_at,
:updated_at,
:deleted_at
)";
$params = [
':uuid' => $petDTO->getUuid(),
':nome' => $petDTO->getNome(),
':especie_id' => $petDTO->getEspecieId(),
':raca_id' => $petDTO->getRacaId(),
':caminho_foto' => $petDTO->getCaminhoFoto(),
':registro_geral_animal' => $petDTO->getRegistroGeralAnimal(),
':photo_path' => $petDTO->getPhotoPath(),
':data_nascimento' => $petDTO->getDataNascimento(),
':data_obito' => $petDTO->getDataObito(),
':sexo' => $petDTO->getSexo(),
':created_at' => $petDTO->getCreatedAt(),
':updated_at' => $petDTO->getUpdatedAt(),
':deleted_at' => $petDTO->getDeletedAt()
];
DB::execute(sql: $sql, params: $params);
return [
'response_code' => 201,
'status' => 'success',
'message' => 'Pet armazenado com sucesso.',
'output' => [
'data' => [
'id' => DB::lastInsertId(),
'uuid' => $petDTO->getUuid(),
'nome' => $petDTO->getNome(),
'especie_id' => $petDTO->getEspecieId(),
'raca_id' => $petDTO->getRacaId(),
'caminho_foto' => $petDTO->getCaminhoFoto(),
'registro_geral_animal' => $petDTO->getRegistroGeralAnimal(),
'photo_path' => $petDTO->getPhotoPath(),
'data_nascimento' => $petDTO->getDataNascimento(),
'data_obito' => $petDTO->getDataObito(),
'sexo' => $petDTO->getSexo(),
'created_at' => $petDTO->getCreatedAt(),
'updated_at' => $petDTO->getUpdatedAt(),
'deleted_at' => $petDTO->getDeletedAt()
]
]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?? 500,
'status' => 'error',
'message' => $e->getMessage(),
];
}
}
/**
* Associa um pet a um usuário (tutor) no banco de dados.
*
* Este método insere um novo registro na tabela de associação `{$this->usuarioPet}`, estabelecendo a relação de propriedade entre o usuário e o pet.
*
* #### Fluxo de Operação:
* 1. **Execução da Query:** Prepara e executa a instrução `INSERT` para inserir os IDs do usuário e do pet na tabela de associação.
* 2. **Retorno de Sucesso:** Se a inserção for bem-sucedida, retorna um array com **`status` 'success'**, código **201 Created** e uma mensagem de êxito.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo retorna um array com **`status` 'error'**, o código de erro da exceção (ou `500` como fallback) e uma mensagem detalhada de erro.
*
* @param int $usuario_id O ID do usuário (tutor) a ser associado.
* @param int $pet_id O ID do pet a ser associado.
* @return array Um array associativo contendo o status da operação, um código de resposta HTTP e uma mensagem.
*/
public function storeUsuarioPet(int $usuario_id, int $pet_id): array {
try {
$sql =
"INSERT INTO {$this->usuarioPet} (
usuario_id,
pet_id
) VALUES (
:usuario_id,
:pet_id
)";
$params = [
':usuario_id' => $usuario_id,
':pet_id' => $pet_id
];
DB::execute(sql: $sql, params: $params);
return [
'response_code' => 201,
'status' => 'success',
'message' => 'Associação entre usuário e pet criada com sucesso.'
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?? 500,
'status' => 'error',
'message' => $e->getMessage(),
];
}
}
/**
* Busca um único pet no banco de dados por um identificador específico.
*
* Este método recupera todos os detalhes de um pet da tabela `{$this->petTable}`. A busca é flexível e permite usar qualquer coluna como identificador (`$identifier`) e o valor correspondente.
*
* #### Fluxo de Operação:
* 1. **Execução da Query:** O método monta e executa uma consulta `SELECT` que usa o `$identifier` de forma dinâmica na cláusula `WHERE`. A consulta usa `DB::fetchOne`, indicando que apenas um registro é esperado, e ordena o resultado pelo nome.
* 2. **Verificação de Resultados:**
* - Se o pet for encontrado, retorna um array com **`status` 'success'**, código **200 OK** e os dados do pet na chave `output`.
* - Se o pet **não for encontrado** (`empty($result)`), retorna um array com **`status` 'success'** (indicando que a consulta foi bem-sucedida) e uma mensagem de "Pet não encontrado.".
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo retorna um array com **`status` 'error'**, o código de erro da exceção (ou `500` como fallback) e uma mensagem de erro detalhada.
*
* @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'uuid', 'id', 'registro_geral_animal').
* @param string $value O valor correspondente ao identificador que está sendo buscado.
* @return array Um array associativo contendo o status da operação, um código de resposta HTTP e os dados do pet, se encontrado.
*/
public function getPetByIdentifier(string $identifier, string $value): array {
try {
$sql =
"SELECT
p.id,
p.uuid,
p.nome,
p.especie_id,
pae.descricao AS especie,
p.raca_id,
par.descricao AS raca,
p.caminho_foto,
p.registro_geral_animal,
p.photo_path,
p.data_nascimento,
p.data_obito,
p.sexo,
p.created_at,
p.updated_at,
p.deleted_at
FROM {$this->petTable} p
LEFT JOIN {$this->petAuxRacaTable} par ON p.raca_id = par.id
LEFT JOIN {$this->petAuxEspecieTable} pae ON p.especie_id = pae.id
WHERE p.{$identifier} = :value
ORDER BY p.nome ASC";
$params = [
':value' => $value
];
$result = DB::fetchOne(sql: $sql, params: $params);
if(empty($result)) {
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Pet não encontrado.',
'output' => [
'data' => []
]
];
}
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Pet encontrado com sucesso.',
'output' => [
'data' => $result
]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?? 500,
'status' => 'error',
'message' => $e->getMessage(),
];
}
}
}

View File

@@ -0,0 +1,41 @@
<?php
use AxiumPHP\Core\Router;
use Zampet\Module\Tutor\v0\Controllers\PetController;
Router::group(
prefix: '/pet',
callback: function() {
// Rota para listar todos os pets do tutor autenticado
Router::GET(
uri: '/list-my-pets',
handler: [PetController::class, 'listMyPets']
);
// Rota para listar todas as espécies e raças de pets
Router::GET(
uri: '/especies',
handler: [PetController::class, 'getAllEspecies']
);
// Cria Nova Raça
Router::POST(
uri: '/create-raca',
handler: [PetController::class, 'storeRaca']
);
// Cria Novo Pet
Router::POST(
uri: '/create-pet',
handler: [PetController::class, 'storePet']
);
// Consulta Detalhes do Pet
Router::GET(
uri: '/{pet_uuid}',
handler: [PetController::class, 'getPetDetails']
);
},
middlewares: [
]
);

View File

@@ -0,0 +1,9 @@
<?php
use AxiumPHP\Core\Router;
Router::group(
prefix: '/tutor/v0',
callback: function() {
require 'PetRoutes.php';
}
);

View File

@@ -0,0 +1,241 @@
<?php
namespace Zampet\Module\Tutor\v0\Services;
use DateTime;
use Exception;
use Ramsey\Uuid\Uuid;
use Zampet\Module\Auth\v0\Models\UsuarioModel;
use Zampet\Services\DB;
use Zampet\Services\JWTService;
use Zampet\Helpers\DataValidator;
use Zampet\Module\Tutor\v0\DTO\PetDTO;
use Zampet\Module\Tutor\v0\DTO\PetRacaDTO;
use Zampet\Module\Tutor\v0\Models\PetModel;
class PetService {
/**
* Retorna a lista de todas as espécies de pets disponíveis no sistema.
*
* Este método atua como um **wrapper** (invólucro) para o método de mesmo nome na classe `PetModel`. Ele delega a responsabilidade de buscar a lista completa das espécies de pets cadastradas para a camada de modelo (DAO).
*
* @return array Um array associativo contendo o status da operação, o código HTTP e os dados das espécies, conforme retornado pelo `PetModel`.
*/
public function getAllEspecies(): array {
return (new PetModel())->getAllEspecies();
}
/**
* Lista todos os pets de um tutor específico, buscando pelo UUID do tutor.
*
* Este método atua como um **wrapper** para o método `listPetsByTutorIdentifier` da classe `PetModel`. Ele delega a responsabilidade de buscar a lista completa e detalhada de pets associados a um tutor, utilizando o UUID como o identificador principal.
*
* @param string $uuid O UUID do tutor cujos pets devem ser listados.
* @return array Um array associativo contendo o status da operação, o código HTTP e os dados dos pets, conforme retornado pelo `PetModel`.
*/
public function listPetsByTutorUuid(string $uuid): array {
return (new PetModel())->listPetsByTutorIdentifier(identifier: 'uuid', value: $uuid);
}
/**
* Busca uma espécie de pet no sistema por um identificador específico.
*
* Este método atua como um **wrapper** para o método de busca de espécies na classe `PetModel`. Ele delega a responsabilidade de consultar a espécie para a camada de modelo (DAO), que lida com a lógica de acesso ao banco de dados.
*
* @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'id', 'uuid', 'descricao').
* @param string $value O valor correspondente ao identificador que está sendo buscado.
* @return array Um array associativo contendo o status da operação, o código HTTP e os dados da espécie, conforme retornado pelo `PetModel`.
*/
public function getEspecieByIdentifier(string $identifier, string $value): array {
return (new PetModel())->getEspecieByIdentifier(identifier: $identifier, value: $value);
}
/**
* Busca uma raça de pet no sistema por um identificador específico.
*
* Este método atua como um **wrapper** (invólucro) para o método de busca de raças na classe `PetModel`. Ele delega a responsabilidade de consultar a raça para a camada de modelo (DAO), que lida com a lógica de acesso ao banco de dados e retorna o resultado.
*
* @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'uuid', 'id', 'descricao').
* @param string $value O valor correspondente ao identificador que está sendo buscado.
* @return array Um array associativo contendo o status da operação, o código HTTP e os dados da raça, conforme retornado pelo `PetModel`.
*/
public function getRacaByIdentifier(string $identifier, string $value): array {
return (new PetModel())->getRacaByIdentifier(identifier: $identifier, value: $value);
}
/**
* Busca um pet no sistema por um identificador específico.
*
* Este método atua como um **wrapper** (invólucro) para o método de busca de pets na classe `PetModel`. Ele delega a responsabilidade de consultar o pet para a camada de modelo (DAO), que lida com a lógica de acesso ao banco de dados.
*
* @param string $identifier A coluna da tabela a ser usada como critério de busca (ex: 'uuid', 'id', 'nome').
* @param string $value O valor correspondente ao identificador que está sendo buscado.
* @return array Um array associativo contendo o status da operação, o código HTTP e os dados do pet, conforme retornado pelo `PetModel`.
*/
public function getPetByIdentifier(string $identifier, string $value): array {
return (new PetModel())->getPetByIdentifier(identifier: $identifier, value: $value);
}
/**
* Cria uma nova raça de pet no sistema após validação.
*
* Este método orquestra a criação de uma nova raça de pet. Ele realiza a validação dos dados de entrada, define metadados essenciais (UUID) e persiste o registro no banco de dados, garantindo a atomicidade da operação através de uma transação.
*
* #### Fluxo de Operação:
* 1. **Início da Transação:** Inicia uma transação no banco de dados (`DB::beginTransaction()`).
* 2. **Validação de Dados:** Utiliza o `DataValidator` para verificar se os campos **'especie_id'** e **'descricao'** foram preenchidos.
* 3. **Encerramento em Caso de Falha na Validação:** Se houver erros de validação, a transação é revertida (`DB::rollBack()`) e um array de erro formatado é retornado com o código `400 Bad Request`.
* 4. **Preparação Final dos Dados:** Define um **UUID** único para a nova raça.
* 5. **Persistência:** Chama o método `storeRaca` do `PetModel` para inserir o registro no banco de dados. Se a persistência falhar, uma `Exception` é lançada com a mensagem de erro do modelo.
* 6. **Confirmação e Retorno:** Se a persistência for bem-sucedida, a transação é **confirmada** (`DB::commit()`) e o resultado de sucesso é retornado com o código `201 Created`.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` lançada durante o processo é capturada, e a transação é **revertida** (`DB::rollBack()`).
* - Retorna um array de erro formatado contendo o código HTTP e a mensagem da exceção.
*
* @param PetRacaDTO $petRacaDTO O objeto DTO contendo o ID da espécie e a descrição da nova raça.
* @return array Um array associativo contendo o status da operação, o código de resposta HTTP e os dados de saída, ou erros de validação.
*/
public function storeRaca(PetRacaDTO $petRacaDTO): array {
// Inicia transação no banco de dados
DB::beginTransaction();
try {
// Valida os dados do DTO
$dataValidador = new DataValidator(inputs: $petRacaDTO->toArray());
$dataValidador->validate(field: 'especie_id', rule: 'notEmpty', message: 'Espécie é obrigatória.');
$dataValidador->validate(field: 'descricao', rule: 'notEmpty', message: 'Descrição é obrigatória.');
// Se houver erros de validação, retornar resposta de erro
if(!$dataValidador->passes()) {
DB::rollBack();
return [
'response_code' => 400,
'status' => 'error',
'message' => 'Validation errors',
'output' => [
'errors' => $dataValidador->getErrors()
]
];
}
// Define dados adicionais
$petRacaDTO->setUuid(uuid: Uuid::uuid7()->toString());
// Cria a nova raça no banco de dados
$storeRaca = (new PetModel())->storeRaca(petRacaDTO: $petRacaDTO);
if($storeRaca['status'] !== 'success') {
throw new Exception(message: $storeRaca['message'], code: $storeRaca['response_code']);
}
DB::commit();
return [
'response_code' => 201,
'status' => 'success',
'message' => 'Raça criada com sucesso.',
'output' => $storeRaca['output']
];
} catch(Exception $e) {
DB::rollBack();
return [
'response_code' => $e->getCode(),
'status' => 'error',
'message' => $e->getMessage()
];
}
}
/**
* Armazena um novo pet no sistema e o associa ao usuário autenticado.
*
* Este método é responsável por orquestrar a criação de um novo pet, desde a validação inicial até a persistência do pet e o registro da relação com o tutor. A operação é **atômica** e garante a integridade dos dados através de uma transação de banco de dados.
*
* #### Fluxo de Operação:
* 1. **Início da Transação:** Inicia uma transação no banco de dados (`DB::beginTransaction()`).
* 2. **Validação de Dados:** O `DataValidator` verifica a presença e validade dos campos obrigatórios (nome, raça UUID, data de nascimento e sexo). Se falhar, a transação é revertida e um erro `400 Bad Request` é retornado.
* 3. **Validação de Duplicidade (RGA):** Se fornecido, o Registro Geral Animal (RGA) é verificado quanto à unicidade. Se um pet com o mesmo RGA for encontrado, uma `Exception` é lançada com o código `409 Conflict`.
* 4. **Preparação Final dos Dados:** O UUID do pet e o timestamp de criação são definidos.
* 5. **Criação do Pet:** O método `storePet` do `PetModel` é chamado para inserir o registro do pet. Se falhar, uma `Exception` é lançada.
* 6. **Associação ao Tutor:** O token JWT é decodificado para obter o UUID do tutor. O ID interno do tutor é buscado, e o pet é associado ao tutor através do método `storeUsuarioPet`. Se o tutor não for encontrado ou a associação falhar, uma `Exception` é lançada.
* 7. **Confirmação e Retorno:** Se todas as operações forem bem-sucedidas, a transação é **confirmada** (`DB::commit()`) e o resultado de sucesso é retornado com o código `201 Created`.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` lançada durante o processo é capturada, a transação é **revertida** (`DB::rollBack()`), e um array de erro formatado contendo o código HTTP e a mensagem da exceção é retornado.
*
* @param PetDTO $petDTO O objeto DTO contendo os dados do novo pet.
* @return array Um array associativo com o status da operação, código HTTP e os dados do pet criado.
*/
public function storePet(PetDTO $petDTO): array {
// Inicia transação no banco de dados
DB::beginTransaction();
try {
// Valida os dados do DTO
$dataValidador = new DataValidator(inputs: $petDTO->toArray());
$dataValidador->validate(field: 'nome', rule: 'notEmpty', message: 'Nome do pet é obrigatório.');
$dataValidador->validate(field: 'sexo', rule: 'notEmpty', message: 'Sexo do pet é obrigatório.');
$dataValidador->validate(field: 'raca_id', rule: 'notEmpty', message: 'Raça do pet é obrigatória.');
$dataValidador->validate(field: 'especie_id', rule: 'notEmpty', message: 'Espécie do pet é obrigatória.');
$dataValidador->validate(field: 'data_nascimento', rule: 'notEmpty', message: 'Data de nascimento do pet é obrigatória.');
// Se houver erros de validação, retornar resposta de erro
if(!$dataValidador->passes()) {
return [
'response_code' => 400,
'status' => 'error',
'message' => 'Validation errors',
'output' => [
'errors' => $dataValidador->getErrors()
]
];
}
// Verifica se o Registro Geral Animal do PET (RGA) é único, se fornecido
if(($petDTO->getRegistroGeralAnimal() !== null) && !empty($petDTO->getRegistroGeralAnimal())) {
$existingPet = $this->getPetByIdentifier(identifier: 'registro_geral_animal', value: $petDTO->getRegistroGeralAnimal());
if($existingPet['status'] === 'success' && !empty($existingPet['output']['data'])) {
throw new Exception(message: 'Já existe um pet cadastrado com este Registro Geral Animal (RGA).', code: 409);
}
}
// Define dados adicionais
$petDTO->setUuid(uuid: Uuid::uuid7()->toString());
$petDTO->setCreatedAt(created_at: (new DateTime())->format(format: 'Y-m-d H:i:s'));
// Cria o novo pet no banco de dados
$storePet = (new PetModel())->storePet(petDTO: $petDTO);
if($storePet['status'] !== 'success') {
throw new Exception(message: $storePet['message'], code: $storePet['response_code']);
}
// Consulta o token JWT
$decodedToken = JWTService::decode(token: JWTService::getBearerToken());
// Consulta o ID do tutor pelo UUID
$getTutor = (new UsuarioModel())->getUsuarioByIdentifier(identifier: 'uuid', value: $decodedToken['user_uuid']);
if($getTutor['status'] !== 'success' || empty($getTutor['output']['data'])) {
throw new Exception(message: 'Tutor não encontrado.', code: 404);
}
// Relaciona o pet ao tutor autenticado
$dataValidador = (new PetModel())->storeUsuarioPet(usuario_id: $getTutor['output']['data']['id'], pet_id: $storePet['output']['data']['id']);
if($dataValidador['status'] !== 'success') {
throw new Exception(message: $dataValidador['message'], code: $dataValidador['response_code']);
}
DB::commit();
return [
'response_code' => 201,
'status' => 'success',
'message' => 'Pet criado com sucesso.',
'output' => $storePet['output']
];
} catch(Exception $e) {
DB::rollBack();
return [
'response_code' => $e->getCode(),
'status' => 'error',
'message' => $e->getMessage()
];
}
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace Zampet\Module\Tutor;
use DateTime;
use Exception;
// Caminho do manifest.json
$manifestPath = realpath(path: __DIR__ . "/manifest.json");
if (!$manifestPath || !file_exists(filename: $manifestPath)) {
throw new Exception(message: "Arquivo 'manifest.json' não encontrado.");
}
// Lê e decodifica
$manifest = json_decode(json: file_get_contents(filename: $manifestPath), associative: true);
// Verifica estrutura
if (!isset($manifest['apis']) || !is_array(value: $manifest['apis'])) {
throw new Exception(message: "Estrutura de 'apis' inválida no manifest.");
}
$now = (new DateTime())->format(format: 'Y-m-d');
$newApis = [];
$updated = false;
// Checa se tem versão esperando que começa hoje
foreach ($manifest['apis'] as $api) {
if ($api['status'] === 'planned' && $api['start_date'] === $now) {
// Promote a nova
$api['status'] = 'active';
$newApis[] = $api;
$updated = true;
} elseif ($api['status'] === 'active' && $api['start_date'] !== $now) {
// Ignora versão antiga ativa
continue;
} else {
$newApis[] = $api;
}
}
// Atualiza manifest se tiver mudança
if ($updated) {
$manifest['apis'] = $newApis;
file_put_contents(filename: $manifestPath, data: json_encode(value: $manifest, flags: JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
// Carrega a versão ativa
$loaded = false;
foreach ($manifest['apis'] as $api) {
$startDate = new DateTime(datetime: $api['start_date']);
if ($api['status'] === 'active' && $startDate <= new DateTime()) {
$routeFile = MODULE_PATH . "/{$manifest['dirName']}/{$api['version']}/Routes/Routes.php";
if (file_exists(filename: $routeFile)) {
require_once realpath(path: $routeFile);
$loaded = true;
break;
} else {
throw new Exception(message: "Arquivo de rotas não encontrado para a versão '{$api['version']}'.");
}
}
}
// Verifica se carregou
if (!$loaded) {
throw new Exception(message: "Nenhuma versão ativa da API foi carregada.");
}

View File

@@ -0,0 +1,16 @@
{
"name": "Tutor",
"dirName": "Tutor",
"slug": "tutor",
"description": "Gerencia informações dos tutores e suas interações.",
"uuid": "0199b5de-f72c-702a-ba26-1c1d6f6d3dc2",
"version": "1.0",
"dependencies": [],
"apis": [
{
"version": "v0",
"status": "active",
"start_date": "2025-08-01"
}
]
}

0
app/Module/blank_file Executable file
View File

129
app/Storage/Logs/php_errors.log Executable file
View File

@@ -0,0 +1,129 @@
[03-Oct-2025 16:59:04 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 16:59:04 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 16:59:04 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 16:59:04 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 16:59:04 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 16:59:36 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 16:59:36 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 16:59:36 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 16:59:36 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 16:59:36 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:01:25 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:01:25 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:01:25 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:01:25 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:01:25 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:01:57 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:01:57 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:01:57 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:01:57 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:01:57 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:03:27 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:03:27 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:03:27 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:03:27 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:03:27 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:04:20 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:04:20 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:04:20 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:04:20 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:04:20 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:05:37 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:05:37 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:05:37 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:05:37 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:05:37 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:07:45 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:07:45 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:07:45 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:07:45 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:07:45 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:25:56 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:25:56 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:25:56 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:25:56 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:25:56 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:26:15 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:26:15 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:26:15 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:26:15 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:26:15 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:26:25 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:26:25 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:26:25 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:26:25 America/Fortaleza] PHP 2. require() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:26:25 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:28:02 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:28:02 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:28:02 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:28:02 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:28:02 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:30:33 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:30:33 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:30:33 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:30:33 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:4
[03-Oct-2025 17:30:33 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(4): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:30:45 America/Fortaleza] PHP Warning: require_once(/var/www/zampet/backend): Failed to open stream: No such file or directory in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:30:45 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:30:45 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0
[03-Oct-2025 17:30:45 America/Fortaleza] PHP 2. require_once() /var/www/zampet/backend/zampet-cli:3
[03-Oct-2025 17:30:45 America/Fortaleza] PHP Fatal error: Uncaught Error: Failed opening required '' (include_path='.:/usr/share/php') in /var/www/zampet/backend/public/index.php:71
Stack trace:
#0 /var/www/zampet/backend/zampet-cli(3): require_once()
#1 {main}
thrown in /var/www/zampet/backend/public/index.php on line 71
[03-Oct-2025 17:31:46 America/Fortaleza] PHP Warning: Undefined array key 1 in /var/www/zampet/backend/zampet-cli on line 13
[03-Oct-2025 17:31:46 America/Fortaleza] PHP Stack trace:
[03-Oct-2025 17:31:46 America/Fortaleza] PHP 1. {main}() /var/www/zampet/backend/zampet-cli:0