# Arquivo (Drive) — Repositório, Validação, Permissões e Comparativo v6

> **Complemento de `03-ROTAS-E-CONTROLADORES.md`.** Este arquivo cobre o
> repositório, a validação de entrada, o formato das respostas, as
> permissões exigidas e o comparativo de rotas com a v6.

---

## 1. Repositório

`v5/app/Repositories/Eloquent/Prefecture/Archive/ArchiveRepository.php`:

```php
class ArchiveRepository extends Repository implements IArchiveRepository {

    public function __construct()
    {
        $this->model = new Archive;
    }

    protected function structureUpInsert($request): array
	{
        return $request;
    }

    public function upInsert($request, int $id = null)
    {
        if($archive = parent::upInsert($request, $id))
        {
            $request['path'] = str_replace('storage', '', $request['path']);
            $request['path'] = str_replace("/$request[name]", '', $request['path']);

            if($this->saveUpload($request[0], $request['path'], null, $request['name']))
                return $archive;
        }
        return false;
    }
}
```

- `IArchiveRepository` (`v5/app/Repositories/Contracts/IArchiveRepository.php`)
  é uma **interface vazia** — não há contrato a respeitar nem a testar.
- `upInsert()` é **sobrescrito** e **não grava o arquivo** (grava a
  linha e o arquivo separadamente, sem transação). Na prática **não é
  usado** por nenhum controller do módulo: as chamadas diretas são
  `Archive::create()`.
- `Repository::upInsert()` base engole a exceção com `dd($th)`
  (`v5/app/Repositories/Repository.php:63-67`).
- O `str_replace('storage', '', …)` remove o prefixo **em qualquer
  posição** da string, não só no início — um caminho com a palavra
  "storage" no meio seria truncado.

## 2. Form Requests / validação

**Não existe Form Request para o módulo.** Não há nenhuma classe em
`v5/app/Http/Requests/` que valide `archives` ou `archive_categories`
(verificado por `grep`).

A validação de upload existente é:

| Camada | Regra |
|---|---|
| JS | `acceptFiles: "image/jpeg, image/png, image/jpg, application/pdf, text/plain, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document"` (`v5/public/assets/js/custom/contents/midias/index.js:21`) |
| PHP | apenas `$request->file('file')->isValid()` implícito; `saveUpload()` chamaria `$file->getClientOriginalExtension()` e `getMimeType()` — **sem validação de tamanho, tipo real ou extensão maliciosa**. |

O `ContentController@ajaxUploadFiles` é mais rigoroso: define listas
`$imagensEstaticas`, `$imagens`, `$permitidosMidias` e retorna 422 para
extensão não permitida (`v5/app/Http/Controllers/Prefecture/ContentController.php:381-399`).

## 3. API Resources / presenters

**Não existem API Resources** para o módulo. As respostas são JSON
montado à mão com `response()->json([...])`, no formato:

```json
{ "data": [ { "id": 1, "path": "…", "extension": "png", "name": "…", "order": 3, "category_id": 2 } ] }
```

O `category` aparece como objeto aninhado quando o model está com a
relação carregada (`->load('category:id,name')`).

## 4. Permissões exigidas

### 4.1 O que o código pretende

`check.route.permission` (`v5/app/Http/Middleware/CheckRoutePermission.php`)
resolve o slug de permissão a partir do **nome da rota** ou do **segmento
da URL**:

```php
$access = Route::getCurrentRoute()->getName();
$accessArr = explode('.', $access);

$accessSegment = $this->hasAccessSegment();
$open = array_where($accessArr, function ($value, $key) use ($openRoutes, $resourceRoutes) {

    if (in_array($value, $openRoutes) || in_array($value, $resourceRoutes)) {
        return true;
    }
});

if (!empty($open)) {
    return true;
}

if ($access == "generic.conteudo.delete") $access = "conteudo.delete";

if ($access == "responder_chamado" || $access == "enviar_resposta") {
    $access = "ouvidoria.index";
}

if (str_contains($accessSegment, '_')) $accessSegment = str_replace('_', '-', $accessSegment);
if (str_contains($access, 'menu-item')) $access = str_replace('menu-item', 'menu', $access);
if (str_contains($access, 'painel.feriados-municipais')) $access = str_replace('painel.feriados-municipais', 'feriados-municipais', $access);

$slugs = resolve_permission_aliases([$access, $accessSegment]);

return Sentinel::check()->hasAnyAccess($slugs);
```

Para as rotas deste módulo os slugs seriam:

| Rota | `getName()` | Slug de permissão |
|---|---|---|
| `/cms/galeria` (POST/GET/DELETE) | `galeria.` | `galeria.` (inexistente na tabela `resources`) |
| `/cms/drive/categorias` | `categories.drive` | `categories.drive` |
| `/cms/drive/cadastrar/categoria` | `categories.drive.store` | `categories.drive.store` |
| `/cms/drive/atualizar-ordem` | `drive.update.order` | `drive.update.order` |
| `/order` (POST/PUT) | `fetch.default.order` / `fetch.set.order` | idem |

O resource equivalente na v5 é `resources.id = 44`, `slug = midias`,
`name = Drive` — o menu lateral usa `case 'midias':` em
`v5/app/Providers/MenuServiceProvider.php:283-285`. **Nenhum** dos slugs
acima corresponde a uma permissão cadastrada.

### 4.2 O que realmente acontece

**Nenhuma** dessas rotas executa `check.route.permission` (ver
`03-ROTAS-E-CONTROLADORES.md` §3.1 e `01-ARQUITETURA.md` §5).

Consequência verificada **em execução** (HTTP sem cookie de sessão):

| Requisição | Resultado |
|---|---|
| `GET /cms/drive/categorias` | **200** com o JSON das 4 categorias |
| `POST /cms/drive/cadastrar/categoria` (`{"name":"TESTE-PROBE"}`) | **200** — categoria **criada** (id 6), confirmada no banco e removida em seguida |
| `POST /cms/galeria/delete/{id}` | executa o controller (500 por id inexistente) — o soft delete é alcançado |
| `GET /cms/galeria` | **200** `{"data":[]}` |
| `PUT /order` com `{"list":[68],"contents_id":164}` | **200** `{"status":"success"}` — gravou `order = 1` (restaurado em seguida) |
| `POST /cms/drive/atualizar-ordem` `{"dragged_id":1,"target_id":2}` | **200** `{"success":"Ordem trocada entre 1 e 2."}` |
| `GET /cms/dashboard` (referência) | 302 → `/login` (o middleware funciona em outros controllers) |

O CSRF **é** validado (via `VerifyCsrfToken` no grupo `web`), mas o
token é público na tela de login (`<meta name="csrf-token" …>`), o que
**não** impede o abuso por um atacante com acesso à rede.

## 5. Comparação com a v6 (rotas)

| Rota | v5 | v6 |
|---|---|---|
| `POST /cms/galeria` | ✅ | ✅ |
| `GET /cms/galeria/{column?}/{id?}` | ✅ | ✅ |
| `POST /cms/galeria/delete/{id}` | ✅ | ✅ |
| `PUT /cms/galeria/update-title/{id}` | ❌ | ✅ (novo) |
| `POST /cms/galeria/update-order` | ❌ | ✅ (novo) |
| `GET /cms/drive/categorias` | ✅ | ❌ **removida** |
| `POST /cms/drive/cadastrar/categoria` | ✅ | ❌ **removida** |
| `POST /cms/drive/atualizar-ordem` | ✅ | ❌ **removida** |
| `POST /order` | ✅ | ✅ |
| `PUT /order` | ✅ | ✅ |

Detalhamento completo em `08-GAPS-E-DIVIDAS.md` §5.
