# Migração Moderna (continuação) — Endpoints, Views e Operação

> Continuação de [`06-MIGRACAO-MODERNA.md`](06-MIGRACAO-MODERNA.md). Caminhos novos relativos a
> `src/`. Decisões de schema, modelos, catálogo, validação e autorização estão no arquivo anterior.

---

## 7. Endpoints REST/JSON

Superfície CMS (`cms.{base}`), atrás de autenticação e Gate `layout.*` (ADR-033):

| Método | URI | Ação | Permissão |
|---|---|---|---|
| GET | `/cms/layout/{layoutKey}` | árvore de slots + configs | `layout.ver` |
| POST | `/cms/layout/{layoutKey}/slots` | cria bloco | `layout.editar` |
| PUT | `/cms/layout/slots/{slot}` | atualiza props/hidden | `layout.editar` |
| DELETE | `/cms/layout/slots/{slot}` | arquiva | `layout.arquivar` |
| PUT | `/cms/layout/{layoutKey}/order` | reordena em transação | `layout.editar` |
| POST | `/cms/layout/{layoutKey}/publish` | publica rascunho | `layout.aprovar` |
| GET | `/cms/layout/blocks` | catálogo + regras de formulário | `layout.ver` |
| GET | `/cms/layout/{layoutKey}/preview` | preview autenticado | `layout.ver` |

Regras de contrato:

- `POST /slots` valida que `type` existe no catálogo e que `region` é suportada pelo tipo.
- `PUT /order` recebe a lista completa de `{id, position}` e roda em transação com índice
  `DEFERRABLE` (schema em `06` §2.2).
- `DELETE` nunca apaga: `hidden = true`; o histórico fica na auditoria.
- Nenhuma rota renderiza HTML vindo do corpo da requisição.

Resposta com `JsonResource` concreto (sem abstração):

```php
final class LayoutSlotResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'type' => $this->widget_type,
            'region' => $this->region,
            'position' => $this->position,
            'hidden' => $this->hidden,
            'props' => $this->props,
        ];
    }
}
```

### 7.1 Preview

- `POST /cms/layout/{layoutKey}/preview` grava o rascunho em cache com chave
  `layout.preview.{user_id}.{layoutKey}`, TTL 30 min.
- `GET /cms/layout/{layoutKey}/preview` só lê a chave do **próprio usuário** autenticado.
- Sem token no cliente, sem URL compartilhável, sem cache global.
- CSP sem inline (ADR-022): o preview é HTML servido pelo mesmo pipeline, sem `<script>` inline; o
  iframe usa `sandbox` e `srcdoc` apenas para conteúdo sem script.

### 7.2 Erros

Formato único (RFC 7807, ADR-024):

| Status | `type` | Quando |
|---|---|---|
| 422 | `layout/invalid-block` | `props` inválidas para o tipo |
| 403 | `layout/forbidden` | falta de permissão |
| 404 | `layout/module-inactive` | módulo desativado (ADR-034) |
| 409 | `layout/order-conflict` | reordenação concorrente |

---

## 8. Views e componentes

| Legado | V7 |
|---|---|
| `v2-structurallandingeditor::cms.submodules.*` | `Modules\Support\StructuralLandingEditor\Views\cms\*` com `<x-layout::cms>` |
| `landing-sections-editor` (jQuery) | componente Blade + JS em módulo Vite, sem jQuery |
| 35 forms | um `<x-landing::block-form>` que escolhe o partial pelo tipo |
| 36 renders | um componente por tipo, em código, com props tipadas |
| `preview.blade.php` | `cms.layout.preview` com iframe sandbox |
| `manual/content.blade.php` | documentação no próprio CMS, sem exemplos executáveis |

Regras de render:

- `props` são validadas antes de chegar ao componente.
- Toda saída de texto é `{{ }}`; nenhum `{!! !!}`.
- Imagens usam `<x-service-media::picture>` (ADR-026) com `media_id`.
- SEO/estrutura vêm do serviço SEO (ADR-027), não de HTML no bloco.
- O shell do portal resolve os slots (`header`, `main`, `footer`, `aside_*`) e o módulo entrega
  somente o conteúdo do slot `main` (ADR-016).

### 8.1 Componente de bloco

```blade
{{-- exemplo ilustrativo --}}
<x-landing::block :type="$slot->widget_type" :props="$slot->props" :slot-id="$slot->id" />
```

O componente `block` resolve o partial de render por `type` em um mapa de código (não em banco).
Tipos desconhecidos não renderizam e geram log estruturado.

---

## 9. Fila, cache e storage

| Item | Decisão |
|---|---|
| Cache de layout | chave `layout.resolved.{layoutKey}` com versão monotônica; invalidação no publish |
| Preview | cache por usuário, TTL curto, sem compartilhamento |
| Mídia | S3/RustFS (ADR-030); variantes AVIF/WebP pelo MediaInputer |
| Assets CSS/JS | build Vite; proibido upload de JS/CSS pelo CMS |
| Auditoria | síncrona, na mesma transação da escrita (ADR-035) |
| Ativação | módulo `StructuralLandingEditor` sob `CheckModuleActivation` (ADR-034) |
| Observabilidade | logs `layout.published`, `layout.slot.saved`, `layout.preview.opened` (ADR-024) |

A resolução de layout no request deve ser **uma** consulta (slots + configs) e ficar em cache de
leitura; a invalidação usa a versão do layout, não TTL adivinhado.

---

## 10. Riscos e decisões de equivalência

| Decisão | Não pode mudar | Como garantir |
|---|---|---|
| Ordem da home | 9 seções na ordem default | fixture `LayoutDefaultsTest` |
| `hide` | posição preservada | teste de reorder + hidden |
| Header fora da ordem | `_header_title/_header_subtitle` | slot `header` separado |
| Dinâmicas | conteúdo vem do módulo de origem | `type` + `source`, sem `props` |
| Comunicado | pode substituir a home com aviso | AST + `role="status"` |
| Fallback | sem config = layout atual | snapshot de HTML |
| Acessibilidade | flags ligam barra/widget/VLibras | teste de render por flag |

O que **não** é equivalente e deve ser aceito como mudança:

- Não existe mais edição de HTML/CSS/JS pelo CMS.
- `html-free` deixa de existir; conteúdo livre passa pelo editor TipTap.
- Assets de terceiros entram no build (zero CDN, ADR-002).
- Preview não é compartilhável por link.

---

## 11. Ordem de implementação sugerida

1. Migrations `layout_configs` e `layout_slots` + seeds default.
2. `LandingWidgetCatalog` + `LandingBlockRules` + 9 widgets da home.
3. Renderer de slots + partials da home migrados para componentes.
4. Gate + endpoints + editor (formulário por tipo).
5. Preview por sessão + publicação.
6. Migração de dados: converter `homepage_sections` v3 em linhas de `layout_slots`.
7. Testes de contrato e snapshot; remover o legado.

---

## 12. Definition of Done

- [ ] Nenhuma rota aceita template/HTML executável.
- [ ] Gate aplicado em leitura e escrita.
- [ ] Reordenação testada com índice único `DEFERRABLE`.
- [ ] Preview isolado por usuário.
- [ ] Snapshot da home default aprovado.
- [ ] Auditoria com ator e diff.
- [ ] Nenhum asset JS/CSS enviado pelo CMS.
