# Newsletters — Rotas e Controllers

> **Módulo:** `newsletters` (tabela `modulos`, id 53)

---

## 1. Rotas do CMS (prefixo `/cms`)

Arquivo: `v6/routes/basepro.php`, grupo `Route::group(['prefix' => 'cms', 'before' => 'csrf'], function () {`
aberto na linha 35. O grupo recebe o middleware `web` em
`v6/app/Providers/RouteServiceProvider.php:80-85`.

| Método | URI | Nome (`->name()`) | Controller@ação | Linha |
|---|---|---|---|---|
| GET | `/cms/newsletter` | `newsletter.index` | `Prefecture\NewsLetterController@index` | `v6/routes/basepro.php:81` |
| GET | `/cms/newsletter/export` | `newsletter.export` | `Prefecture\NewsLetterController@export` | `v6/routes/basepro.php:82` |
| DELETE | `/cms/newsletter/delete/{id}` | `newsletter.delete` | `Prefecture\NewsLetterController@destroy` | `v6/routes/basepro.php:83` |
| GET | `/cms/newsletter/create` | `newsletter.create` | `Prefecture\NewsLetterController@create` | `v6/routes/basepro.php:84` |
| POST | `/cms/newsletter/store` | `newsletter.store` | `Prefecture\NewsLetterController@store` | `v6/routes/basepro.php:85` |
| GET | `/cms/newsletter/edit/{id}` | `newsletter.edit` | `Prefecture\NewsLetterController@edit` | `v6/routes/basepro.php:86` |
| PUT | `/cms/newsletter/update/{id}` | `newsletter.update` | `Prefecture\NewsLetterController@update` | `v6/routes/basepro.php:87` |

Nenhuma dessas rotas declara `->middleware('check.logged')`. A proteção vem do **controller
base** `App\Http\Controllers\Prefecture\Controller`, cujo construtor registra
`check.route.permission` (`v6/app/Http/Controllers/Prefecture/Controller.php:24-27`).
`NewsLetterController` não define construtor, logo herda esse registro.

## 2. Rota do Portal (público)

| Método | URI | Nome | Controller@ação | Linha |
|---|---|---|---|---|
| POST | `/newsletter/store-portal` | `newsletter.storeportal` | `Prefecture\NewsLetterController@storePortal` | `v6/routes/web.php:229` |

Grupo `web` (sessão + CSRF + `SubstituteBindings`). **Sem** `check.logged` — mas, como a
ação está em um controller que herda o construtor do `Prefecture\Controller`, o middleware
`check.route.permission` também é aplicado aqui (ver seção 6).

## 3. Rota de DataTable (CMS)

| Método | URI | Nome | Controller@ação | Linha |
|---|---|---|---|---|
| GET | `/cms/datatable/newsletter` | `datatable.newsletter` | `Prefecture\DatatableController@get_newsletter` | `v6/routes/basepro.php:420` |

Está dentro do grupo `Route::group(['prefix' => 'datatable', 'as' => 'datatable.'], function () {`
(`v6/routes/basepro.php:396`). Responde JSON no formato do Yajra DataTables.

## 4. Ações do controller

`v6/app/Http/Controllers/Prefecture/NewsLetterController.php`

| Ação | Linha | Responsabilidade |
|---|---|---|
| `index()` | 13-17 | Carrega `Newsletters::all()` e renderiza `prefecture.contents.newsletter.index`. A collection **não é usada** pela view (a listagem vem do AJAX). |
| `create()` | 20-23 | Renderiza `prefecture.contents.newsletter.create`. |
| `store(Request)` | 26-43 | Valida `name` e `email`; grava `ip_address`, `sent_at`, `active`; redireciona para `newsletter.index` com flash `success`. |
| `storePortal(Request)` | 45-63 | Valida **somente** `email`; grava `name` e `active` sem validação; `redirect()->back()` com flash `success`. |
| `edit($id)` | 66-70 | `Newsletters::findOrFail($id)` e renderiza `prefecture.contents.newsletter.edit`. |
| `update(Request, $id)` | 73-90 | Valida `name` e `email` (único ignorando o id atual); atualiza apenas `name`, `email`, `active`. |
| `export()` | 92-131 | Gera CSV em streaming a partir de `DB::table('newsletters')`. |
| `show($id)` | 138-144 | Renderiza `view('newsletters.show')` — **view inexistente** e **rota inexistente**: código morto. |
| `destroy($id)` | 147-159 | Soft delete; devolve JSON `{success}` ou `{erro}` com HTTP 500. |

### Código do `storePortal` (fonte)

```php
public function storePortal(Request $request)
{
    $request->validate([
        'email' => 'required|email|unique:newsletters,email',
    ], [
        'email.unique' => 'E-mail já cadastrado.',
    ]);

    Newsletters::create([
        'name' => $request->name,
        'email' => $request->email,
        'ip_address' => $request->ip(),
        'sent_at' => Carbon::now(),
        'active' => $request->active,
    ]);

    return redirect()->back()->with('success', 'Inscrição realizada com sucesso!');
}
```

## 5. Form Requests e validações

O módulo **não usa Form Request**. A validação é feita com `$request->validate()` dentro
do controller, com as seguintes regras:

| Rota | Campo | Regras | Mensagem customizada |
|---|---|---|---|
| `POST /cms/newsletter/store` | `name` | `required\|string\|max:255` | — |
| `POST /cms/newsletter/store` | `email` | `required\|email\|unique:newsletters,email` | `email.unique` → "E-mail já cadastrado." |
| `PUT /cms/newsletter/update/{id}` | `name` | `required\|string\|max:255` | — |
| `PUT /cms/newsletter/update/{id}` | `email` | `required\|email\|unique:newsletters,email,{id}` | `email.unique` → "E-mail já cadastrado." |
| `POST /newsletter/store-portal` | `email` | `required\|email\|unique:newsletters,email` | `email.unique` → "E-mail já cadastrado." |

Observações:

- A regra `unique` consulta a **tabela** `newsletters`, não a view `newsletter`: assinaturas
  em soft delete bloqueiam o re-cadastro (o mesmo vale para a restrição `UNIQUE` do banco).
- `storePortal` **não valida** `name` nem `active`. `name` é anulável no banco (aceita),
  mas `active` é `NOT NULL` e sem default: um POST sem `active` gera `QueryException` e
  resposta HTTP 500 (ver `08-GAPS-E-DIVIDAS.md`).
- Não há sanitização de `name`/`email` além das regras do Laravel.

## 6. Permissões exigidas

O middleware `v6/app/Http/Middleware/CheckRoutePermission.php` resolve a permissão a
partir do **nome da rota**:

| Nome da rota | Segmento avaliado | Permissão aceita | Resultado |
|---|---|---|---|
| `newsletter.index` | `index` | `newsletter.index` (ou `newsletter.index` do segmento) | exige permissão |
| `newsletter.export` | `export` | `newsletter.export` ou `export.index` | exige permissão |
| `newsletter.delete` | `delete` | `newsletter.delete` ou `delete.index` | exige permissão |
| `newsletter.create` | `create` | `newsletter.create` ou `create.index` | exige permissão |
| `newsletter.store` | `store` | — | **aberto**: `store` está na lista `resourceRoutes` do middleware |
| `newsletter.edit` | `edit` | `newsletter.edit` ou `edit.index` | exige permissão |
| `newsletter.update` | `update` | — | **aberto**: `update` está na lista `resourceRoutes` do middleware |
| `datatable.newsletter` | `datatable` | — | **aberto**: `datatable` está na lista `openRoutes` |
| `newsletter.storeportal` | `storeportal` | `newsletter.storeportal` ou `storeportal.index` | exige permissão **e** exige sessão logada |

Regras gerais do middleware:

- `super-admin` passa direto (`Sentinel::inRole('super-admin')`).
- Visitante **não logado** é redirecionado para `login` em qualquer rota servida por um
  controller do namespace `Prefecture` — inclusive `newsletter.storeportal`
  (`v6/app/Http/Middleware/CheckRoutePermission.php:31-34`).
- Usuário logado sem a permissão recebe `flash('Você não tem autorização para acessar esta funcionalidade!')` e
  `redirect()->back()`.

Além do middleware, as views usam o helper `has_access()`
(`v6/app/Library/Helper/appHelpers.php:22`) para exibir/ocultar botões:

| Verificação na view | Efeito |
|---|---|
| `has_access(['newsletter.delete'])` | exibe o botão de exclusão em lote e o "checkbox mestre" da DataTable |

## 7. API Resources / presenters

Não existem API Resources, transformers ou presenters para o módulo. A saída do CMS é:

- **DataTable**: JSON do Yajra (`Yajra\DataTables\Facades\DataTables`), com os campos da
  view mais `edit`, `delete`, `active`, `inactivate` injetados por
  `DatatableController::struct_views()` (`v6/app/Http/Controllers/Prefecture/DatatableController.php:504-521`).
- **Exclusão**: JSON `{ "success": "Inscrição deletada com sucesso!" }` ou `{ "erro": "Erro ao deletar inscrição!" }` com HTTP 500.
- **Exportação**: `StreamedResponse` com `Content-Type: text/csv`.
- **Portal**: redirect com `session('success')` (consumido pela view Blade).

## 8. Permissões do menu (segunda camada)

`v6/app/Providers/MenuServiceProvider.php` monta o menu do CMS a partir de `menu_orders`.
Para `slug = 'newsletter'` o item exibido é:

```php
case 'newsletter':
    $event->menu->add(['text' => 'Newsletter', 'url' => '/cms/newsletter', 'icon' => 'book']);
    break;
```

O item só é adicionado quando `has_access("$menu->slug.index")` **e** `$menu->modulo->status`
são verdadeiros — ou seja, é preciso ter a permissão `newsletter.index` **e** o módulo
`newsletters` (id 53) estar habilitado em `modulos.status`.
