# VIAP — Rotas, Controllers e Autorização

> Todas as rotas abaixo foram confirmadas em runtime com
> `php artisan route:list` no container `v6_6f22e1bd-app-1` e por requisição
> HTTP direta (`curl -sk`).

---

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

Definidas em `v6/routes/basepro.php:189-192`, dentro do grupo
`Route::group(['prefix' => 'cms', 'before' => 'csrf'], ...)`
(`v6/routes/basepro.php:35`), carregado com o middleware `web` em
`v6/app/Providers/RouteServiceProvider.php:84-88`.

| Método | URI | Nome | Controller@action | Middleware (route:list) |
|---|---|---|---|---|
| GET | `cms/viap` | `viap.index` | `Prefecture\ViapController@index` | `web, check.route.permission` |
| GET | `cms/viap/create` | `viap.create` | `Prefecture\ViapController@create` | `web, check.route.permission` |
| POST | `cms/viap` | `viap.store` | `Prefecture\ViapController@store` | `web, check.route.permission` |
| GET | `cms/viap/{viap}` | `viap.show` | `Prefecture\ViapController@show` | `web, check.route.permission` |
| GET | `cms/viap/{viap}/edit` | `viap.edit` | `Prefecture\ViapController@edit` | `web, check.route.permission` |
| PUT/PATCH | `cms/viap/{viap}` | `viap.update` | `Prefecture\ViapController@update` | `web, check.route.permission` |
| DELETE | `cms/viap/{viap}` | `viap.destroy` | `Prefecture\ViapController@destroy` | `web, check.route.permission` |
| POST | `cms/viap/delete-por-lote` | `viap.delete-por-lote` | `Prefecture\ViapController@deleteByLote` | `web, check.route.permission` |
| POST | `cms/viap/ajax-delete-files` | `viap.ajax.delete.files` | `Prefecture\ViapController@ajaxDeleteFiles` | `web, check.route.permission` |
| GET | `cms/datatable/viap` | `datatable.viap` | `Prefecture\DatatableController@get_viap` | `web, check.route.permission` |

**Rota `viap.show` quebrada:** `ViapController` **não implementa** `show()`
(métodos existentes: `__construct`, `index`, `create`, `store`, `edit`,
`update`, `destroy`, `deleteByLote`, `ajaxDeleteFiles`, `getRelatedData`,
`extractedToArchive` — `v6/app/Http/Controllers/Prefecture/ViapController.php:24-171`).
Um GET em `/cms/viap/{id}` cai em `BadMethodCallException` (HTTP 500). Ver
Gap G-04.

### 1.1 Verificação runtime — acesso sem sessão

```
$ curl -sk -o /dev/null -w "%{http_code} -> %{redirect_url}\n" https://<host>/cms/viap
302 -> https://<host>/login
$ curl -sk -o /dev/null -w "%{http_code} -> %{redirect_url}\n" https://<host>/cms/viap/1
302 -> https://<host>/login
$ curl -sk -o /dev/null -w "%{http_code} -> %{redirect_url}\n" https://<host>/cms/datatable/viap
302 -> https://<host>/login
$ curl -sk -o /dev/null -w "%{http_code} -> %{redirect_url}\n" https://<host>/cms/viap/1/edit
302 -> https://<host>/login
```

**Conclusão:** as rotas de escrita do VIAP **não estão expostas a anônimos**.
O redirecionamento para `/login` vem do próprio `check.route.permission`
(`v6/app/Http/Middleware/CheckRoutePermission.php:33-36`), e não de
`check.logged`.

## 2. Rotas do Portal (`/viap`)

Definidas em `v6/routes/web.php:160-163`, dentro do grupo
`Route::middleware(['check.site.manutention'])->group(...)`
(`v6/routes/web.php:7`).

| Método | URI | Nome | Controller@action | Middleware (route:list) |
|---|---|---|---|---|
| GET | `viap` | `portal.viap.index` | `Portal\ViapPortalController@index` | `web, check.site.manutention` |
| GET/POST/PUT/PATCH/DELETE/OPTIONS | `viap/export` | `viap.export` | `Portal\ViapPortalController@index` | `web, check.site.manutention` |
| GET | `viap/{slug}` | `portal.viap.show` | `Portal\ViapPortalController@show` | `web, check.site.manutention` |

Observações:

- `viap/export` usa `Route::any` — aceita **GET** (usado pelos links de
  download) e também POST/PUT/DELETE. Não há restrição de método.
- A rota de export **não** é `portal.viap.export`; o nome é `viap.export`
  (global), o que pode colidir com nomes futuros.
- Breadcrumbs: `v6/routes/breadcrumbs.php:485-494` registra `viap` (Home >
  VIAP) e `viap-show` (Home > VIAP > registro), apontando para
  `route('portal.viap.index')` e `route('portal.viap.show', $viap->slug)`.

Verificação runtime (sem sessão): `/viap` → **200**, `/viap/export?type=csv` →
**200**. São rotas públicas por design.

## 3. Form Requests / validação

`App\Http\Requests\ViapRequest` (`v6/app/Http/Requests/ViapRequest.php`):

```php
public function authorize()
{
    return true;      // ← a autorização real é feita pelo middleware
}

public function rules()
{
    return [
        'publication_date' => 'required',
        'reference_month' => 'required|integer|min:1|max:12',
        'reference_year' => 'required|integer|min:1900',
        'link_type' => 'required|in:gabinete,outro_orgao',
        'councilman_id' => 'required_if:link_type,gabinete',
        'organ_id' => 'required_if:link_type,outro_orgao',
    ];
}
```

Mensagens customizadas (linhas 38-48): obrigatoriedade de data de publicação,
mês, ano, tipo de vínculo, parlamentar (quando gabinete) e órgão (quando outros
órgãos).

Pontos frágeis:

- `authorize()` retorna `true` **sempre** — quem decide a permissão é o
  middleware, não o Form Request.
- `amount` e `description` **não são validados** (e `amount` nem existe no
  formulário).
- Não há validação de `exists` para `councilman_id` / `organ_id` nem de
  unicidade do slug.
- `deleteByLote` e `ajaxDeleteFiles` recebem `Illuminate\Http\Request` puro,
  **sem nenhuma validação**: `toDelete` (array de ids) e `archive_id` são
  usados diretamente.

## 4. API Resources / presenters

**Não existem** API Resources nem presenters para o VIAP.

- As respostas do CMS são: HTML (Blade) ou JSON do Yajra DataTables
  (`DataTables::of(...)->make(true)`).
- As respostas do portal são: HTML (Blade), XLSX/CSV (`maatwebsite/excel`),
  PDF (dompdf) ou TXT puro.
- `Content` (usado para os órgãos) usa `PresentableTrait`
  (`v6/app/Http/Model/Prefecture/Content.php`), mas o VIAP não consome o
  presenter.

## 5. Autorização

### 5.1 Como o middleware decide

`App\Http\Middleware\CheckRoutePermission::handle()`
(`v6/app/Http/Middleware/CheckRoutePermission.php:20-38`):

```php
if (Sentinel::check()) {
    if (!Sentinel::inRole('super-admin')) {
        if (!$this->hasAccess()) {
            flash('Você não tem autorização para acessar esta funcionalidade!')->warning();
            return redirect()->back();
        }
    }
}
if (!Sentinel::check()) {
    return redirect()->route('login');
}
```

`hasAccess()` (linhas 41-95) resolve a permissão a partir do **nome da rota**:

1. Se algum segmento do nome da rota estiver em `$openRoutes`
   (`login, logout, datatable, dashboard, error, ajax, info-request-status-update`)
   ou em `$resourceRoutes` (`edit=>update, create=>store, delete=>destroy`,
   comparados **pelos valores**), a rota é **aberta** a qualquer usuário
   logado.
2. Caso contrário, exige `hasAnyAccess([$nomeDaRota, $accessSegment])`, onde
   `$accessSegment` é derivado da URL (`hasAccessSegment()`, linhas 97-118).

### 5.2 O construtor do controller

`ViapController::__construct` **sobrescreve** o construtor da classe base
`App\Http\Controllers\Prefecture\Controller` e **não** chama
`parent::__construct()`. Isso, isoladamente, removeria o middleware
`check.route.permission` — **mas o controller o registra de novo
explicitamente**:

```php
// v6/app/Http/Controllers/Prefecture/ViapController.php:24-29
public function __construct(IViapRepository $viapRepository, IArchiveRepository $archiveRepository)
{
    $this->middleware('check.route.permission');
    $this->viapRepository = $viapRepository;
    $this->archiveRepository = $archiveRepository;
}
```

Consequência prática: **não há rota de escrita do VIAP sem proteção de
autenticação** (confirmado em runtime, seção 1.1). O que resta é o problema
de **granularidade**, tratado a seguir.

### 5.3 Matriz de permissões (verificada em runtime)

Cenário: usuário logado com o papel `administrador` (as permissões `viap.*`
gravadas pelo seed). Reproduzido com o `StandardPermissions` do Sentinel sobre
as permissões reais do papel no banco e a mesma lógica de
`hasAccess()`/`hasAccessSegment()`:

| Rota | Veredito | Permissão avaliada |
|---|---|---|
| `viap.index` | **PERMITIDO** | `viap.index` |
| `viap.create` | **PERMITIDO** | `viap.create` |
| `viap.store` | **ABERTO a qualquer logado** | segmento `store` em `$resourceRoutes` |
| `viap.show` | PERMITIDO (mas **500**, método inexistente) | `viap.show` |
| `viap.edit` | **PERMITIDO** | `viap.edit` |
| `viap.update` | **ABERTO a qualquer logado** | segmento `update` em `$resourceRoutes` |
| `viap.destroy` | **ABERTO a qualquer logado** | segmento `destroy` em `$resourceRoutes` |
| `viap.delete-por-lote` | **NEGADO** | `viap.delete-por-lote` / `delete-por-lote.index` — **nenhuma** das duas existe no papel |
| `viap.ajax.delete.files` | **ABERTO a qualquer logado** | segmento `ajax` em `$openRoutes` |
| `datatable.viap` | **ABERTO a qualquer logado** | segmento `datatable` em `$openRoutes` |

Leituras:

- **Qualquer usuário logado no CMS** (inclusive com papel `basico`) pode
  **criar, editar e excluir** lançamentos de VIAP, porque os segmentos
  `store`/`update`/`destroy` estão em `$resourceRoutes`. O módulo **não** tem
  controle de escrita por permissão. Ver Gap G-05.
- O botão "Excluir Registro(s)" **aparece** (a view usa
  `has_access(['viap.delete'])`, que é `true` para `administrador`) mas o
  POST é **recusado** pelo middleware — o operador vê o botão e recebe
  "Você não tem autorização para acessar esta funcionalidade!". Ver Gap G-07.
- A leitura da listagem (`datatable.viap`) é aberta a qualquer logado.

### 5.4 Papéis

| Papel | Permissões VIAP | Origem |
|---|---|---|
| `super-admin` | todas (bypass no middleware) | `Sentinel::inRole('super-admin')` |
| `administrador` | `viap.index`, `viap.create`, `viap.edit`, `viap.delete`, `viap.inactive`, `viap.inactivate`, `viap.restore`, `viap.show` | `v6/database/seeds/ViapSeeder.php:61-74` |
| demais papéis | nenhuma | — |

O helper global `has_access()` (`v6/app/Library/Helper/appHelpers.php:22-42`)
é o usado nas views do CMS para exibir botões; retorna `false` para usuário
não logado e `true` para `super-admin`.

## 6. Resumo dos controllers

### `Prefecture\ViapController` (CMS)

| Método | Linha | Comportamento |
|---|---|---|
| `__construct` | 24 | Injeta `IViapRepository` e `IArchiveRepository`; registra `check.route.permission` |
| `index` | 31 | Registra atividade (`activity()->performedOn(new Viap())`), devolve `prefecture.viap.index` |
| `create` | 42 | Carrega gabinetes e órgãos (`getRelatedData()`), devolve `prefecture.viap.create` |
| `store` | 50 | `upInsert` + `extractedToArchive`; flash de sucesso/erro; redirect para `viap.index` |
| `edit` | 64 | `Viap::with('archives')->findOrFail($id)`; devolve `prefecture.viap.edit` |
| `update` | 77 | `upInsert($request, $id)` + `extractedToArchive`; flash; redirect |
| `destroy` | 91 | `viapRepository->destroy($id)`; JSON `{success}` ou `{erro}` |
| `deleteByLote` | 102 | `destroyAll($request->toDelete)`; JSON `true`/`false` ou `{erro}` |
| `ajaxDeleteFiles` | 112 | Apaga um `archives` pelo `archive_id`; JSON `{success}` ou `{erro}` |
| `getRelatedData` | 124 | Parlamentares ativos + órgãos da categoria `orgaos` |
| `extractedToArchive` | 134 | Itera `archive[i][]`, grava arquivo e faz `attach` no pivot |

### `Portal\ViapPortalController` (público)

| Método | Linha | Comportamento |
|---|---|---|
| `index` | 19 | Filtros (`link_type`, `councilman_id`, `organ_id`, `month`, `year`); paginação de 15; detecta `viap.export`; monta `$viaps`, `$months`, `$councilmen`, `$organs`, `$viap_static_page`; view `portal.layouts.{template}.viap.index` |
| `show` | 75 | Busca por `slug` + `active()` + `firstOrFail`; view `portal.layouts.{template}.viap.show` |
| `export` | 88 | `xls` → `Excel::download(new ViapExport(...), 'viap.xlsx')`; `pdf` → `PDF::loadView('exports.viap', ...)`; `csv` → `Excel::download(..., 'viap.csv')`; padrão → TXT |
| `gerarTxt` | 106 | Monta o texto tabular `Data Publicação | Referência | Vínculo` |
| `exportTxt` | 126 | `Response::make()` com `Content-Type: plain/txt` e `Content-Disposition: attachment` |

### `Prefecture\DatatableController::get_viap`

`v6/app/Http/Controllers/Prefecture/DatatableController.php:480-489`:

```php
public function get_viap()
{
    $viap = DB::table('view_viap')->get();
    $data = [];
    foreach ($viap as $index => $item) {
        $item->value = $index + 1;
        $data[] = $item;
    }
    return DataTables::of($this->struct_views($data, 'viap'))->make(true);
}
```

`struct_views($data, 'viap')` (linhas 504-520) injeta as flags
`edit`, `delete`, `active` e `inactivate` a partir de
`has_access('viap.edit' | 'viap.delete' | 'viap.restore' | 'viap.inactivate')`.
