Módulo Usuario — Exemplo Completo
Exemplo prático com o módulo Usuario — simples, comentado e pronto para usar.
O módulo Usuario com registro, listagem, atualização e deleção — com Entity comentada, Repository, Service, Controller, Migration, Seeder e Rotas.
Estrutura de arquivos
Passo 1 — Criar a estrutura
php vupi make:module Usuario
Ou manualmente:
mkdir -p src/Modules/Usuario/{Controllers,Services,Repositories,Entities,Database/Migrations,Database/Seeders,Routes}
Passo 2 — Entity
A Entity representa o domínio do usuário. Ela encapsula os dados e garante que nenhum objeto inválido seja criado — validações ficam aqui, não no Controller.
Arquivo: src/Modules/Usuario/Entities/Usuario.php
<?php
namespace Src\Modules\Usuario\Entities;
use DateTimeImmutable;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
/**
* Entity Usuario — representa um usuário do sistema.
*
* Regras de negócio ficam aqui: validações de e-mail, senha e nível de acesso.
* O construtor é privado — use os métodos estáticos registrar() e reconstituir().
*/
final class Usuario
{
// Níveis de acesso permitidos no sistema
private const NIVEIS_VALIDOS = ['usuario', 'admin', 'moderador', 'admin_system'];
// Construtor privado: força o uso das factories abaixo
private function __construct(
private readonly UuidInterface $uuid, // Identificador único imutável
private string $nomeCompleto, // Nome completo do usuário
private string $username, // Login único (ex: joao.silva)
private string $email, // E-mail único
private string $senhaHash, // Hash Argon2ID — nunca a senha em texto
private string $nivelAcesso, // Permissão: usuario | admin | admin_system
private bool $ativo, // false = conta desativada
private bool $verificadoEmail, // true = e-mail confirmado
private readonly DateTimeImmutable $criadoEm, // Data de criação (imutável)
private ?DateTimeImmutable $atualizadoEm = null // Última atualização
) {}
// ── Factories ────────────────────────────────────────────────────────
/**
* Cria um novo usuário com validações.
* Use este método ao registrar um usuário novo.
*/
public static function registrar(
string $nomeCompleto,
string $username,
string $email,
string $senha,
string $nivelAcesso = 'usuario'
): self {
self::validarEmail($email);
self::validarSenha($senha);
self::validarNivel($nivelAcesso);
return new self(
uuid: Uuid::uuid4(), // Gera UUID v4 aleatório
nomeCompleto: trim($nomeCompleto),
username: strtolower(trim($username)), // Normaliza para minúsculas
email: strtolower(trim($email)), // Normaliza para minúsculas
senhaHash: password_hash($senha, PASSWORD_ARGON2ID), // Hash seguro
nivelAcesso: $nivelAcesso,
ativo: true,
verificadoEmail: false,
criadoEm: new DateTimeImmutable()
);
}
/**
* Reconstitui um usuário a partir dos dados do banco.
* Use este método ao mapear uma linha do banco para a Entity.
*/
public static function reconstituir(
UuidInterface $uuid,
string $nomeCompleto,
string $username,
string $email,
string $senhaHash,
string $nivelAcesso,
bool $ativo,
bool $verificadoEmail,
DateTimeImmutable $criadoEm,
?DateTimeImmutable $atualizadoEm = null
): self {
return new self(
$uuid, $nomeCompleto, strtolower(trim($username)),
$email, $senhaHash, $nivelAcesso,
$ativo, $verificadoEmail, $criadoEm, $atualizadoEm
);
}
// ── Validações ────────────────────────────────────────────────────────
private static function validarEmail(string $email): void
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException('E-mail inválido.');
}
}
private static function validarSenha(string $senha): void
{
if (strlen($senha) < 8) {
throw new \InvalidArgumentException('Senha deve ter ao menos 8 caracteres.');
}
}
private static function validarNivel(string $nivel): void
{
if (!in_array($nivel, self::NIVEIS_VALIDOS, true)) {
throw new \InvalidArgumentException('Nível de acesso inválido.');
}
}
// ── Comportamentos ────────────────────────────────────────────────────
/** Verifica se a senha informada corresponde ao hash armazenado. */
public function verificarSenha(string $senhaPlana): bool
{
return password_verify($senhaPlana, $this->senhaHash);
}
/** Altera a senha aplicando validação e gerando novo hash. */
public function alterarSenha(string $novaSenha): void
{
self::validarSenha($novaSenha);
$this->senhaHash = password_hash($novaSenha, PASSWORD_ARGON2ID);
$this->atualizadoEm = new DateTimeImmutable();
}
/** Promove o usuário para um novo nível de acesso. */
public function promoverPara(string $nivel): void
{
self::validarNivel($nivel);
$this->nivelAcesso = $nivel;
$this->atualizadoEm = new DateTimeImmutable();
}
public function ativar(): void { $this->ativo = true; $this->atualizadoEm = new DateTimeImmutable(); }
public function desativar(): void { $this->ativo = false; $this->atualizadoEm = new DateTimeImmutable(); }
// ── Getters ───────────────────────────────────────────────────────────
public function getUuid(): UuidInterface { return $this->uuid; }
public function getNomeCompleto(): string { return $this->nomeCompleto; }
public function getUsername(): string { return $this->username; }
public function getEmail(): string { return $this->email; }
public function getSenhaHash(): string { return $this->senhaHash; }
public function getNivelAcesso(): string { return $this->nivelAcesso; }
public function isAtivo(): bool { return $this->ativo; }
public function isEmailVerificado(): bool { return $this->verificadoEmail; }
public function getCriadoEm(): DateTimeImmutable { return $this->criadoEm; }
public function getAtualizadoEm(): ?DateTimeImmutable { return $this->atualizadoEm; }
// ── Setters ───────────────────────────────────────────────────────────
public function setNomeCompleto(string $v): void { $this->nomeCompleto = trim($v); }
public function setUsername(string $v): void { $this->username = strtolower(trim($v)); }
public function setEmail(string $v): void { self::validarEmail($v); $this->email = strtolower(trim($v)); }
}
Garante que um Usuario só pode ser criado via registrar() (novo) ou reconstituir() (do banco), nunca diretamente com new Usuario(...). Isso evita objetos em estado inválido.
Passo 3 — Repository
A interface define o contrato de persistência. O Repository concreto implementa as queries SQL — o Service nunca toca no banco diretamente.
Arquivo: src/Modules/Usuario/Repositories/UsuarioRepositoryInterface.php
<?php
namespace Src\Modules\Usuario\Repositories;
use Src\Kernel\Contracts\UserRepositoryInterface; // Contrato do Kernel — obrigatório para o Auth funcionar
use Src\Modules\Usuario\Entities\Usuario;
interface UsuarioRepositoryInterface extends UserRepositoryInterface
{
// CRUD básico
public function salvar(Usuario $usuario): void;
public function deletar(string $uuid): void;
public function buscarPorUuid(string $uuid): ?Usuario;
public function buscarPorEmail(string $email): ?Usuario;
public function listar(int $limite = 50, int $offset = 0): array;
public function emailExiste(string $email, ?string $excluirUuid = null): bool;
// Obrigatórios pelo UserRepositoryInterface do Kernel (necessários para o Auth funcionar)
public function buscarPorUsername(string $username): ?object;
public function marcarEmailComoVerificado(string $uuid, bool $verificado = true): void;
public function salvarTokenRecuperacaoSenha(string $uuid, string $token): void;
public function buscarPorTokenRecuperacaoSenha(string $token): ?object;
public function limparTokenRecuperacaoSenha(string $uuid): void;
public function salvarTokenVerificacaoEmail(string $uuid, string $token): void;
public function buscarPorTokenVerificacaoEmail(string $token): ?object;
}
Arquivo: src/Modules/Usuario/Repositories/UsuarioRepository.php
<?php
namespace Src\Modules\Usuario\Repositories;
use PDO;
use DateTimeImmutable;
use Ramsey\Uuid\Uuid;
use Src\Modules\Usuario\Entities\Usuario;
class UsuarioRepository implements UsuarioRepositoryInterface
{
public function __construct(private PDO $pdo) {} // PDO injetado pelo Container
public function salvar(Usuario $usuario): void
{
$driver = $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
$uuid = $usuario->getUuid()->toString();
// Upsert: insere ou atualiza com uma única query (sem SELECT antes)
if ($driver === 'pgsql') {
$sql = "INSERT INTO usuarios
(uuid, nome_completo, username, email, senha_hash, nivel_acesso, ativo, verificado_email, criado_em)
VALUES
(:uuid, :nome, :username, :email, :hash, :nivel, :ativo, FALSE, :criado_em)
ON CONFLICT (uuid) DO UPDATE SET
nome_completo = EXCLUDED.nome_completo,
username = EXCLUDED.username,
email = EXCLUDED.email,
senha_hash = EXCLUDED.senha_hash,
nivel_acesso = EXCLUDED.nivel_acesso,
ativo = EXCLUDED.ativo,
atualizado_em = NOW()";
} else {
$sql = "INSERT INTO usuarios
(uuid, nome_completo, username, email, senha_hash, nivel_acesso, ativo, verificado_email, criado_em)
VALUES
(:uuid, :nome, :username, :email, :hash, :nivel, :ativo, 0, :criado_em)
ON DUPLICATE KEY UPDATE
nome_completo = VALUES(nome_completo),
username = VALUES(username),
email = VALUES(email),
senha_hash = VALUES(senha_hash),
nivel_acesso = VALUES(nivel_acesso),
ativo = VALUES(ativo),
atualizado_em = NOW()";
}
$this->pdo->prepare($sql)->execute([
':uuid' => $uuid,
':nome' => $usuario->getNomeCompleto(),
':username' => $usuario->getUsername(),
':email' => $usuario->getEmail(),
':hash' => $usuario->getSenhaHash(),
':nivel' => $usuario->getNivelAcesso(),
':ativo' => $usuario->isAtivo() ? 1 : 0,
':criado_em' => $usuario->getCriadoEm()->format('Y-m-d H:i:s'),
]);
}
public function buscarPorUuid(string $uuid): ?Usuario
{
$stmt = $this->pdo->prepare('SELECT * FROM usuarios WHERE uuid = :uuid LIMIT 1');
$stmt->execute([':uuid' => $uuid]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? $this->mapear($row) : null;
}
public function buscarPorEmail(string $email): ?Usuario
{
$stmt = $this->pdo->prepare('SELECT * FROM usuarios WHERE email = :email LIMIT 1');
$stmt->execute([':email' => strtolower($email)]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row ? $this->mapear($row) : null;
}
public function listar(int $limite = 50, int $offset = 0): array
{
$stmt = $this->pdo->prepare('SELECT * FROM usuarios ORDER BY criado_em DESC LIMIT :l OFFSET :o');
$stmt->bindValue(':l', $limite, PDO::PARAM_INT);
$stmt->bindValue(':o', $offset, PDO::PARAM_INT);
$stmt->execute();
return array_map([$this, 'mapear'], $stmt->fetchAll(PDO::FETCH_ASSOC));
}
public function deletar(string $uuid): void
{
$this->pdo->prepare('DELETE FROM usuarios WHERE uuid = :uuid')->execute([':uuid' => $uuid]);
}
public function emailExiste(string $email, ?string $excluirUuid = null): bool
{
$sql = 'SELECT COUNT(*) FROM usuarios WHERE email = :email';
$params = [':email' => strtolower($email)];
if ($excluirUuid !== null) {
$sql .= ' AND uuid != :uuid'; // Ignora o próprio usuário ao atualizar
$params[':uuid'] = $excluirUuid;
}
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return (int) $stmt->fetchColumn() > 0; // fetchColumn() retorna o COUNT — não execute()
}
// Mapeia uma linha do banco para a Entity — centraliza o mapeamento em um único lugar
private function mapear(array $row): Usuario
{
return Usuario::reconstituir(
uuid: Uuid::fromString($row['uuid']),
nomeCompleto: $row['nome_completo'],
username: $row['username'],
email: $row['email'],
senhaHash: $row['senha_hash'],
nivelAcesso: $row['nivel_acesso'],
ativo: (bool) $row['ativo'],
verificadoEmail: (bool) ($row['verificado_email'] ?? false),
criadoEm: new DateTimeImmutable($row['criado_em']),
atualizadoEm: isset($row['atualizado_em']) ? new DateTimeImmutable($row['atualizado_em']) : null
);
}
// ── Métodos obrigatórios pelo UserRepositoryInterface do Kernel ───────
// O Auth usa estes métodos para autenticar, verificar e-mail e recuperar senha.
/** Este módulo não usa username — retorna null para compatibilidade com o Kernel. */
public function buscarPorUsername(string $username): ?object { return null; }
public function marcarEmailComoVerificado(string $uuid, bool $verificado = true): void
{
$this->pdo->prepare('UPDATE usuarios SET ativo = :v WHERE uuid = :uuid')
->execute([':v' => $verificado ? 1 : 0, ':uuid' => $uuid]);
}
// Os métodos abaixo são stubs — implemente-os se quiser suporte a
// recuperação de senha e verificação de e-mail neste módulo.
public function salvarTokenRecuperacaoSenha(string $uuid, string $token): void {}
public function buscarPorTokenRecuperacaoSenha(string $token): ?object { return null; }
public function limparTokenRecuperacaoSenha(string $uuid): void {}
public function salvarTokenVerificacaoEmail(string $uuid, string $token): void {}
public function buscarPorTokenVerificacaoEmail(string $token): ?object { return null; }
}
Passo 4 — Service
O Service contém as regras de negócio. Recebe a interface do Repository via construtor — o Container injeta a implementação concreta automaticamente.
Arquivo: src/Modules/Usuario/Services/UsuarioService.php
<?php
namespace Src\Modules\Usuario\Services;
use DomainException;
use Src\Modules\Usuario\Entities\Usuario;
use Src\Modules\Usuario\Repositories\UsuarioRepositoryInterface;
class UsuarioService
{
// Recebe a interface — nunca a implementação concreta (princípio da inversão de dependência)
public function __construct(private UsuarioRepositoryInterface $repository) {}
/**
* Cria um novo usuário.
* Recebe a Entity já construída — as validações ficam em Usuario::registrar().
*/
public function criar(Usuario $usuario): void
{
if ($this->repository->emailExiste($usuario->getEmail())) {
throw new DomainException('E-mail já cadastrado.', 409);
}
$this->repository->salvar($usuario);
}
public function atualizar(string $uuid, array $dados): Usuario
{
$usuario = $this->buscarPorUuid($uuid); // Lança 404 se não encontrar
if (isset($dados['nome_completo'])) $usuario->setNomeCompleto($dados['nome_completo']);
if (isset($dados['username'])) $usuario->setUsername($dados['username']);
if (isset($dados['email'])) {
if ($this->repository->emailExiste($dados['email'], $uuid)) {
throw new DomainException('E-mail já cadastrado.', 409);
}
$usuario->setEmail($dados['email']);
}
if (isset($dados['senha'])) $usuario->alterarSenha($dados['senha']);
if (isset($dados['nivel_acesso'])) $usuario->promoverPara($dados['nivel_acesso']);
$this->repository->salvar($usuario);
return $usuario;
}
public function buscarPorUuid(string $uuid): Usuario
{
return $this->repository->buscarPorUuid($uuid)
?? throw new DomainException('Usuário não encontrado.', 404);
}
public function buscarPorEmail(string $email): ?Usuario
{
return $this->repository->buscarPorEmail($email);
}
public function listar(int $pagina = 1, int $porPagina = 20): array
{
$offset = ($pagina - 1) * $porPagina;
return $this->repository->listar($porPagina, $offset);
}
public function desativar(string $uuid): void
{
$usuario = $this->buscarPorUuid($uuid);
$usuario->desativar();
$this->repository->salvar($usuario);
}
public function deletar(string $uuid): void
{
$this->buscarPorUuid($uuid); // Garante que existe antes de deletar
$this->repository->deletar($uuid);
}
}
Passo 5 — Controller
O Controller recebe a requisição HTTP, delega ao Service e retorna a resposta. Não contém regras de negócio.
Arquivo: src/Modules/Usuario/Controllers/UsuarioController.php
<?php
namespace Src\Modules\Usuario\Controllers;
use DomainException;
use Src\Kernel\Http\Request\Request;
use Src\Kernel\Http\Response\Response;
use Src\Modules\Usuario\Entities\Usuario;
use Src\Modules\Usuario\Services\UsuarioService;
class UsuarioController
{
public function __construct(private UsuarioService $service) {}
/** POST /api/usuarios — Registrar novo usuário */
public function criar(Request $request): Response
{
try {
$b = $request->body ?? [];
$nome = trim((string) ($b['nome_completo'] ?? $b['nome'] ?? ''));
$user = trim((string) ($b['username'] ?? ''));
$mail = trim((string) ($b['email'] ?? ''));
$pass = (string) ($b['senha'] ?? '');
if ($nome === '' || $user === '' || $mail === '' || $pass === '') {
return Response::json(['status' => 'error', 'message' => 'Campos obrigatórios: nome_completo, username, email, senha.'], 422);
}
// Cria a Entity (validações ficam em Usuario::registrar())
$usuario = Usuario::registrar($nome, $user, $mail, $pass);
$this->service->criar($usuario); // Service verifica duplicidade e persiste
return Response::json(['status' => 'success', 'usuario' => $this->serial($usuario)], 201);
} catch (DomainException $e) {
return Response::json(['status' => 'error', 'message' => $e->getMessage()], $e->getCode() ?: 422);
} catch (\Throwable $e) {
return Response::json(['status' => 'error', 'message' => 'Erro ao criar usuário.'], 500);
}
}
/** GET /api/usuarios — Listar usuários */
public function listar(Request $request): Response
{
$pagina = (int) ($request->query['pagina'] ?? 1);
$porPagina = (int) ($request->query['por_pagina'] ?? 20);
$usuarios = $this->service->listar($pagina, $porPagina);
return Response::json(['status' => 'success', 'usuarios' => array_map([$this, 'serial'], $usuarios)]);
}
/** GET /api/usuarios/{uuid} — Buscar por UUID */
public function buscar(Request $request, string $uuid): Response
{
try {
return Response::json(['status' => 'success', 'usuario' => $this->serial($this->service->buscarPorUuid($uuid))]);
} catch (DomainException $e) {
return Response::json(['status' => 'error', 'message' => $e->getMessage()], 404);
}
}
/** PUT /api/usuarios/{uuid} — Atualizar usuário */
public function atualizar(Request $request, string $uuid): Response
{
try {
$usuario = $this->service->atualizar($uuid, $request->body ?? []);
return Response::json(['status' => 'success', 'usuario' => $this->serial($usuario)]);
} catch (DomainException $e) {
return Response::json(['status' => 'error', 'message' => $e->getMessage()], $e->getCode() ?: 422);
} catch (\Throwable $e) {
return Response::json(['status' => 'error', 'message' => 'Erro ao atualizar usuário.'], 500);
}
}
/** DELETE /api/usuarios/{uuid} — Deletar usuário */
public function deletar(Request $request, string $uuid): Response
{
try {
$this->service->deletar($uuid);
return Response::json(['status' => 'success', 'message' => 'Usuário removido.']);
} catch (DomainException $e) {
return Response::json(['status' => 'error', 'message' => $e->getMessage()], $e->getCode() ?: 404);
}
}
/** Serializa a Entity para array — nunca exponha o senhaHash na resposta */
private function serial(\Src\Modules\Usuario\Entities\Usuario $u): array
{
return [
'uuid' => $u->getUuid()->toString(),
'nome_completo' => $u->getNomeCompleto(),
'username' => $u->getUsername(),
'email' => $u->getEmail(),
'nivel_acesso' => $u->getNivelAcesso(),
'ativo' => $u->isAtivo(),
'verificado_email'=> $u->isEmailVerificado(),
'criado_em' => $u->getCriadoEm()->format('Y-m-d\TH:i:sP'),
];
}
}
Passo 6 — Migration
Arquivo: src/Modules/Usuario/Database/Migrations/001_create_usuarios.php
<?php
return [
'up' => function (PDO $pdo): void {
$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
if ($driver === 'pgsql') {
$pdo->exec("
CREATE TABLE IF NOT EXISTS usuarios (
uuid UUID NOT NULL PRIMARY KEY,
nome_completo VARCHAR(255) NOT NULL,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(255) NOT NULL UNIQUE,
senha_hash VARCHAR(255) NOT NULL,
nivel_acesso VARCHAR(20) NOT NULL DEFAULT 'usuario'
CHECK (nivel_acesso IN ('usuario','admin','moderador','admin_system')),
ativo BOOLEAN NOT NULL DEFAULT TRUE,
verificado_email BOOLEAN NOT NULL DEFAULT FALSE,
criado_em TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
atualizado_em TIMESTAMP
)
");
$pdo->exec('CREATE INDEX IF NOT EXISTS idx_usuarios_email ON usuarios (email)');
$pdo->exec('CREATE INDEX IF NOT EXISTS idx_usuarios_username ON usuarios (username)');
} else {
$pdo->exec("
CREATE TABLE IF NOT EXISTS usuarios (
uuid CHAR(36) NOT NULL PRIMARY KEY,
nome_completo VARCHAR(255) NOT NULL,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(255) NOT NULL UNIQUE,
senha_hash VARCHAR(255) NOT NULL,
nivel_acesso VARCHAR(20) NOT NULL DEFAULT 'usuario',
ativo TINYINT(1) NOT NULL DEFAULT 1,
verificado_email TINYINT(1) NOT NULL DEFAULT 0,
criado_em DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
atualizado_em DATETIME,
INDEX idx_email (email),
INDEX idx_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
");
}
},
'down' => function (PDO $pdo): void {
$pdo->exec('DROP TABLE IF EXISTS usuarios');
},
];
Passo 7 — Seeder
Defina ADMIN_EMAIL e ADMIN_PASSWORD no .env antes de rodar em produção.
Arquivo: src/Modules/Usuario/Database/Seeders/001_admin_user.php
<?php
return function (PDO $pdo): void {
$email = $_ENV['ADMIN_EMAIL'] ?? '[email protected]';
$senha = $_ENV['ADMIN_PASSWORD'] ?? 'Admin@123';
$nome = $_ENV['ADMIN_NAME'] ?? 'Administrador';
// Não insere se o e-mail já existir
$stmt = $pdo->prepare('SELECT 1 FROM usuarios WHERE email = :email LIMIT 1');
$stmt->execute([':email' => $email]);
if ($stmt->fetchColumn()) {
echo " ⊘ Admin já existe: {$email}\n";
return;
}
$uuid = \Ramsey\Uuid\Uuid::uuid4()->toString();
$hash = password_hash($senha, PASSWORD_ARGON2ID);
$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
$ativo = $driver === 'pgsql' ? 'TRUE' : '1';
$username = $_ENV['ADMIN_USERNAME'] ?? 'admin';
$pdo->prepare("
INSERT INTO usuarios (uuid, nome_completo, username, email, senha_hash, nivel_acesso, ativo, verificado_email, criado_em)
VALUES (:uuid, :nome, :username, :email, :hash, 'admin_system', {$ativo}, {$ativo}, NOW())
")->execute([':uuid' => $uuid, ':nome' => $nome, ':username' => $username, ':email' => $email, ':hash' => $hash]);
echo " ✔ Admin criado: {$email} / {$senha}\n";
echo " ⚠ TROQUE A SENHA EM PRODUÇÃO!\n";
};
Passo 8 — connection.php
Arquivo: src/Modules/Usuario/Database/connection.php
<?php
// 'core' usa DB_* do .env | 'modules' usa DB2_* | 'auto' detecta automaticamente
return 'core';
Passo 9 — Rotas
Arquivo: src/Modules/Usuario/Routes/web.php
<?php
use Src\Modules\Usuario\Controllers\UsuarioController;
/** @var \Src\Kernel\Contracts\RouterInterface $router */
$router->post('/api/usuarios', [UsuarioController::class, 'criar']); // Registrar
$router->get('/api/usuarios', [UsuarioController::class, 'listar']); // Listar (admin)
$router->get('/api/usuarios/{uuid}', [UsuarioController::class, 'buscar']); // Buscar por UUID
$router->put('/api/usuarios/{uuid}', [UsuarioController::class, 'atualizar']); // Atualizar
$router->delete('/api/usuarios/{uuid}', [UsuarioController::class, 'deletar']); // Deletar
Rotas disponíveis
| Método | Rota | Descrição |
|---|---|---|
| POST | /api/usuarios | Registrar novo usuário |
| GET | /api/usuarios | Listar usuários |
| GET | /api/usuarios/{uuid} | Buscar por UUID |
| PUT | /api/usuarios/{uuid} | Atualizar dados |
| DELETE | /api/usuarios/{uuid} | Deletar usuário |
Com todos os arquivos criados, rode as migrations e o seeder para inicializar o banco:
php vupi migrate --seedDepois teste o registro com:
curl -X POST http://localhost/api/usuarios \
-H "Content-Type: application/json" \
-d '{"nome_completo":"João Silva","username":"joao.silva","email":"[email protected]","senha":"Senha@123"}'