Try Live
Add Docs
Rankings
Pricing
Docs
Install
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
LabSIS KIT
https://github.com/iurygdeoliveira/labsis-saas-kit-v4
Admin
A starter kit for developing SaaS applications using the TALL stack (Tailwind, Alpine.js, Laravel,
...
Tokens:
87,106
Snippets:
659
Trust Score:
8.7
Update:
2 months ago
Context
Skills
Chat
Benchmark
72.5
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# LabSIS SaaS KIT V4 O LabSIS KIT é um Starter Kit completo para desenvolvimento de aplicações SaaS (Software as a Service) utilizando a stack TALL (Tailwind, Alpine.js, Laravel, Livewire) e Filament v4. O projeto fornece uma base sólida e rica em recursos para acelerar o desenvolvimento de novas aplicações, seguindo as melhores práticas e convenções do ecossistema Laravel. A arquitetura é multi-tenant com banco de dados único, onde cada tenant possui seus próprios usuários, dados e permissões isoladas. O kit implementa funcionalidades essenciais como gestão de tenants, sistema hierárquico de roles e permissões (Admin global, Owner por tenant, User por tenant), gestão de mídias com URLs assinadas, autenticação de dois fatores (2FA), login unificado para múltiplos painéis, registro de logs de autenticação em MongoDB, suspensão de usuários e fluxo de aprovação de novos cadastros. A stack tecnológica inclui PHP 8.5, Laravel 12, Filament v4, Livewire v3, e integração com MinIO/S3 para armazenamento de arquivos. --- ## Modelo User - Gestão de Usuários e Acesso a Tenants O modelo User implementa interfaces do Filament para multi-tenancy e controle de acesso aos painéis. Ele gerencia relacionamentos com tenants, roles por tenant, autenticação 2FA e verificações de estado (suspenso, aprovado). ```php <?php use App\Models\User; use App\Models\Tenant; use App\Enums\RoleType; // Criar um novo usuário $user = User::create([ 'name' => 'João Silva', 'email' => 'joao@empresa.com.br', 'password' => Hash::make('senha123'), 'is_approved' => true, 'is_suspended' => false, ]); // Verificar se o usuário pode acessar um painel específico $panel = Filament::getPanel('admin'); if ($user->canAccessPanel($panel)) { // Usuário tem acesso ao painel admin (requer role Admin) } // Verificar se o usuário é Owner de um tenant $tenant = Tenant::find(1); if ($user->isOwnerOfTenant($tenant)) { // Usuário é proprietário deste tenant } // Obter roles do usuário em um tenant específico $roles = $user->getRolesForTenant($tenant); // Retorna Collection com roles: Owner, User, etc. // Atribuir role a um usuário em um tenant $ownerRole = Role::where('name', RoleType::OWNER->value) ->where('team_id', $tenant->id) ->first(); $user->assignRoleInTenant($ownerRole, $tenant); // Obter todos os tenants ativos do usuário $tenants = $user->getTenants($panel); // Retorna Collection de Tenants onde is_active = true // Verificar estados do usuário $isSuspended = $user->isSuspended(); // true/false $isApproved = $user->isApproved(); // true/false ``` --- ## Modelo Tenant - Gestão de Organizações Multi-Tenant O modelo Tenant representa uma organização/empresa no sistema. Cada tenant possui seus próprios usuários associados e mídias, com isolamento completo de dados. ```php <?php use App\Models\Tenant; use App\Models\User; use Illuminate\Support\Str; // Criar um novo tenant $tenant = Tenant::create([ 'uuid' => (string) Str::uuid(), 'name' => 'Empresa ABC', 'is_active' => true, ]); // Obter todos os usuários do tenant $users = $tenant->users()->get(); // Associar um usuário ao tenant $user = User::find(1); $tenant->users()->attach($user->id); // Obter mídias do tenant $medias = $tenant->mediaItems()->get(); // Verificar se tenant está ativo if ($tenant->is_active) { // Tenant está ativo e pode ser acessado } // Buscar tenant por UUID (usado em rotas) $tenant = Tenant::where('uuid', $uuid)->first(); ``` --- ## Enum RoleType - Sistema de Papéis Hierárquico O enum RoleType define os três níveis de acesso do sistema: Admin (global), Owner (por tenant) e User (por tenant). Inclui métodos para criação dinâmica de roles. ```php <?php use App\Enums\RoleType; use App\Models\Role; // Tipos de roles disponíveis RoleType::ADMIN; // 'Admin' - Administrador global RoleType::OWNER; // 'Owner' - Proprietário de tenant RoleType::USER; // 'User' - Usuário comum // Obter label traduzido echo RoleType::ADMIN->getLabel(); // "Administrador" echo RoleType::OWNER->getLabel(); // "Proprietário" echo RoleType::USER->getLabel(); // "Usuário Comum" // Criar role Admin global RoleType::ensureGlobalRoles('web'); // Criar role Owner para um tenant específico $tenantId = 1; $ownerRole = RoleType::ensureOwnerRoleForTeam($tenantId, 'web'); // Cria ou retorna Role existente com team_id = 1 // Criar role User para um tenant específico $userRole = RoleType::ensureUserRoleForTeam($tenantId, 'web'); // Verificar role do usuário $user = User::find(1); if ($user->hasRole(RoleType::ADMIN->value)) { // Usuário é Admin global } ``` --- ## Enum Permission - Sistema de Permissões Granulares O enum Permission define as permissões básicas (VIEW, CREATE, UPDATE, DELETE) e gera permissões específicas por recurso usando o método `for()`. ```php <?php use App\Enums\Permission; // Permissões disponíveis Permission::VIEW; // 'view' Permission::CREATE; // 'create' Permission::UPDATE; // 'update' Permission::DELETE; // 'delete' // Obter label traduzido echo Permission::VIEW->getLabel(); // "Visualizar" echo Permission::CREATE->getLabel(); // "Criar" echo Permission::UPDATE->getLabel(); // "Editar" echo Permission::DELETE->getLabel(); // "Apagar" // Gerar permissão para recurso específico $mediaView = Permission::VIEW->for('media'); // "media.view" $usersCreate = Permission::CREATE->for('users'); // "users.create" $authLogDelete = Permission::DELETE->for('authentication-log'); // "authentication-log.delete" // Verificar permissão do usuário $user = auth()->user(); if ($user->can(Permission::UPDATE->for('media'))) { // Usuário pode editar mídias } // Criar todas as permissões para um recurso $resources = ['media', 'users', 'authentication-log']; foreach ($resources as $resource) { foreach (Permission::cases() as $permission) { \Spatie\Permission\Models\Permission::firstOrCreate([ 'name' => $permission->for($resource), 'guard_name' => 'web', ]); } } ``` --- ## MediaService - Gestão de Mídias e Uploads O MediaService encapsula a lógica de criação, atualização e consulta de mídias. Suporta tanto upload de arquivos quanto URLs de vídeos externos (YouTube). ```php <?php use App\Services\MediaService; use App\Models\MediaItem; use Illuminate\Http\UploadedFile; $mediaService = app(MediaService::class); // Criar mídia a partir de upload de arquivo $file = $request->file('document'); $mediaItem = $mediaService->createFromUpload( file: $file, name: 'Relatório Mensal', collection: 'media' ); // Retorna MediaItem com arquivo anexado no S3 // Criar mídia a partir de URL de vídeo (YouTube) $mediaItem = $mediaService->createFromVideoUrl( videoUrl: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', name: 'Tutorial de Introdução', collection: 'media' ); // Atualizar mídia existente $updatedMedia = $mediaService->updateMedia($mediaItem, [ 'name' => 'Novo Nome do Arquivo', 'video' => 'https://youtube.com/watch?v=xyz123', // ou null para arquivo 'media' => $uploadedFile, // opcional: novo arquivo ]); // Obter URL da mídia (assinada para arquivos S3) $url = $mediaService->getMediaUrl($mediaItem); // Para arquivos: URL temporária assinada com SigV4 // Para vídeos: URL do YouTube diretamente // Obter caminho interno no storage $path = $mediaService->getMediaPath($mediaItem); // Retorna null para vídeos externos // Identificar tipo da mídia $type = $mediaService->getMediaType($mediaItem); // Retorna: 'image', 'audio', 'video', 'video_url', 'document' ou 'unknown' ``` --- ## Model MediaItem - Entidade de Mídia com Spatie Media Library O MediaItem é o modelo de domínio para mídias cadastradas. Utiliza Spatie Media Library para anexar arquivos ao S3 e oferece atributos derivados para a UI. ```php <?php use App\Models\MediaItem; use App\Models\Tenant; // Criar MediaItem para upload $mediaItem = MediaItem::create([ 'uuid' => \Illuminate\Support\Str::uuid(), 'name' => 'Imagem do Produto', 'video' => null, // null para arquivos, URL para vídeos 'tenant_id' => $tenant->id, ]); // Anexar arquivo via Spatie Media Library $mediaItem->addMedia($uploadedFile) ->usingFileName($uploadedFile->getClientOriginalName()) ->toMediaCollection('media'); // Acessar atributos derivados echo $mediaItem->file_type; // "Imagem", "Vídeo", "Áudio", "Documento" echo $mediaItem->human_size; // "2.5 MB" echo $mediaItem->image_url; // URL assinada para preview echo $mediaItem->mime_type; // "image/jpeg" echo $mediaItem->size; // 2621440 (bytes) // Relacionamento com Video (para URLs externas) $videoInfo = $mediaItem->video()->first(); if ($videoInfo) { echo $videoInfo->title; // "Título do Vídeo" echo $videoInfo->duration; // 180 (segundos) echo $videoInfo->provider; // "youtube" } // Relacionamento com Tenant $tenant = $mediaItem->tenant; // Obter mídia do Spatie $spatieMedia = $mediaItem->getFirstMedia('media'); $signedUrl = $spatieMedia->getUrl(); // URL temporária assinada automaticamente ``` --- ## UserPolicy - Autorização com Hierarquia de Acesso As policies implementam lógica de autorização hierárquica: Admin tem acesso total, Owner tem acesso total no tenant, e User depende de permissões específicas. ```php <?php use App\Policies\UserPolicy; use App\Models\User; use App\Enums\RoleType; use App\Enums\Permission; use Filament\Facades\Filament; // A policy é registrada automaticamente pelo Laravel // Uso em controllers: $this->authorize('view', $user); $this->authorize('update', $user); $this->authorize('delete', $user); // Uso com Gate facade use Illuminate\Support\Facades\Gate; if (Gate::allows('viewAny', User::class)) { // Pode listar usuários } // A hierarquia funciona assim: // 1. Admin global: sempre true (verificado em before()) // 2. Owner do tenant atual: sempre true (verificado em before()) // 3. Outros: verificam permissão específica // Exemplo de implementação interna da policy: class UserPolicy { public function before(User $user): ?bool { // Admin tem acesso total if ($user->hasRole(RoleType::ADMIN->value)) { return true; } // Owner tem acesso total no tenant atual $currentTenant = Filament::getTenant(); if ($currentTenant && $user->isOwnerOfTenant($currentTenant)) { return true; } return null; // Delega para métodos específicos } public function view(User $authUser, User $record): bool { // Verifica permissão 'users.view' return $authUser->can(Permission::VIEW->for('users')); } } ``` --- ## TeamSyncMiddleware - Sincronização de Contexto de Tenant O middleware sincroniza o contexto de tenant do Filament com o Spatie Permission, garantindo que as permissões sejam verificadas no tenant correto. ```php <?php // Configuração no UserPanelProvider.php use App\Http\Middleware\TeamSyncMiddleware; $panel->middleware([ TeamSyncMiddleware::class, ]); // O middleware funciona automaticamente: // 1. Verifica se está no painel 'user' // 2. Obtém o tenant da rota ou seleciona o primeiro ativo // 3. Sincroniza o team_id no SpatieTeamResolver // Uso manual do resolver (em casos específicos) use App\Tenancy\SpatieTeamResolver; $resolver = app(SpatieTeamResolver::class); // Definir contexto de tenant para verificações de permissão $resolver->setPermissionsTeamId($tenant->id); // Verificar permissão no contexto correto if ($user->can('media.view')) { // Permissão verificada no tenant atual } // Resetar para contexto global $resolver->setPermissionsTeamId(0); // Ou resetar completamente $resolver->setPermissionsTeamId(null); ``` --- ## Filament UserResource - CRUD de Usuários O UserResource implementa o CRUD completo de usuários no painel Filament, com suporte a multi-tenancy através do relacionamento `tenants`. ```php <?php use App\Filament\Resources\Users\UserResource; use App\Filament\Resources\Users\Pages\ListUsers; use App\Filament\Resources\Users\Pages\CreateUser; use App\Filament\Resources\Users\Pages\ViewUser; use App\Filament\Resources\Users\Pages\EditUser; // O resource é registrado automaticamente no painel // Rotas geradas: // GET /user/{tenant}/users -> ListUsers (index) // GET /user/{tenant}/users/create -> CreateUser // GET /user/{tenant}/users/{uuid} -> ViewUser // GET /user/{tenant}/users/{uuid}/edit -> EditUser // Configurações do Resource: class UserResource extends Resource { protected static ?string $model = User::class; // Habilita multi-tenancy protected static ?string $tenantOwnershipRelationshipName = 'tenants'; // Usa UUID nas rotas em vez de ID public static function getRecordRouteKeyName(): string { return 'uuid'; } } // Schema do formulário (UserForm.php) // - Nome (text input) // - Email (email input, unique) // - Senha (password, hashed) // - Status de suspensão (toggle) // - Motivo da suspensão (textarea) // Tabela (UsersTable.php) // - Nome com avatar // - Email // - Roles no tenant atual // - Status (ativo/suspenso) // - Data de criação ``` --- ## Filament MediaResource - CRUD de Mídias O MediaResource gerencia arquivos e vídeos com suporte a upload para S3/MinIO, preview de conteúdo e URLs temporárias assinadas. ```php <?php use App\Filament\Resources\Media\MediaResource; use Filament\Forms\Components\SpatieMediaLibraryFileUpload; // Rotas geradas: // GET /user/{tenant}/media -> ListMedia // GET /user/{tenant}/media/create -> CreateMedia // GET /user/{tenant}/media/{record} -> ViewMedia // Configuração do upload no formulário: SpatieMediaLibraryFileUpload::make('media') ->disk('s3') // Armazena no MinIO/S3 ->visibility('private') // Arquivos privados ->downloadable(true) // Permite download ->openable(true) // Permite abrir no navegador ->previewable(true) // Preview de imagens ->acceptedFileTypes([ 'image/*', 'audio/*', 'video/*', 'application/pdf', ]) ->maxFiles(1) ->maxSize(5120) // 5MB máximo ->collection('media'); // Alternativa: URL de vídeo externo TextInput::make('video.url') ->label('URL do Vídeo (YouTube)') ->url() ->disabled(fn ($get) => !empty($get('media'))); // Visualização com MediaAction (filament-media-action) use HugoMyb\FilamentMediaAction\Tables\Actions\MediaAction; MediaAction::make('open') ->media(fn ($record) => $record->image_url ?? $record->video?->url) ->visible(fn ($record) => $record->file_type !== 'Desconhecido'); ``` --- ## Filament TenantResource - CRUD de Tenants O TenantResource permite administradores gerenciarem organizações/tenants do sistema, incluindo ativação/desativação. ```php <?php use App\Filament\Resources\Tenants\TenantResource; use App\Models\Tenant; // Disponível apenas no painel Admin // Rotas geradas: // GET /admin/tenants -> ListTenants // GET /admin/tenants/create -> CreateTenant // GET /admin/tenants/{record} -> ViewTenant // GET /admin/tenants/{record}/edit -> EditTenant // Formulário (TenantForm.php) TextInput::make('name') ->label('Nome da Organização') ->required() ->maxLength(255) ->unique(ignoreRecord: true); Toggle::make('is_active') ->label('Ativo') ->default(true); // Tabela (TenantsTable.php) // - Nome do tenant // - Status (ativo/inativo) // - Quantidade de usuários // - Data de criação // Infolist (TenantInfolist.php) // - Informações detalhadas // - Lista de usuários vinculados // - Estatísticas de uso ``` --- ## Registro de Usuários com Criação de Tenant O fluxo de registro cria automaticamente um tenant e atribui a role Owner ao usuário. O cadastro fica suspenso até aprovação do admin. ```php <?php use App\Filament\Pages\Auth\Register; use App\Models\User; use App\Models\Tenant; use App\Events\UserRegistered; // Formulário de registro personalizado: // - Nome completo // - Email // - Senha (confirmação) // - Nome do Tenant (organização) // Fluxo de criação: protected function handleRegistration(array $data): Model { // 1. Criar usuário (suspenso por padrão) $user = User::create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => $data['password'], 'is_suspended' => true, // Aguarda aprovação 'is_approved' => false, ]); // 2. Criar tenant $tenant = Tenant::create([ 'name' => $data['tenant_name'], 'is_active' => true, ]); // 3. Associar usuário ao tenant $user->tenants()->attach($tenant->id); // 4. Atribuir role Owner $ownerRole = Role::firstOrCreate([ 'name' => 'Owner', 'team_id' => $tenant->id, ]); $user->rolesWithTeams()->syncWithoutDetaching([ $ownerRole->id => ['team_id' => $tenant->id], ]); // 5. Disparar evento para notificar admin event(new UserRegistered($user)); return $user; } // Após aprovação pelo admin: // - is_approved = true // - is_suspended = false // - email_verified_at = now() // - Evento UserApproved é disparado // - Email de aprovação é enviado ao usuário ``` --- ## Login Unificado com Verificações de Estado O sistema de login verifica suspensão e aprovação antes de permitir acesso, redirecionando para páginas apropriadas. ```php <?php use App\Filament\Pages\Auth\Login; use Filament\Facades\Filament; // Login customizado com verificações adicionais: public function authenticate(): ?LoginResponse { $data = $this->form->getState(); $authGuard = Filament::auth(); // Verificar credenciais $user = $authGuard->getProvider()->retrieveByCredentials( $this->getCredentialsFromFormData($data) ); if (!$user) { $this->throwFailureValidationException(); } // Verificar suspensão if ($user->isSuspended()) { $authGuard->login($user); $this->redirect(route('filament.auth.account-suspended')); return null; } // Verificar aprovação if (!$user->isApproved()) { $authGuard->login($user); $this->redirect(route('filament.auth.verification-pending')); return null; } // Continuar com autenticação padrão (inclui 2FA se habilitado) return parent::authenticate(); } // Páginas de estado: // - AccountSuspended: conta suspensa pelo admin // - VerificationPending: aguardando aprovação do cadastro ``` --- ## Configuração de Painéis Filament O BasePanelProvider centraliza configurações compartilhadas entre os painéis Admin e User, incluindo plugins, middleware e 2FA. ```php <?php use App\Providers\Filament\BasePanelProvider; use App\Providers\Filament\AdminPanelProvider; use App\Providers\Filament\UserPanelProvider; // BasePanelProvider - Configurações compartilhadas: $panel ->spa() // Single Page Application ->databaseTransactions() // Transações automáticas ->profile() // Página de perfil ->multiFactorAuthentication( // 2FA com app authenticator AppAuthentication::make()->recoverable() ) ->middleware([ RedirectToProperPanelMiddleware::class, EnsureSecurityHeaders::class, ]); // AdminPanelProvider - Painel administrativo global: // Acesso: /admin // Recursos: Tenants, Todos os usuários, Logs de autenticação // Não possui tenant (global) $panel->tenant(null); // UserPanelProvider - Painel do usuário por tenant: // Acesso: /user/{tenant-uuid}/... // Recursos: Usuários do tenant, Mídias, Logs // Multi-tenant habilitado: $panel ->tenant(Tenant::class, slugAttribute: 'uuid') ->tenantMenu(true) // Menu de seleção de tenant ->middleware([ TeamSyncMiddleware::class, // Sincroniza permissões ]); ``` --- ## UserSeeder - Configuração Inicial do Sistema O UserSeeder cria usuários de demonstração, tenants e configura permissões por recurso e role. ```php <?php use Database\Seeders\UserSeeder; // Executar seeder: // php artisan db:seed --class=UserSeeder // Usuários criados: // 1. Admin (global) // Email: admin@labsis.dev.br // Senha: mudar123 // Role: Admin (global, team_id = 0) // Acesso: /admin // 2. Sicrano // Email: sicrano@labsis.dev.br // Senha: mudar123 // Tenants: Tenant A (Owner), Tenant B (User) // Acesso: /user // 3. Beltrano // Email: beltrano@labsis.dev.br // Senha: mudar123 // Tenants: Tenant A (User), Tenant B (Owner) // Acesso: /user // Permissões criadas por recurso: $resources = ['media', 'users', 'authentication-log']; // Cada recurso recebe: view, create, update, delete // Atribuição de permissões: // - Admin: todas as permissões (global) // - Owner: todas as permissões (no tenant específico) // - User: permissões definidas manualmente via UI // Comando para reset completo: // composer reset // (executa migrate:fresh, seeders, build assets, testes) ``` --- ## Resumo de Casos de Uso e Padrões de Integração O LabSIS KIT é ideal para aplicações SaaS que necessitam de isolamento de dados por organização (multi-tenancy), controle de acesso granular baseado em papéis e permissões, e gestão de mídias com armazenamento seguro. Os principais casos de uso incluem: sistemas de gestão empresarial onde cada cliente possui seu ambiente isolado, plataformas educacionais com múltiplas instituições, marketplaces com vendedores independentes, e qualquer aplicação que necessite de painéis administrativos diferenciados (admin global vs usuário por organização). A integração com novos recursos segue o padrão estabelecido: criar model com trait `UuidTrait`, implementar policy com hierarquia Admin > Owner > User, criar Filament Resource com `tenantOwnershipRelationshipName` configurado, adicionar permissões no `UserSeeder`, e registrar no provider do painel apropriado. O sistema de permissões utiliza o Spatie Laravel Permission com suporte a teams, onde `team_id` corresponde ao `tenant_id`, permitindo que o mesmo usuário tenha diferentes papéis em diferentes organizações. Para mídias, basta utilizar o `MediaService` ou implementar `HasMedia` do Spatie com o disco `s3` configurado para MinIO, garantindo URLs temporárias assinadas automaticamente.