# Sessões da Câmara Municipal — Rotas e Controllers

> Módulo `sessoes` na `v6`.
> O CMS **não tem controller dedicado**: reaproveita
> `Prefecture\ContentController` (CRUD genérico) e
> `Prefecture\DatatableController@get_sessoes` (DataTable server-side).

---

## 1. Rotas do portal (público)

Arquivo: `v6/routes/web.php`. Todas as rotas abaixo estão dentro do grupo
`Route::middleware(['check.site.manutention'])` aberto na linha 8.

| Método | URI | Nome da rota | Controller@action | Middleware |
|---|---|---|---|---|
| GET | `/sessoes` | `sessoes.index` | `Portal\SessionsController@index` | `check.site.manutention` |
| GET | `/sessoes/{slug}` | `sessoes.show` | `Portal\SessionsController@show` | `check.site.manutention` |

Trecho real (`v6/routes/web.php:113-114`):

```php
Route::get('/sessoes', 'Portal\SessionsController@index')->name('sessoes.index');
Route::get('/sessoes/{slug}', 'Portal\SessionsController@show')->name('sessoes.show');
```

### 1.1 Rotas do portal usadas *dentro* do módulo

| URI | Nome | Origem |
|---|---|---|
| `/portal/{slug1}/{slug2}` | `conteudo.portal.show` | Usada pelo `modelo1` e `modelo2` na listagem de sessões, **em vez** de `sessoes.show` (`v6/routes/web.php:60`). |
| `/portal/{slug1}/{slug2}/{slug3}` | `conteudo.portal.view` | Variante com três segmentos. |
| `/portal/{slug}` | `conteudo.portal.list` | Usada pelo breadcrumb `category-content` (`v6/routes/breadcrumbs.php:65-68`). |

Isso cria **duas URLs públicas** para o mesmo conteúdo:
`/sessoes/{slug}` (canônica do módulo) e `/portal/sessoes/{slug}` (canônica
genérica). Ver `08-GAPS-E-DIVIDAS.md`.

## 2. Rotas do CMS (AdminLTE)

Arquivo: `v6/routes/basepro.php`. Grupo externo:
`Route::group` com `prefix = cms` e `before = csrf` (linha 35).

### 2.1 Rotas específicas do módulo

| Método | URI | Nome | Controller@action | Middleware |
|---|---|---|---|---|
| GET | `/cms/conteudos/sessoes` | `listar-conteudo` | `Prefecture\ContentController@genericIndex` | `check.route.permission` (via construtor) |
| GET | `/cms/conteudos/cadastrar/sessoes` | `cadastrar-conteudo` | `Prefecture\ContentController@genericCreate` | `check.route.permission` |
| GET | `/cms/conteudos/editar/sessoes/{id}` | `editar-conteudo` | `Prefecture\ContentController@genericEdit` | `check.route.permission` |
| GET | `/cms/datatable/sessoes` | `datatable.sessoes` | `Prefecture\DatatableController@get_sessoes` | `check.route.permission` |

Rotas reais (`v6/routes/basepro.php:235-237, 398`):

```php
Route::get('conteudos/{slug}', ['uses' => 'Prefecture\ContentController@genericIndex'])->name('listar-conteudo');
Route::get('conteudos/cadastrar/{slug}', ['uses' => 'Prefecture\ContentController@genericCreate'])->name('cadastrar-conteudo');
Route::get('conteudos/editar/{slug}/{id}', ['uses' => 'Prefecture\ContentController@genericEdit'])->name('editar-conteudo');
```

```php
Route::get('/sessoes', 'Prefecture\DatatableController@get_sessoes')->name('sessoes');
```

### 2.2 Rotas genéricas de `contents` usadas pelo módulo

`Route::resource('conteudo', 'Prefecture\ContentController')`
(`v6/routes/basepro.php:174`) gera:

| Método | URI | Nome | Action | Usado por sessões |
|---|---|---|---|---|
| GET | `/cms/conteudo` | `conteudo.index` | `index` | Não (o CMS usa `listar-conteudo`). |
| GET | `/cms/conteudo/create` | `conteudo.create` | `create` | Não. |
| POST | `/cms/conteudo` | `conteudo.store` | `store` | **Sim** — cria a sessão. |
| GET | `/cms/conteudo/{id}` | `conteudo.show` | `show` | Não. |
| GET | `/cms/conteudo/{id}/edit` | `conteudo.edit` | `edit` | Não. |
| PUT | `/cms/conteudo/{id}` | `conteudo.update` | `update` | **Sim** — salva a sessão. |
| DELETE | `/cms/conteudo/{id}` | `conteudo.destroy` | `destroy` | Não (o CMS usa `generic.conteudo.delete`). |

Rotas auxiliares:

| Método | URI | Nome | Controller@action | Finalidade |
|---|---|---|---|---|
| DELETE | `/cms/conteudo/delete/{id}` | `generic.conteudo.delete` | `ContentController@genericDestroy` | Exclusão (lógica) de uma sessão. |
| POST | `/cms/conteudo/deleteAll` | `ajax.generic.content.deleteAll` | `ContentController@genericDeleteAll` | Exclusão em lote (checkbox da listagem). |
| POST | `/cms/ajax/ajax-delete-files` | `ajax.delete.files` | `ContentController@ajaxDeleteFiles` | Remoção da imagem de destaque. |
| POST | `/cms/ajax/ajax-delete-files-gallery` | `ajax.delete.files.gallery` | `ContentController@ajaxDeleteFilesofGallery` | Remoção de foto da galeria. |
| POST | `/cms/set-status` | `set.status` | `ContentController@setStatus` | Alterna `contents.status` (publicado/rascunho). |
| GET/POST/PUT | `/cms/galeria/*` | `galeria.*` | `Prefecture\ArchiveController` | Upload, ordem e exclusão de arquivos da galeria. |

## 3. Controllers do portal

### 3.1 `Portal\SessionsController`

Caminho: `v6/app/Http/Controllers/Portal/SessionsController.php`.
Herda de `App\Http\Controllers\Admin\Controller`, que fornece
`getTemplateActived()` (`v6/app/Http/Controllers/Admin/Controller.php:39-49`).

| Método | Assinatura | Comportamento |
|---|---|---|
| `index` | `index(): View` | Busca a categoria `sessoes`, pagina 12 conteúdos publicados e vigentes, ordena por `publication_date DESC`, renderiza `portal.layouts.{layout}.sessoes.index` com `title`, `sessions`, `category`. |
| `show` | `show(string $slug): View` | Localiza o conteúdo **dentro** da categoria `sessoes` (`firstOrFail`), carrega os anexos sem `highlight`, calcula `$linkVideo` e renderiza `portal.layouts.{layout}.sessoes.show`. |

Comportamento de 404 do controller de base:

```php
protected function getTemplateActived()
{
    $templateActived = Configuration::where('key', 'layout')->first()->value;

    if (!View::exists('portal.layouts.'.$templateActived.'.home.index')) {
        DB::table('configurations')->where('key', 'pagina_em_manutencao')->update(['value' => 1]);
        abort(404, 'view_inexistente');
    }

    return $templateActived;
}
```

Só os layouts `modelo1`..`modelo6` possuem `home/index.blade.php`; portanto
`default` e `newsbit` terminam em **404 `view_inexistente`** — e ambos também
não possuem a pasta `sessoes/`.

## 4. Controllers do CMS

### 4.1 `Prefecture\ContentController` — CRUD genérico

Caminho: `v6/app/Http/Controllers/Prefecture/ContentController.php`.
Construtor (linhas 58-68) registra `check.route.permission` e injeta
`Content $contentLog` e `IContentRepository $contentRepository`.

| Método | Linha | Papel para o módulo |
|---|---|---|
| `genericIndex($slug)` | 91 | Dispatch: `if ($slug === 'sessoes') return $this->returnIndex('Sessões', $slug);` (linha 109). |
| `returnIndex($title, $slug, $alt_click = null)` | 121 | `generateLog()`, `menuClicks($slug)` e renderiza `prefecture.contents.sessoes.index`. |
| `genericCreate($slug)` | 140 | Encaminha para `genericGenerate`. |
| `genericEdit($slug, $id)` | 158 | Encaminha para `genericGenerate` com `$id`. |
| `genericGenerate($slug, $id = null)` | 163 | `if ($slug === 'sessoes')` (linha 197) monta `returnData('Sessão', $slug, null, ['categoryContent' => <raiz sessoes>], $id)`. |
| `returnData($title, $slug, $self, $extra, $id)` | 275 | Carrega `content`, `archives`, `gallery`, `category`, `destaque` e resolve `create`/`edit`. |
| `store(ContentRequest $request)` | 616 | Grava `contents` (com `slug` derivado do título), cria o `link` do vídeo e o `archive` de destaque. |
| `update(ContentRequest $request, $id)` | 937 | Atualiza `contents`, o `link` e o `archive` de destaque. |
| `genericDestroy($id)` | 1369 | Exclusão lógica do conteúdo. |
| `setStatus(Request $request)` | 1548 | Alterna `status`; ao final redireciona para `listar-conteudo` com o slug. |

### 4.2 `Prefecture\DatatableController` — listagem server-side

Caminho: `v6/app/Http/Controllers/Prefecture/DatatableController.php`.

```php
public function get_sessoes()
{
    $sessoes = DB::table('sessoes')->get();
    return DataTables::of($this->struct_views($sessoes, 'sessoes'))->make(true);
}
```

`struct_views($data, $permission)` (linha 504) anexa a cada linha:

| Flag | Origem | Uso na UI |
|---|---|---|
| `edit` | `has_access('sessoes.edit')` | Habilita o botão de editar e o botão de galeria. |
| `delete` | `has_access('sessoes.delete')` | Habilita o botão de excluir e o checkbox de exclusão em lote. |
| `active` | `has_access('sessoes.restore')` | Reservado para o badge de status ativo. |
| `inactivate` | `has_access('sessoes.inactivate')` | Reservado para o badge de status inativo. |

## 5. Form Requests / validação

`v6/app/Http/Requests/ContentRequest.php` resolve o slug a partir do
`category_slug` (ou do `category_contents_id`) e aplica um `switch ($slug)`.
O caso `sessoes`:

```php
case 'sessoes':

    $rules = [
        "title" => "required|max:150",
        "publication_date" => "required",
        "link" => "max:2999",
        "text" => "required",
        "archive_highlight" => "max:20000|mimes:jpg,jpeg,png,gif,webp",
    ];
    break;
```

| Regra | Efeito observável |
|---|---|
| `title` required, max 150 | Bloqueia gravação sem título e limita o tamanho. |
| `publication_date` required | Garante a data/hora; sem ela a sessão não sairia do rascunho no portal. |
| `text` required | Exige o corpo da sessão. |
| `link` max 2999 | Limita a URL do vídeo (coluna `links.link` é `varchar(3000)`). |
| `archive_highlight` max 20000 + mimes | Imagem de destaque opcional, até 20 MB, apenas `jpg/jpeg/png/gif/webp`. |

**Não há** regras específicas para `galery`, `status`, `category_contents_id` ou
`slug` no caso `sessoes` — todos são tratados como opcionais pelo validador
genérico.

Validações extras aplicadas fora do `switch`:

- `archive.*`: extensões `rar, zip, doc, docx, pdf, odt, xls, xlsx, xlsb, xslm,
  ppt, pptx` aceitas para anexos adicionais (código em
  `v6/app/Http/Requests/ContentRequest.php:41-61`);
- o `slug` só é regerado no `store` (`generateSlug`, linha 584); no `update`
  o campo não vem do formulário e o valor existente é preservado.

## 6. Permissões exigidas

| Recurso / ação | Permissão Sentinel | Onde é verificada |
|---|---|---|
| Ver o item de menu "Sessões" no CMS | `sessoes.index` + `modulos.status = 1` | `v6/app/Providers/MenuServiceProvider.php:46-53` |
| Abrir a listagem `/cms/conteudos/sessoes` | `sessoes.index` | `hasAccessSegment()` em `v6/app/Http/Middleware/CheckRoutePermission.php` (segmento 3 = `sessoes`) |
| Abrir o formulário de criação | `sessoes.create` | `has_access(['sessoes.create'])` em `v6/resources/views/prefecture/contents/sessoes/index.blade.php` |
| Abrir o formulário de edição | `sessoes.edit` | `hasAccessSegment()` (segmento 3 = `editar`, segmento 4 = `sessoes`) |
| Gravar criação/edição | `sessoes.create` / `sessoes.edit` | `CheckRoutePermission` sobre `conteudo.store` / `conteudo.update` |
| Excluir | `sessoes.delete` | `has_access(['sessoes.delete'])` e flag `delete` de `struct_views` |
| Listagem DataTable | liberada para qualquer usuário logado | nome da rota `datatable.sessoes` contém o segmento `datatable`, listado em `openRoutes` (`v6/app/Http/Middleware/CheckRoutePermission.php:50-58`) |

Base das permissões: `v6/database/seeds/ResourcesTableSeeder.php:486-493`
(cria o recurso `sessoes` ligado ao módulo 40). A função `has_access()` está
em `v6/app/Library/Helper/appHelpers.php:22`; o papel `super-admin` passa em
tudo.

## 7. Breadcrumbs

`v6/routes/breadcrumbs.php`:

```php
// Home > [Category Content]
Breadcrumbs::register('category-content', function ($breadcrumbs, $post) {
    $breadcrumbs->parent('home');
    $breadcrumbs->push($post->name, route('conteudo.portal.list', $post->slug));
});

// Home > [Category Content] > [Content]
Breadcrumbs::register('content', function ($trail, $post) {
    $trail->parent('category-content', $post->categoryContents);
    $trail->push($post->title, route('conteudo.portal.show', [$post->categoryContents->slug, $post->slug,]));
});
```

As views de sessão usam `Breadcrumbs::render('category-content', $category)`
(na listagem) e `Breadcrumbs::render('content', $session)` (no detalhe). O
breadcrumb do detalhe aponta para `/portal/sessoes/{slug}`, **não** para
`/sessoes/{slug}` — divergência de URL dentro do próprio módulo.

## 8. API / Resources

O módulo **não expõe API JSON**. Não há `App\Http\Resources` para sessões nem
rotas em `v6/routes/api.php` relacionadas. A única saída estruturada é o
payload do DataTables em `/cms/datatable/sessoes` (JSON com as colunas da view
`sessoes` mais as flags `edit`, `delete`, `active`, `inactivate`).
