# Rotas e Controladores — Editor Estrutural + Landing Sections Editor

> Caminhos relativos à raiz `lotep/`. Nenhum `FormRequest` dedicado: validação inline e schemas do
> serviço. Nenhum `API Resource`: respostas JSON são montadas à mão.

---

## 1. Rotas do módulo (`V2/Modules/Support/StructuralLandingEditor/Routes/cms.php`)

Prefixo: `cms/infra/structural-editor` · Middleware do grupo: `CmsAuthenticate` e
`v2.permission:structural-landing-editor` · O Core acrescenta `web` + `module.active:StructuralLandingEditor`.

| Método | URI | Nome | Action | Linha |
|---|---|---|---|---|
| GET | `/meta` | `v2.structural-editor.meta` | `SleCmsController@meta` | `:23` |
| POST | `/meta/save` | `v2.structural-editor.meta.save` | `SleCmsController@saveMeta` | `:24` |
| GET | `/theme` | `v2.structural-editor.theme` | `SleCmsController@theme` | `:29` |
| POST | `/theme/save` | `v2.structural-editor.theme.save` | `SleCmsController@saveTheme` | `:30` |
| GET | `/structural-data` | `v2.structural-editor.structural-data` | `SleCmsController@structuralData` | `:35` |
| POST | `/structural-data/save` | `v2.structural-editor.structural-data.save` | `SleCmsController@saveStructuralData` | `:36` |
| GET | `/homepage` | `v2.structural-editor.homepage` | `SleCmsController@homepage` | `:41` |
| POST | `/homepage/save` | `v2.structural-editor.homepage.save` | `SleCmsController@saveHomepage` | `:42` |
| GET | `/features` | `v2.structural-editor.features` | `SleCmsController@features` | `:47` |
| POST | `/features/save` | `v2.structural-editor.features.save` | `SleCmsController@saveFeatures` | `:48` |
| GET | `/editor` | `v2.structural-editor.editor` | `SleCmsController@editor` | `:53` |
| POST | `/editor/save` | `v2.structural-editor.editor.save` | `SleCmsController@saveEditor` | `:54` |
| GET | `/editor/assets` | `v2.structural-editor.assets` | `SleCmsController@assets` | `:60` |
| POST | `/editor/assets/upload` | `v2.structural-editor.assets.upload` | `SleCmsController@assetsUpload` | `:61` |
| POST | `/editor/assets/from-gallery` | `v2.structural-editor.assets.from-gallery` | `SleCmsController@assetsFromGallery` | `:62` |
| POST | `/editor/assets/delete/{id}` | `v2.structural-editor.assets.delete` | `SleCmsController@assetsDelete` | `:63` |
| GET | `/` | `v2.structural-editor.index` | `SleCmsController@editor` | `:68` |
| POST | `/save` | — | `SleCmsController@saveEditor` | `:69` |
| POST | `/save-configs` | — | `SleCmsController@saveMeta` | `:70` |

As três últimas são **aliases retrocompatíveis**; `/save` e `/save-configs` não têm nome.

---

## 2. Rotas do serviço (`V2/Services/LandingSectionsEditor/Routes/cms.php`)

Prefixo: `cms/v2/landing-sections` · Middleware do grupo: **apenas** `CmsAuthenticate`.

| Método | URI | Nome | Action | Linha |
|---|---|---|---|---|
| GET | `/form` | `v2.landing-sections.form` | `getForm` | `:12` |
| POST | `/validate` | `v2.landing-sections.validate` | `validateSection` | `:13` |
| POST | `/preview/save` | `v2.landing-sections.preview.save` | `savePreview` | `:14` |
| GET | `/preview/render` | `v2.landing-sections.preview.render` | `renderPreview` | `:15` |
| POST | `/preview/render-section` | `v2.landing-sections.render-section` | `renderSection` | `:16` |
| GET | `/media` | `v2.landing-sections.media.index` | `listMedia` | `:19` |
| POST | `/media` | `v2.landing-sections.media.store` | `storeMedia` | `:20` |
| PUT | `/media/{id}` | `v2.landing-sections.media.update` | `updateMedia` | `:21` |
| POST | `/media/{id}/rotate` | `v2.landing-sections.media.rotate` | `rotateMedia` | `:22` |
| DELETE | `/media/{id}` | `v2.landing-sections.media.destroy` | `destroyMedia` | `:23` |

Trecho real do grupo:

```php
Route::group(['prefix' => 'cms/v2/landing-sections', 'middleware' => [\V2\Core\Middleware\CmsAuthenticate::class]], function() {
    Route::get('/form', 'LandingSectionsEditorController@getForm')->name('v2.landing-sections.form');
```

Fonte: `V2/Services/LandingSectionsEditor/Routes/cms.php:9-12`.

**Diferença crítica:** o Core monta rotas de serviço com `['web']` apenas
(`V2/Core/Providers/V2ServiceProvider.php:391-395`), então este grupo não recebe `module.active`
nem `v2.permission`. Qualquer usuário autenticado no CMS alcança os 10 endpoints.

---

## 3. Controllers e ações

### 3.1 `SleCmsController`

Caminho: `V2/Modules/Support/StructuralLandingEditor/Controllers/SleCmsController.php`.
Construtor recebe `V2AuditLogger` e `AssetManagerService` (`:25-29`).

| Action | Linha | Comportamento | Grava em |
|---|---|---|---|
| `meta()` | `:49` | carrega `UserConfigsService::getAll()` e `LayoutBaseConfig::all()`; renderiza `meta.index` | — |
| `saveMeta()` | `:74` | captura antigo, chama `UserConfigsCmsController::update()`, audita `meta_save` | `v2_portal_configs`, `v2_cms_theme_configs`, `v2_system_mail_configs` |
| `theme()` | `:107` | carrega configs e renderiza `theme.index` | — |
| `saveTheme()` | `:132` | se `tab ∈ {logos, comunicado-global}` delega a `LayoutBaseCmsController`; senão a `UserConfigsCmsController` | ambos |
| `structuralData()` | `:175` | carrega `LayoutBaseConfig` e renderiza `structural-data.index` | — |
| `saveStructuralData()` | `:192` | delega a `LayoutBaseCmsController::update()`; audita `structural_data_save` | `v2_layout_base_configs` |
| `homepage()` | `:228` | lê `homepage_sections`; se versão 3, serializa `sections`; senão usa `defaultSectionsList()` | — |
| `saveHomepage()` | `:265` | força `tab=homepage`, delega a `LayoutBaseCmsController`; audita `homepage_sections_save` | `v2_layout_base_configs` |
| `features()` | `:300` | carrega configs e renderiza `features.index` | — |
| `saveFeatures()` | `:325` | grava toggles via `UserConfigsService` + `lai_enabled` | `v2_portal_configs`, `v2_layout_base_configs` |
| `editor()` | `:376` | 8 chamadas `StructuralPortalContent::getForArea()`; renderiza `editor.index` | — |
| `saveEditor()` | `:393` | transação; cria/atualiza/desativa áreas; audita `editor_save` | `v2_structural_portal_contents` |
| `assets()` | `:484` | lista assets por tipo | — |
| `assetsUpload()` | `:497` | valida `file|max:10240` e `type in css,js,image`; audita | `v2_sle_assets` + storage/Gallery |
| `assetsFromGallery()` | `:549` | cria asset a partir de `gallery_media_id` | `v2_sle_assets` |
| `assetsDelete()` | `:578` | apaga físico + soft delete + auditoria | `v2_sle_assets` |

Trecho do gate:

```php
private function authorizeSupportAccess()
{
    if (!Sentinel::check() || Sentinel::getUser()->email !== config('v2.support.email')) {
        abort(403, 'Acesso restrito ao suporte técnico autorizado.');
    }
}
```

Fonte: `SleCmsController.php:35-40`.

**Chamada de controller dentro de controller:** `saveMeta`, `saveTheme`, `saveStructuralData` e
`saveHomepage` fazem `app(OutroController::class)->update($request)`
(`:84`, `:148-152`, `:202`, `:276`). Isso duplica a resolução de dependências e ignora middleware.

### 3.2 `LayoutBaseCmsController`

Caminho: `V2/Modules/Support/StructuralLandingEditor/Controllers/LayoutBaseCmsController.php`.
Não tem rota própria; é invocado pelos controllers acima.

| Método | Linha | `tab` | Efeito |
|---|---|---|---|
| `update()` | `:36` | switch | despacha para os handlers |
| `handleLogosUpdate()` | `:78` | `logos` | resolve galeria ou upload; favicon especial |
| `handleFaviconUpload()` | `:136` | — | valida `.ico`, ≤100 KB, grava caminho fixo |
| `handleLinksHeaderUpdate()` | `:190` | `links-header` | `json_decode` do textarea |
| `handleLinksFooterUpdate()` | `:204` | `links-footer` | idem |
| `handleContatoUpdate()` | `:218` | `contato` | campos fixos |
| `handleRedesSociaisUpdate()` | `:235` | `redes-sociais` | idem |
| `handleTransparenciaLaiUpdate()` | `:249` | `transparencia-lai` | idem |
| `handleComunicadoGlobalUpdate()` | `:263` | `comunicado-global` | banner + datas + alt |
| `handleHomepageUpdate()` | `:303` | `homepage` | `sanitizeContent()` e payload v3 |
| `saveConfig()` | `:336` | — | `firstOrNew(['key'])`, salva e audita |

`update()` **não retorna nada**; erros são relançados após `Log::error` (`:66-69`).

### 3.3 `UserConfigsCmsController`

Caminho: `V2/Modules/Support/StructuralLandingEditor/Controllers/UserConfigsCmsController.php`. Sem rotas.

- `update(Request)`: separa `mail_*`, `cms_*` e demais; delega a `UserConfigsService::updateConfigs`
  (`:25-41`).
- `resetCmsTheme()`: chama `SupportConfigsService::resetCmsThemeToDefault()` (`:46-55`).

### 3.4 `LandingSectionsEditorController`

Caminho: `V2/Services/LandingSectionsEditor/Controllers/LandingSectionsEditorController.php`.
Construtor recebe `ValidationService`, `LandingSectionsEditorRendererService`,
`ActivityLoggerService`; instancia `new GallerySender('landing_page')` (`:50-61`).

| Action | Linha | Entrada | Saída |
|---|---|---|---|
| `getForm` | `:72` | `type`, `section_id`, `parameters` (JSON) | HTML do form; 400 sem tipo; 404 se a view não existir |
| `validateSection` | `:108` | `type` + `parameters` | `{success:true}` ou 422 com `errors`; 500 genérico |
| `savePreview` | `:133` | `token`, `sections`, `preview_vars` | `{success:true}`; cache `landing_preview_{token}` por 3600 s |
| `renderPreview` | `:179` | `token` | view `preview` (iframe) |
| `renderSection` | `:213` | `type`, `parameters`, `section_id` | `{success:true, html}` ou 404 |
| `listMedia` | `:271` | — | JSON do `GallerySender` |
| `storeMedia` | `:288` | `file` (≤10 MB), `file_alt`, `description` | JSON da mídia; auditoria |
| `updateMedia` | `:337` | `alt_text`, `description` | `{success}`/404 |
| `rotateMedia` | `:382` | `{id}` | `{success}`/400 |
| `destroyMedia` | `:414` | `{id}` | `{success}`/400 |

Sanitização e preparação no preview de seção:

```php
$sanitized = $this->rendererService->sanitizeContent([$section]);
$prepared = $this->rendererService->prepareSections($sanitized);
$finalSection = $prepared[0];
```

Fonte: `LandingSectionsEditorController.php:245-247`.

---

## 4. Form Requests e validators

Não há classes `FormRequest`.

### 4.1 Validação inline

| Onde | Regras |
|---|---|
| `SleCmsController::assetsUpload` | `file: required|file|max:10240`; `type: required|in:css,js,image` (`:499-502`) |
| `SleCmsController::assetsFromGallery` | `gallery_media_id: required|integer|min:1` (`:551-553`) |
| `LandingSectionsEditorController::storeMedia` | `file: required|file|max:10240`; `file_alt: nullable|string|max:255`; `description: nullable|string|max:255` (`:290-294`) |
| `LandingSectionsEditorController::updateMedia` | `alt_text`/`description`: `nullable|string|max:255` (`:339-342`) |

### 4.2 Schemas por tipo

`ValidationService::validate()` lê `type` e aplica `getRulesFor($type)` sobre `$request->all()`
(`V2/Services/LandingSectionsEditor/ValidationSchemas/ValidationService.php:16-31`). A resolução:

1. `WidgetRegistry::get($type)->getValidationRules()`;
2. fallback: `Str::studly($type) . 'Schema'` no namespace
   `V2\Services\LandingSectionsEditor\ValidationSchemas` (`:38-52`).

Os 31 schemas usam chaves `parameters.*` — o editor envia `parameters` como array no POST
(`validateSection`) e como JSON no `getForm`. Exemplos:

```php
// HomeHeroSchema
'parameters.image_id' => 'nullable|integer|exists:v2_gallery_media,id',
'parameters.primary_cta.url' => 'nullable|string|max:255',
```

```php
// BlocoSleSchema
'parameters.template' => 'required|string',
```

Nenhum schema limita o **tamanho total** do JSON de seções nem a quantidade de seções.

---

## 5. API Resources e presenters

**Nenhum.** As respostas são arrays PHP convertidos por `response()->json()`:

| Endpoint | Forma |
|---|---|
| `validateSection` | `{success, errors?}` ou `{success:false, message}` |
| `savePreview` | `{success:true}` / `{success:false, message}` |
| `renderSection` | `{success:true, html}` / `{success:false, message}` |
| `storeMedia`/`updateMedia`/`rotateMedia`/`destroyMedia` | JSON do Gallery ou `{success}` |

O `SleCmsController` responde com redirect + `flash()` na maioria das ações; upload e galeria
respondem JSON quando `$request->ajax()`/`wantsJson()` (`:527-544`).

---

## 6. Permissões

| Superfície | Verificação efetiva | Evidência |
|---|---|---|
| Rotas do módulo | `module.active` + `CmsAuthenticate` + `v2.permission` (módulo resolvido por `active_pattern`) + e-mail fixo em cada action | `Routes/cms.php:16`; `SleCmsController.php:35-40` |
| Rotas do serviço | apenas `CmsAuthenticate` | `V2/Services/LandingSectionsEditor/Routes/cms.php:9` |
| Views do módulo | `@v2can('LayoutBase', 'write')` em botões de salvar | `Views/cms/tabs/layoutbase/links-header.blade.php:31` |
| Render `bloco-sle` | e-mail de suporte no preview; portal confia no dado | `V2/Services/LandingSectionsEditor/Views/render/bloco-sle.blade.php:12-31` |

**Inconsistência de chave:** a rota pede `structural-landing-editor`, as views pedem `LayoutBase`.
Como o middleware ignora o parâmetro e o grupo Support já exige o e-mail, a divergência fica
mascarada — mas ela impede um RBAC granular.

---

## 7. Eventos de auditoria

| `type` | Origem | Payload |
|---|---|---|
| `meta_save` | `saveMeta` | `tab`, `old`, `new` (request sem `_token`/`_method`/`tab`) |
| `theme_save` | `saveTheme` | `tab`, `old`, `new` |
| `structural_data_save` | `saveStructuralData` | `tab`, `old` (configs completas), `new` |
| `homepage_sections_save` | `saveHomepage` | `old` (payload), `new` = string JSON crua |
| `features_save` | `saveFeatures` | quatro flags old/new |
| `editor_save` | `saveEditor` | `areas`, `old`, `new` |
| `created`/`deleted` | assets | metadados do arquivo |

`saveConfig()` também audita cada gravação de `LayoutBaseConfig` com `old/new`
(`LayoutBaseCmsController.php:343-347`) — portanto salvar a home gera **dois** registros: um do
config e um da tela, com `new` divergente (o da tela guarda a string JSON não sanitizada).
