# Arquitetura — Editor Estrutural + Landing Sections Editor

> **Escopo:** `V2/Modules/Support/StructuralLandingEditor/**` (módulo) e
> `V2/Services/LandingSectionsEditor/**` (serviço). Documento estático: nada foi executado.
> Caminhos relativos à raiz `lotep/`.

---

## 1. Duas unidades auto-descobertas

| Unidade | Como nasce | Evidência |
|---|---|---|
| Módulo `V2/Modules/Support/StructuralLandingEditor` | varredura de `V2/Modules/` por grupo e módulo; provider instanciado no boot | `V2/Core/Providers/V2ServiceProvider.php:89-121` |
| Serviço `V2/Services/LandingSectionsEditor` | varredura de `V2/Services/` por serviço; migrações, views e rotas carregadas | `V2/Core/Providers/V2ServiceProvider.php:168-195` |

O módulo entra no manifesto `v2.modules.configs` com `group = Support`; o serviço **não** entra em
manifesto (não tem `ModuleProvider`), portanto não aparece no menu, não tem `active_pattern` e não
passa por `v2.permission`.

**Consequência:** o módulo é o único ponto com checagem de módulo/RBAC; os endpoints do serviço
dependem apenas de `CmsAuthenticate`.

---

## 2. Fluxo de uma requisição do CMS (módulo)

```mermaid
flowchart LR
    A[GET/POST /cms/infra/structural-editor/*] --> B[web<br/>CSRF + sessão]
    B --> C[module.active:StructuralLandingEditor]
    C --> D[CmsAuthenticate]
    D --> E[v2.permission:structural-landing-editor]
    E --> F[SleCmsController]
    F --> G[authorizeSupportAccess<br/>e-mail fixo]
    G --> H[LayoutBaseCmsController / UserConfigsCmsController]
    G --> I[AssetManagerService]
    H --> J[(v2_layout_base_configs)]
    H --> K[(v2_structural_portal_contents)]
    I --> L[(v2_sle_assets + Gallery)]
    F --> M[v2-structurallandingeditor::cms.*]
    F --> N[V2AuditLogger]
```

Etapas verificáveis:

1. O Core aplica `module.active:{Modulo}` a **todo** `cms.php`
   (`V2/Core/Providers/V2ServiceProvider.php:513-518`).
2. O próprio arquivo de rotas declara `CmsAuthenticate` e `v2.permission:structural-landing-editor`
   (`V2/Modules/Support/StructuralLandingEditor/Routes/cms.php:16`).
3. `V2PermissionMiddleware` resolve o módulo por `active_pattern`
   (`cms/infra/structural-editor/*`) e **ignora** o argumento da rota
   (`V2/Core/Middleware/V2PermissionMiddleware.php:38-48`).
4. Para o grupo `Support`, qualquer usuário que não seja `config('v2.support.email')` é bloqueado
   (`V2PermissionMiddleware.php:50-66`).
5. Cada action reforça o gate em código (`SleCmsController.php:35-40`).

---

## 3. Fluxo do serviço (form, validação e preview)

```mermaid
flowchart LR
    A[GET /cms/v2/landing-sections/form] --> B[web + CmsAuthenticate]
    B --> C[LandingSectionsEditorController]
    C --> D[WidgetRegistry]
    C --> E[ValidationService]
    C --> F[LandingSectionsEditorRendererService]
    F --> G[prepareSections]
    G --> H[DB::table v2_gallery_media<br/>whereIn único]
    C --> I[(Cache landing_preview_TOKEN)]
    C --> J[v2-service-landingsectionseditor::*]
    C --> K[ActivityLoggerService]
```

O serviço é montado com apenas `web` (`V2ServiceProvider.php:391-395`); nenhuma rota recebe
`module.active` ou `v2.permission`.

---

## 4. Classes e responsabilidades

### 4.1 Módulo

| Classe | Caminho | Papel |
|---|---|---|
| `ModuleProvider` | `V2/Modules/Support/StructuralLandingEditor/ModuleProvider.php` | registra singletons `BlockRenderer`/`ComponentResolver`, composers de `v2-portal::*` e `v2-basicpage::*`, e o manifesto de menu |
| `SleCmsController` | `V2/Modules/Support/StructuralLandingEditor/Controllers/SleCmsController.php` | 6 telas + assets; delega gravação a outros controllers; audita |
| `LayoutBaseCmsController` | `V2/Modules/Support/StructuralLandingEditor/Controllers/LayoutBaseCmsController.php` | grava `v2_layout_base_configs` por `tab`; uploads de logo/banner/favicon; `homepage_sections` |
| `UserConfigsCmsController` | `V2/Modules/Support/StructuralLandingEditor/Controllers/UserConfigsCmsController.php` | ponte para `UserConfigsService` (portal + tema CMS + e-mail) |
| `StructuralPortalContent` | `V2/Modules/Support/StructuralLandingEditor/Models/StructuralPortalContent.php` | área única, UUID v4, cast `active` |
| `LayoutBaseConfig` | `V2/Modules/Support/StructuralLandingEditor/Models/LayoutBaseConfig.php` | chave-valor JSONB, soft delete |
| `SleAsset` | `V2/Modules/Support/StructuralLandingEditor/Models/SleAsset.php` | catálogo de CSS/JS/imagem com `snippet` |
| `BlockRenderer` | `V2/Modules/Support/StructuralLandingEditor/Services/BlockRenderer.php` | compila Blade do banco e executa em sandbox de arquivo |
| `ComponentResolver` | `V2/Modules/Support/StructuralLandingEditor/Services/ComponentResolver.php` | whitelist de 11 componentes do portal para `sle_component()` |
| `HomepageSectionsService` | `V2/Modules/Support/StructuralLandingEditor/Services/HomepageSectionsService.php` | contrato v3: defaults, `parseConfig`, IDs estáveis |
| `AssetManagerService` | `V2/Modules/Support/StructuralLandingEditor/Services/AssetManagerService.php` | upload/persistência de CSS/JS/imagem e snippets |
| `UserConfigsService` | `V2/Modules/Support/StructuralLandingEditor/Services/UserConfigsService.php` | agrega configs do portal/CMS/e-mail e grava em transação |
| `GallerySender` | `V2/Modules/Support/StructuralLandingEditor/Services/GallerySender.php` | especializa o `GallerySender` base (`structural_landing_editor`, imagens, 10 MB) |
| `LayoutBaseComposer` | `V2/Modules/Support/StructuralLandingEditor/ViewComposers/LayoutBaseComposer.php` | injeta `$layoutConfigs` nas views do portal |

### 4.2 Serviço

| Classe | Caminho | Papel |
|---|---|---|
| `LandingSectionsEditorController` | `V2/Services/LandingSectionsEditor/Controllers/LandingSectionsEditorController.php` | 10 endpoints: form, validate, preview, render, mídia |
| `LandingSectionsEditorRendererService` | `V2/Services/LandingSectionsEditor/Services/LandingSectionsEditorRendererService.php` | registro dos widgets, `sanitizeContent`, `prepareSections`, `legacyPrepare` |
| `WidgetRegistry` | `V2/Services/LandingSectionsEditor/Services/WidgetRegistry.php` | mapa estático `type => WidgetContract` |
| `IconCatalog` | `V2/Services/LandingSectionsEditor/Services/IconCatalog.php` | lê `bootstrap-icons.css` e cacheia nomes |
| `WidgetContract` | `V2/Services/LandingSectionsEditor/Widgets/WidgetContract.php` | interface de 7 métodos |
| `BaseWidget` / `GenericWidget` | `V2/Services/LandingSectionsEditor/Widgets/BaseWidget.php`, `V2/Services/LandingSectionsEditor/Widgets/GenericWidget.php` | implementação padrão e configuração por construtor |
| `HomeImageWidget` | `V2/Services/LandingSectionsEditor/Widgets/HomeImageWidget.php` | resolve `image_id` por caminho, com wildcard, para `{src, alt}` |
| `HtmlFreeWidget` / `HomeComunicadoWidget` / `BlocoSleWidget` | `V2/Services/LandingSectionsEditor/Widgets/HtmlFreeWidget.php`, `V2/Services/LandingSectionsEditor/Widgets/HomeComunicadoWidget.php`, `V2/Services/LandingSectionsEditor/Widgets/BlocoSleWidget.php` | sanitize específico para conteúdo HTML/template |
| 31 schemas | `V2/Services/LandingSectionsEditor/ValidationSchemas/` | regras `parameters.*` por tipo |
| `ValidationService` | `V2/Services/LandingSectionsEditor/ValidationSchemas/ValidationService.php` | resolve regras via widget e fallback por nome de classe |

### 4.3 Contrato de widget

`WidgetContract` define sete métodos (`V2/Services/LandingSectionsEditor/Widgets/WidgetContract.php:7-60`); segue o trecho real, com os docblocks omitidos:

```php
interface WidgetContract
{
    public function getIcon(): string;

    public function getTitle(): string;

    /** @return array<string, string|array<int, string>> */
    public function getValidationRules(): array;

    /** @param array<string, mixed> $parameters */
    public function sanitize(array $parameters): array;

    public function getFormView(): string;

    public function getRenderView(): string;

    /** @return array<string, mixed> */
    public function getAttachmentIds(array $parameters): array;

    public function prepare(array $parameters, Collection $anexos = null): array;
}
```

| Método | Papel | Implementação padrão |
|---|---|---|
| `getIcon` / `getTitle` | rótulo no dropdown do editor | propriedades do `BaseWidget` |
| `getValidationRules` | regras Laravel do tipo | instancia a classe de schema e chama `rules()` |
| `sanitize` | limpeza no save e no preview | `strip_tags` recursivo |
| `getFormView` | view do modal | `v2-service-landingsectionseditor::forms.<tipo>` |
| `getRenderView` | view do portal | `v2-service-landingsectionseditor::render.<tipo>` |
| `getAttachmentIds` | coleta `media_id` por caminho | vazio; `HomeImageWidget` resolve wildcards |
| `prepare` | injeta URLs/alt/variantes | escreve o caminho do arquivo; `HomeImageWidget` monta `{src, alt}` |

**Sanitize não é validação.** O contrato recebe parâmetros já validados pelo schema; `sanitize` só
reduz HTML. Os tipos `html-free`, `home-comunicado` e `bloco-sle` sobrescrevem o método.

---

## 5. Contrato de renderização

`BlockRenderer::renderBlockWithVars()` é o único caminho de execução de Blade vindo do banco:

```php
$compiler = app('blade.compiler');
$compiled = $compiler->compileString($template);
$data = array_merge($this->getVars(), $extraVars);

// Laravel precisa de $__env para renderizar sub-views (ex: sle_component)
$data['__env'] = app('view');

return $this->runSandbox($this->sandboxPath($compiled), $data);
```

Fonte: `V2/Modules/Support/StructuralLandingEditor/Services/BlockRenderer.php:156-176`.

Variáveis expostas (`getVars()`): `siteTitle`, `portalTheme`, `layoutConfigs`, `contato`,
`headerLinks`, `footerLinks`, `redes`, `logos`, `request` — e `__env`. `extract($data, EXTR_SKIP)`
antes do `include` (`BlockRenderer.php:186-201`).

O sandbox é um arquivo em `storage/framework/views/sle/sle_` + hash SHA-1 + `.php`, gravado de forma atômica
(`tmp` + `rename`), sem expiração nem limpeza (`BlockRenderer.php:211-231`).

---

## 6. Cadeia de renderização da home

```mermaid
flowchart LR
    A[BasicPagePortalController@homepage] --> B[HomepageDataService::buildStatic]
    B --> C[LayoutBaseConfig key=homepage_sections]
    C --> D[HomepageSectionsService::parseConfig]
    D --> E[LandingSectionsEditorRendererService::prepareSections]
    E --> F[whereIn v2_gallery_media]
    B --> G[HomepageDataService::build<br/>imprensa/dashboard/agenda/jogos]
    A --> H[homepage.blade.php]
    D --> H
    E --> H
    G --> H
    H --> I[partials + render.home-comunicado]
```

Evidências: `V2/Modules/Institucional/BasicPage/Controllers/BasicPagePortalController.php:88-126`;
`V2/Modules/Institucional/BasicPage/Services/HomepageDataService.php:104-171`;
`V2/Modules/Institucional/BasicPage/Views/portal/homepage.blade.php:60-103`.

**Acoplamento invertido:** um módulo (`BasicPage`) instancia a classe concreta de outro módulo
(`HomepageSectionsService`) e o serviço (`LandingSectionsEditor`) é chamado por `app()`. Isso viola a
regra "módulos não importam módulos" (ADR-013) e é tratado em `09-APROVEITAMENTO-V7.md`.

---

## 7. Dependências externas

| Dependência | Uso | Evidência |
|---|---|---|
| `ramsey/uuid` | IDs dos três models | imports nos três arquivos de `V2/Modules/Support/StructuralLandingEditor/Models/` |
| Sentinel | login, `Sentinel::getUser()`, gate por e-mail | `SleCmsController.php:8`, `:37`; `bloco-sle.blade.php:20-27` |
| Blade Compiler | compilar templates do banco | `BlockRenderer.php:160-162` |
| `V2\Services\Gallery` | catálogo de mídia (`GallerySender`, `GalleryService`) | `LandingSectionsEditorController.php:19`; `AssetManagerService.php:11-12` |
| `V2\Services\Storage` | gravar favicon/assets | `LayoutBaseCmsController.php:13`; `AssetManagerService.php:13` |
| `V2\Services\Media\MediaInputer` | uploads de logos/banners | `LayoutBaseCmsController.php:12`, `:117` |
| `V2\Core\Services\V2AuditLogger` | trilha de auditoria | `SleCmsController.php:10`, `:26` |
| `V2\Modules\Support\SupportConfigs` | configs de portal/CMS/e-mail | `UserConfigsService.php:9-10` |
| Bootstrap Icons | catálogo/ícones | `IconCatalog.php:19` |
| CodeMirror (via partial `v2-cms::partials.code-editor`) | editor de HTML/CSS/JS | `Views/cms/tabs/main.blade.php:36-42` |

---

## 8. Fronteiras e violações

| Regra do V7 (ADR-013) | Situação no V2 | Evidência |
|---|---|---|
| serviço não importa módulo | `LandingSectionsEditorController` importa `V2\Modules\Support\SupportConfigs`; a view `preview.blade.php` importa `V2\Modules\Institucional\BasicPage` | `LandingSectionsEditorController.php:16`; `Views/preview.blade.php:44-50` |
| serviço não tem UI | o serviço tem `Controllers/`, `Routes/`, `Views/forms`, `Views/render` | `V2/Services/LandingSectionsEditor/README.md:17-34` |
| módulo não importa módulo | `BasicPage` importa `StructuralLandingEditor\Services\HomepageSectionsService` | `HomepageDataService.php:12` |
| render do serviço chama módulo | `render/bloco-sle.blade.php` resolve `BlockRenderer` do módulo | `V2/Services/LandingSectionsEditor/Views/render/bloco-sle.blade.php:38` |

O serviço se apresenta como "serviço com UI" no próprio README, o que o V7 separa em
`src/services` (headless) e `src/modules` (caso de uso) — ver ADR-015 e ADR-013.

---

## 9. Resumo da arquitetura

O módulo é um CMS de "shell como dado": áreas fixas em tabela, configurações chave-valor em JSONB e
um renderizador que executa Blade vindo do banco. O serviço é um kit de seções: registry estático,
schemas de validação, formulários e renders, com preview em cache. A home é o ponto de encontro: o
módulo grava a lista de seções, o serviço prepara mídias e o módulo `BasicPage` renderiza os
partials. A arquitetura é coerente para 2019–2021, mas concentra no banco três coisas que o V7
proíbe: HTML, CSS/JS e templates executáveis.
