Files
amare/openspec/changes/archive/2026-07-29-build-content-cms/design.md
manoel freitas 7a858b52af feat: implement CMS for content management (WEB-02/03/04/06)
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>
2026-07-29 06:18:42 -03:00

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:

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 (PublishPortfolioCase etc.) — 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:

  1. Configurações (singleton page)
  2. Serviços
  3. Portfólio
  4. 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

  1. Rodar migrations em ordem: site_settingsservicesportfolio_cases + portfolio_imagestestimonials.
  2. Executar seed de conteúdo após migrations.
  3. Sem rollback de dados em produção (greenfield); migrate:rollback suportado em dev.

Open Questions

  • Nenhuma bloqueante. Fontes tipográficas finais da home ficam para build-public-site.