# Gestão de Permissões — Rotas e Controladores

> Superfície exclusivamente CMS (`/cms/permissoes`). Não há rotas públicas nem
> API neste módulo.

## 1. Stack de middleware

As rotas são declaradas em
`V2/Modules/Infraestrutura/GestaoPermissoes/Routes/cms.php:10-26`:

```php
Route::group(['prefix' => 'cms/permissoes', 'middleware' => [\V2\Core\Middleware\CmsAuthenticate::class, 'v2.permission:gestao-permissoes']], function () {
```

Além desse grupo, o `V2ServiceProvider` envelopa todo `Routes/cms.php` com:

```php
$this->groupWithDomain(['web', 'module.active:' . $moduleName], function () use ($cmsRoutes, $controllerNamespace) {
    Route::namespace($controllerNamespace)->group($cmsRoutes);
});
```

(`V2/Core/Providers/V2ServiceProvider.php:521-525`)

Stack efetiva, na ordem:

| Ordem | Middleware | Efeito |
|---|---|---|
| 1 | `web` | sessão, CSRF, cookies |
| 2 | `module.active:GestaoPermissoes` | `ModuleAccessMiddleware`; 404/redirect se a flag estiver desligada |
| 3 | `CmsAuthenticate` | exige login Sentinel e ao menos um papel V1; chama o `V2PermissionMiddleware` internamente |
| 4 | `v2.permission:gestao-permissoes` | executa o `V2PermissionMiddleware` novamente |

O parâmetro `gestao-permissoes` **não é lido**: o método é
`handle($request, Closure $next)` (`V2PermissionMiddleware.php:22`). O módulo
efetivo vem do `active_pattern` `cms/permissoes/*`.

## 2. Tabela de rotas

| Método | URI | Nome | Controller@action | Nível mínimo efetivo |
|---|---|---|---|---|
| GET | `/cms/permissoes/permissions` | `v2.gestaopermissoes.permissions.index` | `GestaoPermissoesPermissionsController@index` | `read` |
| GET | `/cms/permissoes/permissions/create` | `v2.gestaopermissoes.permissions.create` | `create` | `read` no middleware, mas `write` no controller |
| POST | `/cms/permissoes/permissions` | `v2.gestaopermissoes.permissions.store` | `store` | `write` |
| GET | `/cms/permissoes/permissions/{id}/edit` | `v2.gestaopermissoes.permissions.edit` | `edit` | `read` no middleware, mas `write` no controller |
| POST | `/cms/permissoes/permissions/{id}` | `v2.gestaopermissoes.permissions.update` | `update` | `write` |
| GET | `/cms/permissoes/roles` | `v2.gestaopermissoes.roles.index` | `GestaoPermissoesRolesController@index` | `read` |
| GET | `/cms/permissoes/roles/create` | `v2.gestaopermissoes.roles.create` | `create` | `read` no middleware, mas `write` no controller |
| POST | `/cms/permissoes/roles` | `v2.gestaopermissoes.roles.store` | `store` | `write` |
| GET | `/cms/permissoes/roles/{id}/edit` | `v2.gestaopermissoes.roles.edit` | `edit` | `read` no middleware, mas `write` no controller |
| POST | `/cms/permissoes/roles/{id}` | `v2.gestaopermissoes.roles.update` | `update` | `write` |
| POST | `/cms/permissoes/roles/{id}/delete` | `v2.gestaopermissoes.roles.destroy` | `destroy` | `write` |

Observações:

- Todos os `POST` usam `Route::post`, inclusive edição e exclusão. Não há
  `PUT`/`PATCH`/`DELETE`; a semântica REST é apenas nominal.
- Não existe rota de “show” de perfil nem de usuário.
- `index` é a única ação sem `v2_can()` dentro do controller. Um usuário com
  nível `read` consegue listar perfis e usuários, mas não vê os botões de ação
  (as views usam `@v2can('GestaoPermissoes', 'write')`).

## 3. `GestaoPermissoesRolesController`

Fonte:
`V2/Modules/Infraestrutura/GestaoPermissoes/Controllers/GestaoPermissoesRolesController.php`.

| Ação | Comportamento | Referência |
|---|---|---|
| `index` | busca `search` em `name`/`slug` via `LIKE`; ordena por `name`, `slug`, `users_count` ou `created_at`; direção `asc/desc`; pagina 15 | `:37-86` |
| `create` | exige `v2_can('GestaoPermissoes','write')`; lista módulos, exceto `restricted` string | `:89-108` |
| `store` | valida `name` e `slug` únicos; bloqueia slug `super-admin`; remove módulos restritos; chama `createRole()` | `:110-161` |
| `edit` | exige `write`; resolve o nível efetivo de cada módulo com `resolvePermission()` antes de renderizar | `:166-198` |
| `update` | exige `write`; valida unicidade ignorando o próprio id; bloqueia renomear para `super-admin`; remove restritos; chama `updateRole()` | `:203-257` |
| `destroy` | exige `write`; chama `deleteRole()`, que recusa perfis com usuários | `:264-284` |

### Validação de `store`

```php
$this->validate($request, [
    'name' => 'required|string|max:255',
    'slug' => 'required|string|unique:v2_roles,slug|max:255',
], [
    'name.required' => 'O nome do perfil é obrigatório.',
    'slug.required' => 'O slug identificador é obrigatório.',
    'slug.unique' => 'Este slug já está sendo utilizado por outro perfil.',
]);
```

(`:116-123`)

### Validação de `update`

```php
'slug' => 'required|string|max:255|unique:v2_roles,slug,' . $id,
```

(`:209-214`)

### Filtro de módulos restritos

```php
$restrictedKeys = array_keys(array_filter($modules, function ($config) {
    return !empty($config['restricted']) && is_string($config['restricted']);
}));
if (!empty($restrictedKeys) && isset($data['permissions'])) {
    foreach ($restrictedKeys as $key) {
        unset($data['permissions'][$key]);
    }
}
```

(`:136-146`; repetido em `:229-239`)

Isso remove da matriz módulos como `SupportConfigs`, `MigracaoLegada` e
`StructuralLandingEditor`, que declaram `'restricted' => config('v2.support.email')`.

### Resolução na edição

```php
$rolePermissions = is_array($role->permissions_v2) ? $role->permissions_v2 : [];
$resolvedPermissions = [];
foreach ($modules as $moduleKey => $config) {
    $resolvedPermissions[(string) $moduleKey] = $this->permissionService->resolvePermission($rolePermissions, (string) $moduleKey);
}
```

(`:187-191`)

Essa é a correção para perfis antigos que gravaram slugs: evita exibir `none`
e sobrescrever o acesso ao salvar.

## 4. `GestaoPermissoesPermissionsController`

Fonte:
`V2/Modules/Infraestrutura/GestaoPermissoes/Controllers/GestaoPermissoesPermissionsController.php`.

| Ação | Comportamento | Referência |
|---|---|---|
| `index` | busca por `name`/`email`; pagina 15; carrega perfis da página em uma consulta e injeta `v2_role_name` | `:38-82` |
| `create` | exige `write`; lista papéis Sentinel V1 exceto `super-admin` e todos os perfis V2 | `:181-198` |
| `store` | exige `write`; valida dados; cria via `Sentinel::registerAndActivate`; grava `v2_role_id`; exige `role_id` V1 e recusa `super-admin` | `:200-271` |
| `edit` | exige `write`; carrega usuário e perfis V2 | `:84-106` |
| `update` | exige `write`; valida nome/e-mail/senha/perfil; grava; audita | `:108-179` |

### Validação de `store`

```php
$this->validate($request, [
    'name' => 'required|string|max:255',
    'email' => 'required|email|unique:users,email|max:255',
    'password' => 'required|string|min:6|confirmed',
    'role_id' => 'required|integer',
    'v2_role_id' => 'nullable|string|max:36|exists:v2_roles,id',
], [
    'name.required' => 'O nome é obrigatório.',
    'email.required' => 'O e-mail é obrigatório.',
    'email.email' => 'Informe um e-mail válido.',
    'email.unique' => 'Este e-mail já está cadastrado no sistema.',
    'password.required' => 'A senha é obrigatória.',
    'password.min' => 'A senha deve possuir pelo menos 6 caracteres.',
    'password.confirmed' => 'A confirmação de senha não coincide.',
    'role_id.required' => 'Selecione um papel de acesso.',
    'v2_role_id.max' => 'O formato do Perfil V2 é inválido.',
    'v2_role_id.exists' => 'O Perfil V2 selecionado não existe no sistema.',
]);
```

(`:207-225`)

### Validação de `update`

```php
$this->validate($request, [
    'name' => 'required|string|max:255',
    'email' => 'required|email|max:255|unique:users,email,' . $id,
    'password' => 'nullable|string|min:6|confirmed',
    'v2_role_id' => 'nullable|string|max:36|exists:v2_roles,id',
], [
    'name.required' => 'O nome é obrigatório.',
    'email.required' => 'O e-mail é obrigatório.',
    'email.email' => 'Informe um e-mail válido.',
    'email.unique' => 'Este e-mail já está sendo utilizado por outro usuário.',
    'password.min' => 'A senha deve possuir pelo menos 6 caracteres.',
    'password.confirmed' => 'A confirmação de senha não coincide.',
    'v2_role_id.max' => 'O formato do Perfil V2 é inválido.',
    'v2_role_id.exists' => 'O Perfil V2 selecionado não existe no sistema.',
]);
```

(`:114-129`)

### Criação no Sentinel

```php
$user = \Sentinel::registerAndActivate([
    'name' => $request->input('name'),
    'email' => $request->input('email'),
    'password' => $request->input('password'),
]);

if ($request->filled('v2_role_id')) {
    $user->v2_role_id = $request->input('v2_role_id');
    $user->save();
}

$role = \Sentinel::findRoleById($request->input('role_id'));
if ($role) {
    if ($role->slug === 'super-admin') {
        Log::warning('[V2 > CONTROLLER > Permissions] Tentativa de atribuição proibida de super-admin para ' . $user->email);
        return redirect()->back()->with('error', 'Ação não permitida.');
    }
    $role->users()->attach($user);
}
```

(`:228-253`)

Ponto de atenção: o usuário é criado e ativado **antes** da checagem de
`super-admin`. Se a checagem falhar, o usuário permanece criado (com
`v2_role_id` possivelmente gravado) e o retorno é um redirect de erro. Não há
transação envolvendo Sentinel + `users`.

### Regras de senha

- `store`: obrigatória, mínimo 6, confirmada.
- `update`: opcional; se preenchida, `Hash::make` e gravação; se vazia, a senha
  atual é preservada (`:143-147`).
- Não há política de complexidade, expiração ou histórico.

## 5. Form Requests

O módulo **não usa Form Requests**. As validações são inline via
`$this->validate()` nos controllers. Não há `Requests/` na pasta do módulo.

## 6. API Resources e presenters

Não existem. As respostas são:

- `Illuminate\View\View` nos GETs;
- `Illuminate\Http\RedirectResponse` nos POSTs, com `success`/`error` em sessão.

## 7. Permissões exigidas por ação

| Ação | Exigência real |
|---|---|
| Listar perfis/usuários | `read` ou `write` em `GestaoPermissoes` |
| Criar/editar/excluir perfil | `write` em `GestaoPermissoes` (controller) |
| Criar/editar usuário | `write` em `GestaoPermissoes` (controller) |
| Acessar o módulo | e-mail de suporte ou `super-admin` (bypass) ou nível ≠ `none` |
| Módulo desativado | `module.active` bloqueia antes de qualquer checagem de perfil |

O `super-admin` do Sentinel passa em todas as verificações, mesmo sem
`v2_role_id`. O e-mail de suporte também, e adicionalmente enxerga o grupo
`Support`.
