tjdft / laravel
Pacote unificado para desenvolvimento de aplicações Laravel no TJDFT.
Installs: 28
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
pkg:composer/tjdft/laravel
Requires
- fakerphp/faker: ^1.24
- livewire/livewire: ^4.1
- postare/blade-mdi: ^1.1
- robsontenorio/mary: ^2.6
- sentry/sentry-laravel: ^4.20
- socialiteproviders/keycloak: ^5.3
- technikermathe/blade-lucide-icons: ^3.137
- webonyx/graphql-php: ^15.30
Requires (Dev)
- orchestra/testbench: ^10.9
- pestphp/pest: ^4.3
README
Introdução
Pacote unificado para desenvolvimento de aplicações Laravel no TJDFT.
Autenticação e Autorização:
- Fluxo de autenticação com Keycloak.
- Funcionalidade de Impersonate.
- Gerenciamento de Permissões.
Integração com o RH:
- Classe base para consulta na API RH.
Integração com o Sentry
- Pré-configuração do Sentry para monitoramento de erros.
Interface:
- Telas de gerenciamento de permissions e impersonate.
- Tela para desambiguação para pessoas com múltiplos vínculos no RH.
- Tela de erro padronizada.
- Pacote extra de ícones.
- Utilitários
NumeroeDatapara diversas formatações em tela. - Trait
HasSearchAnypara busca simplificada em múltiplos campos. - Trait
WithPaginationAndResetpara paginação simplificada com Livewire. - Arquivos de translation em
pt_BR.
Funcionalidades adicionais:
- Classes de exception padronizadas.
- Utilitários para testes automatizados.
- Ativa extensões úteis do PostgreSQL.
Instalação
Utilize o Instalador Laravel do TJDFT para criar uma nova aplicação com este pacote pré-configurado.
Ou, execute a instalação manual
Adicione o pacote.
composer require tjdft/laravel
Instale maryUI incluído no pacote.
php artisan mary:install --yarn
Altere o idioma em .env
APP_LOCALE=pt_BR
Adicione as configurações de middleware e exceptions em bootstrap/app.php.
use TJDFT\Laravel\Exceptions\ExceptionHandler;
// ...
->withMiddleware(function (Middleware $middleware) {
// Para funcionar no Openshift e outros ambientes com proxies reversos
$middleware->trustProxies(at: '*');
})
->withExceptions(function (Exceptions $exceptions) {
// Tratamento personalizado de exceções
ExceptionHandler::register($exceptions);
})
Ajuste tests/Pest.php.
pest()->extend(Tests\TestCase::class)->in('Feature', 'Unit');
Ajuste tests/TestCase.php.
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use TJDFT\Laravel\Traits\TestUtis;
abstract class TestCase extends BaseTestCase
{
// Helpers para testes automatizados
use TestUtis;
// Equivale ao `setUp()` do PHPUnit
protected function boot(): void
{
// Adicione aqui qualquer coisa que precise ser executada antes dos testes.
}
}
Crie as novas variáveis de ambiente em .env.
# Sentry
TJDFT_SENTRY_LARAVEL_DSN=
# API RH
TJDFT_POLVO_API_URL=https://<URL_API_RH>/graphql
TJDFT_POLVO_AUTH_URL=https://<URL_KEYCLOAK>/auth/realms/<NOME_REALM>/protocol/openid-connect/token
TJDFT_POLVO_CLIENT_ID=<NOME_CLIENT>
TJDFT_POLVO_CLIENT_SECRET=<SEGREDO>
TJDFT_POLVO_CACHE_TTL='1 hour'
# Keycloak
TJDFT_KEYCLOAK_BASE_URL=https://<URL_KEYCLOAK>/auth
TJDFT_KEYCLOAK_REALMS=<NOME_REALM>
TJDFT_KEYCLOAK_CLIENT_ID=<NOME_CLIENT>
TJDFT_KEYCLOAK_CLIENT_SECRET=<SEGREDO>
# Schema onde devem ser ativadas as extensões do PostgreSQL
# Use apenas se o schema principal da aplicação for diferente de `public`.
TJDFT_PGSQL_EXTENSIONS_SCHEMA=core
Ajuste a migration existente users.
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->uuid()->index()->nullable();
$table->string('login')->index();
$table->string('matricula')->nullable();
$table->string('cpf')->index()->nullable();
$table->string('nome');
$table->string('email')->nullable();
$table->string('foto')->nullable();
$table->json('localizacao')->nullable();
$table->string('rh_tipo')->nullable();
$table->string('rh_status')->nullable();
$table->timestamps();
$table->unique(['cpf', 'matricula']);
});
Rode as migrations.
# Esta ação destruirá e recriará o banco!
php artisan migrate:fresh --seed
Pronto!
Autenticação
Este pacote implementa o fluxo de autenticação via Keycloak para as rotas protegidas do sistema.
// Rotas protegidas
Route::middleware('auth')->group(function () {
Route::livewire('/paginas/create', 'pages::paginas.criar');
// ...
});
Para o logout de usuários utilize a rota /auth/logout/keycloak.
<x-button title="Sair" link="/auth/logout/keycloak" no-wire-navigate />
Usuários com mais de um vínculo no RH serão redirecionados automaticamente para a rota /auth/perfil.
Ex: Se o usuário possui vínculo de Pensão Alimentícia e Servidor, então ele deve selecionar um perfil para acesso.
<!-- Opção em menu -->
<x-menu-item title="Alterar perfil" link="/auth/perfil" />
Consulte o tópico Autorização para mais detalhes sobre permissões.
pubfic function mount(): void
{
// Lança uma exceção 403 se o usuário não tiver a permissão
auth()->user()->authorize("comprovante.visualizar");
}
<!-- Se não tem a permissão, oculta o menu -->
<x-menu-item title="Criar Página" link="/paginas/create" :hidden="auth()->user()->cannot('paginas.criar')" />
Impersonate
Adicione o trait HasImpersonate no model User.
use TJDFT\Laravel\Traits\HasImpersonate;
// ...
class User extends Authenticatable
{
use HasImpersonate;
//...
}
Utilize a rota /auth/impersonate para a funcionalidade de personificação de usuários.
Funcionalidade disponível apenas para usuários com a permissão impersonate.
<x-menu-item title="Personificar" link="/auth/impersonate" :hidden="auth()->user()->cannot('impersonate')" />
Adicione no arquivo de layout o aviso de personificação, quando em uso.
<!-- resources/views/layouts/app.blade.php -->
<body>
<!-- Aviso de Impersonate -->
<livewire:tjdft::impersonating />
<div>
Menu superior ...
</div>
<div>
Conteúdo da página ...
</div>
</body>
API RH
Este pacote possui a classe base para consultas na API RH.
class PolvoService { ... }
Crie serviços de consulta baseados na classe TJDFT\Laravel\Services\PolvoService.
namespace App\Services;
use Illuminate\Support\Collection;
use TJDFT\Laravel\Services\PolvoService;
class FeriasPolvoService extends PolvoService
{
public function porMatricula(string $matricula): Collection
{
$query = "{ ... query GraphQL ... }";
// Método herdado da classe PolvoService
$response = $this->graphql($query);
return collect($response['data']['servidor']['dadosFuncionais']['ferias']['data'] ?? []);
}
}
Todas as consultas GraphQL tem um prazo de cache padrão de 1 hora.
TJDFT_POLVO_CACHE_TTL='1 hour'.
Pra definir um prazo específico apenas para algumas consultas, utilize o método lembrar().
$ferias = new FeriasPolvoService()->lembrar('1 day')->porMatricula("12345");
Para desabilitar o cache em consultas específicas, utilize o método semCache().
$ferias = new FeriasPolvoService()->semCache()->porMatricula("12345");
Para desabilitar completamente o cache tem todas as consultas GraphQL ajuste a variável de ambiente.
TJDFT_POLVO_CACHE_TTL='0'
Pesquisa
Adicione o trait HasSearchAny nos models pesquisáveis.
use TJDFT\Laravel\Traits\HasSearchAny;
// ...
class Rubrica extends Model
{
use HasSearchAny;
//...
}
// Pesquisa em múltiplos campos, tratando acentuação e case sensitive automaticamente.
Rubrica::query()->searchAny(['nome', 'sigla'], $valor)->get();
// Funciona também em colunas JSON
Espelho::query()->searchAny(['dados->nome', 'dados->endereco'], $valor)->get();
// Considere criar indices nas colunas JSON para melhorar a performance
DB::statement("CREATE INDEX idx_meu_indice ON minha_tabela USING gin (immutable_unaccent(minha_coluna->>'meu_campo') gin_trgm_ops)");
Número
use TJDFT\Laravel\Support\Numero;
Numero::percentual('0.2567') # 25,67%
Numero::percentual('0.2567', 1) # 25,6%
Numero::truncado('14.6789') # 14.67
Numero::truncado('14.6789', 3) # 14.678
Numero::formatado('1234.56') # 1.234,56
Numero::moeda('1234.56') # R$ 3.201,45
Numero::cpf('12345678901') # 123.456.789-01
Numero::cnpj('12345678000195') # 12.345.678-0001/95
Data
use TJDFT\Laravel\Support\Data;
Data::formatada("2025-04-12") # 12/04/2025
Data::formatada(null, "-") # Se for nula mostra "-"
Data::formatada($carbon, "-") # Funciona também com objetos Carbon.
Paginação
Utilize o trait WithPaginationAndReset nas telas com tabelas.
Quando os filtros forem alterados, a paginação será resetada automaticamente.
use TJDFT\Laravel\Traits\WithPaginationAndReset;
// ...
new class extends Component {
use WithPaginationAndReset;
// ...
}
Limpa propriedades de filtro e resetar paginação.
<!-- Invoca manualmente o reset de paginação e propriedades de filtro -->
<x-button label="Limpar" wire:click="clear()" />
Exceptions
Utilize a classe AppException na lógica de negócio para automaticamente exibir um toast do maryUI.
use TJDFT\Laravel\Exceptions\AppException;
// ...
if ($consignacao->status_id === Status::FINALIZADA) {
throw new AppException("Este contrato não pode ser alterado.");
}
Ícones
Este pacote inclui um conjunto extra de ícones para utilização nos componentes do maryUI.
<!-- Hero Icons possuem prefixo "o-" -->
<x-button label="Salvar" icon="o-check" />
<!-- Lucide Icons possuem prefixo "lucide." -->
<x-button label="Consulta" icon="lucide.users" />
<!-- MDI Icons possuem prefixo "mdi." -->
<x-button label="Contato" icon="mdi.whatsapp" />
Autorização
Utilize a rota /auth/permissions para acessar o gerenciamento de permissões.
<x-menu-item title="Permissões" link="/auth/permissions" :hidden="auth()->user()->cannot('permissoes.gerenciar')" />
Adicione o trait HasGrant no model User.
use TJDFT\Laravel\Traits\HasGrant;
// ...
class User extends Authenticatable
{
use HasGrant;
//...
}
Estas são as roles e permissions iniciais registradas automaticamente pelo pacote.
// Permissão master
Permission::create([
'name' => 'permissoes.gerenciar',
'description' => 'Permissões / Gerenciar',
]);
// Permissão de impersonate
Permission::create([
'name' => 'impersonate',
'description' => 'Impersonate',
]);
// Role admin
Role::create([
'name' => 'admin',
'description' => 'Administrador'
])->givePermissionTo(['permissoes.gerenciar', 'impersonate']);
EXEMPLO: authorize()
public function mount(): void
{
// Lança uma exceção 403 se o usuário não tiver a permissão
auth()->user()->authorize("comprovante-rendimentos.visualizar");
}
EXEMPLO: can()
// Se tem a permissão, mostra o aviso
@if(auth()->user()->can('consignacao.portabilidade'))
<div>Disponível para portabilidade</div>
@endif
EXEMPLO: cannot()
<!-- Se não tem a permissão, oculta o menu -->
<x-menu-item title="Criar Página" link="/paginas/create" :hidden="auth()->user()->cannot('paginas.criar')" />
EXEMPLO: crie outras roles e permissions na sua aplicação.
// database/seeders/PermissionsSeeder.php
use Illuminate\Database\Seeder;
use TJDFT\Laravel\Models\Permission;
use TJDFT\Laravel\Models\Role;
// ...
class PermissionsSeeder extends Seeder
{
public function run(): void
{
// Permissão inicial criada pelo pacote
if (Permission::where('name', '<>', 'permissoes.gerenciar')->count()) {
return;
}
// Processar comprovantes
Permission::create([
'name' => 'comprovante.processar',
'description' => 'Comprovantes de Rendimentos / Processar',
]);
// Visualizar comprovantes
Permission::create([
'name' => 'comprovante.visualizar',
'description' => 'Comprovantes de Rendimentos / Visualizar',
]);
// FUNCIONÁRIO tem permissão apenas para visualizar
Role::create([
'name' => 'funcionario',
'description' => 'Funcionário'
])->givePermissionTo([
'comprovante.visualizar',
]);
// ADMIN tem todas as permissões
// A role `admin` já é criada automaticamente pelo pacote
Role::firstWhere('name', 'admin')->givePermissionTo(Permission::all());
// Defina os administradores iniciais do sistema
User::create([
'cpf' => '0123456789',
'matricula' => '123456',
'login' => 't123456',
'nome' => 'Maria Silva'
])->assignRole('admin');
// Note que é inviável atribuir previamente as roles para milhares de `funcionários`.
// Confira o exemplo de roles/permissions dinâmicas abaixo.
}
}
EXEMPLO: lógica personalizada para definir dinamicamente roles/permissions.
// app/Actions/AtualizarPermissionsLoginAction.php
<?php
namespace App\Actions;
use App\Models\User;
/**
* Esta classe é chamada automaticamente pelo pacote `tjdft/laravel` após o login do usuário.
* Baseado nos dados do usuário, defina uma lógica para atribuição de roles.
*/
class AtualizarPermissionsLoginAction
{
public function __construct(private User $user)
{
}
public function execute(): void
{
// Exemplo: se é um `SERVIDOR`, atribua a role 'funcionario'.
if ($this->user->rh_tipo === 'SERVIDOR') {
$this->user->assignRole('funcionario');
}
}
}
Adicione PermissioSeeder aos seeders da aplicação.
// database/seeders/DatabaseSeeder
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$this->call([
// ...
PermissionsSeeder::class,
]);
}
}
Rode as migrations.
# Esta ação destruirá e recriará o banco!
php artisan migrate:fresh --seed
Testes
EXEMPLO: login()
// Cria e autentica um usuário aleatoriamente
$this->login();
// Cria e autentica um usuário com permissão específica
$this->login(permission: 'comprovante.visualizar');
// Cria e autentica um usuário com múltiplas permissões
$this->login(permission: ['comprovante.visualizar', 'comprovante.gerenciar']);
// Autentica um usuário existente
$user = User::factory()->create();
$this->login($user);
EXEMPLO: login()
test('Usuários autenticados podem ver páginas secretas', function () {
// Dado que eu estou logado
// Então eu consigo ver a página
$this->get('/pagina-secreta')->assertOk();
// Não é necessário usar `$this->login()`
// Pois o `TestCase` já faz isso automaticamente antes de cada teste.
});
EXEMPLO: login()
test('Teste permissão', function () {
// Dado que eu tenho permissão básica
$this->login(permission: 'paginas.visualizar');
// Quando eu tentar editar a página, então verei um erro de acesso negado
$this->get('/paginas/99/edit')->assertForbiden();
// Dado que eu tenho permissão de gestão
$this->login(permission: 'paginas.gerenciar');
// Quando eu tentar editar a página, então eu consigo ver a página
$this->get('/paginas/99/edit')->assertOk();
});
EXEMPLO: logout()
test('Visitantes não podem ver páginas secretas.', function () {
// Dado que eu não estava logado
$this->logout();
// Quando eu tentar acessar uma rota protegida
// Então sou redirecionado para a página de login
$this->get('/pagina-secreta')->assertRedirect('/login');
});
EXEMPLO: assertPolvoQueryContains()
test('Consulta movimentações', function () {
// Quando eu definir o período e consultar
Livewire::test('pages::movimentacoes')
->set('data_inicio', '2020-07-20')
->set('data_fim', '2020-07-22')
->call('consultar');
// Então a query GraphQL que foi executada pelo PolvoService deve conter o período correto
$this->assertPolvoQueryContains('
movimentacoes(
periodo: {dataInicio: "2020-07-20" dataFim: "2020-07-22"}
');
});
EXEMPLO: assertPolvoQueryNotContains()
// Dado que eu estou visualizando a unidade `12345`
$this->get('/unidades/12345');
// Então query GraphQL que foi executada pelo PolvoService NÃO contém um trecho esperado
$this->assertPolvoQueryNotContains("localizacao (codigo: 'errado') ";
Fake graphQL
Este pacote expõe o endpoint /graphql-faker para simular respostas de APIs graphQL externas.
- Ajuste
phpunit.xml.
<env name="TJDFT_POLVO_API_URL" value="http://localhost:8080/graphql-faker"/>
- Obtenha o schema SDL original da API RH e salve como
tests/faker.graphql.
# Pode ser obtido executando este comando no terminal da API RH.
# Baixe o arquivo e copie o seu conteúdo para `tests/faker.graphql`
php artisan lighthouse:print-schema > schema.graphql
- Crie o arquivo
tests/faker.graphql.php
<?php
use Faker\Factory;
$faker = Factory::create();
/*
* Sobrescreve valores aleatórios do faker graphQL para casos específicos.
*
*/
/**
* EXEMPLO:
*
* Em algumas situações, é necessário que determinados campos tenham valores conhecidos ou "datas de início e fim" coerentes, para validação de regras.
* Pois, caso contrário, os testes podem falhar de maneira intermitente.
*/
return [
'CapacitacaoParticipante.aprovado' => true,
'Afastamento.dataInicio' => '2021-01-01',
'Afastamento.dataFim' => '2021-01-31',
];
Desenvolvimento local
Execute o clone na raiz da sua aplicação.
git clone git@github.com:tjdft/laravel.git packages/laravel
Adicione o repositório local no composer.json da aplicação.
composer config repositories.local '{"type": "path", "url": "/var/www/html/packages/laravel"}'
Instale a versão local do pacote.
composer require tjdft/laravel:@dev
Pronto!
Para voltar a utilizar a versão do Packagist.
composer config --unset repositories.local
composer require tjdft/laravel
Testes automatizados do pacote.
# Entre na pasta do pacote
cd /var/www/html/packages/laravel
# Instale as dependências
composer install
# Rode os testes
composer test
# Cobertura de código
composer test:coverage
