Introduces Filament management for site settings, services, portfolio cases, and testimonials with admin-only policies, validated media uploads, and deterministic seeding. Includes comprehensive design and task documentation for the implementation. Co-authored-by: Cursor <cursoragent@cursor.com>
4.5 KiB
Context
Fase 0 entregou Laravel 13, Filament 5 em /admin, design tokens, autenticação interna e pipeline CI. O repositório possui apenas home placeholder em routes/web.php. Esta change implementa a metade CMS da Fase 1 (SPEC §18): persistência e gestão Filament de conteúdo, sem rotas públicas.
Padrões existentes a seguir:
- Resources Filament com form/table separados (app/Filament/Resources/Users/)
- Policies admin-only (app/Policies/UserPolicy.php)
declare(strict_types=1);em todo PHP próprio
Goals / Non-Goals
Goals:
- Assessora admin gerencia settings, serviços, portfólio e depoimentos no Filament.
- Dados persistidos conforme SPEC §8.2, prontos para consumo pela change
build-public-site. - Feature tests locais em PostgreSQL (jsonb, constraints reais).
- Seed determinístico parcial para desenvolvimento e testes futuros.
Non-Goals:
- Rotas públicas, home, SEO, sitemap, snapshots visuais, briefing.
- Actions de publicação com auditoria (
PublishPortfolioCaseetc.) — adiadas até Fase 5. - Variantes responsivas de imagem, page builder, categorias dinâmicas.
Decisions
1. Publicação via published_at único
Decisão: published_at nullable timestamp é a única fonte de verdade. Scope published() no model (whereNotNull('published_at')). Sem coluna is_published.
Alternativa rejeitada: boolean is_published + published_at — viola SPEC §1.1 (não persistir status derivável).
2. Mídia em disco public local, S3 em produção
Decisão: uploads de capa/galeria/foto usam disco Laravel configurável via FILESYSTEM_DISK. Local: public + php artisan storage:link. Produção: s3. Path gerado com UUID/hash, nunca nome original.
Alternativa rejeitada: blobs no PostgreSQL — proibido por SPEC §6.4.
3. Validação de mídia centralizada
Decisão: classe em app/Support/ (ex.: PublicImageUploadRules) com allowlist MIME (image/jpeg, image/png, image/webp), extensões correspondentes, limite 10 MB, alt text obrigatório quando imagem presente. Reutilizada nos Resources Filament.
4. Sem Actions de domínio nesta fase
Decisão: Filament Resources salvam diretamente via Eloquent. Actions (PublishPortfolioCase, UnpublishPortfolioCase) só serão criadas quando auditoria (Fase 5) exigir efeito colateral transacional.
Rationale: SPEC §9.5 — não criar Action para CRUD sem regra adicional.
5. Auditoria de publicação adiada
Decisão: publicar/despublicar não grava audit_logs nesta change.
Desvio documentado: ADM-02 lista publicação como auditável, mas tabela audit_logs é Fase 5. Retomar em change de hardening.
6. Autorização admin-only para CMS
Decisão: Policies espelham UserPolicy — $user->isAdmin() para view/create/update/delete em conteúdo do site. Assistant recebe 403 em todos os Resources de conteúdo.
Referência: WEB-02 — "admin pode gerenciar; assistant não pode".
7. Site settings como singleton Filament Page
Decisão: model SiteSetting com método instance() (primeiro registro ou create default). Filament ManageSiteSettings page (não Resource de lista) — evita CRUD genérico de chave/valor (SPEC §WEB-06).
8. Slug automático a partir do título
Decisão: slug gerado no model (observer ou mutator) com Str::slug(), unique constraint no banco. Admin pode editar slug no form.
9. Navegação Filament
Decisão: grupo Conteúdo do site com sort order conforme SPEC §5.2:
- Configurações (singleton page)
- Serviços
- Portfólio
- Depoimentos
Risks / Trade-offs
| Risco | Mitigação |
|---|---|
| Uploads locais sem S3 em dev | Storage::fake() nos testes; documentar storage:link no README |
| Galeria ordenada complexa no Filament | Relation manager com sort_order e reorderable |
jsonb social_links difere SQLite |
phpunit.xml já migrado para PostgreSQL |
| Conteúdo criado sem rotas públicas | seed + testes Filament validam CMS; rotas vêm em build-public-site |
| Auditoria ausente em publicação | desvio explícito; retomar Fase 5 |
Migration Plan
- Rodar migrations em ordem:
site_settings→services→portfolio_cases+portfolio_images→testimonials. - Executar seed de conteúdo após migrations.
- Sem rollback de dados em produção (greenfield);
migrate:rollbacksuportado em dev.
Open Questions
- Nenhuma bloqueante. Fontes tipográficas finais da home ficam para
build-public-site.