# Arquivo (Drive) — Rotas e Controllers

> **Escopo:** v5. Rotas do CMS em `v5/routes/basepro.php`. Nenhuma rota
> pública de arquivo existe em `v5/routes/web.php` (verificado por `grep`).

---

## 1. Rotas do CMS

Todas as rotas abaixo vivem no grupo `Route::group(['prefix' => 'cms', 'before' => 'csrf'], …)`
aberto em `v5/routes/basepro.php:37` e são carregadas com o middleware `web`
(`v5/app/Providers/RouteServiceProvider.php:85-89`).

### 1.1 Galeria de arquivos (`/cms/galeria`)

Definidas em `v5/routes/basepro.php:381-385`:

```php
Route::group(['prefix' => 'galeria', 'as' => 'galeria.'], function () {
    Route::post('/', 'Prefecture\ArchiveController@saveArchivesFromGalleryOrDriver');
    Route::get('/{column?}/{id?}', 'Prefecture\ArchiveController@getArchivesFromGalleryOrDriver');
    Route::post('/delete/{id}', 'Prefecture\ArchiveController@deleteArchives');        
});
```

| Método | URI | Nome | Controller@action | Middleware | Descrição |
|---|---|---|---|---|---|
| POST | `/cms/galeria` | `galeria.` | `ArchiveController@saveArchivesFromGalleryOrDriver` | `web` | Upload (multipart `file`). |
| GET | `/cms/galeria/{column?}/{id?}` | `galeria.` | `ArchiveController@getArchivesFromGalleryOrDriver` | `web` | Lista arquivos de um escopo ou do Drive. |
| POST | `/cms/galeria/delete/{id}` | `galeria.` | `ArchiveController@deleteArchives` | `web` | Soft delete. |

> O grupo declara `as => 'galeria.'`, mas **nenhuma das três rotas chama
> `->name()`**. No Laravel 5.5, o prefixo `as` de um grupo só é aplicado
> quando a rota tem nome próprio (`Route::name()` concatena em
> `$action['as']` — `v5/vendor/laravel/framework/src/Illuminate/Routing/Route.php:638-643`).
> Verificado em `php artisan route:list`: as três rotas aparecem com o nome
> **`galeria.`** (prefixo herdado, sem sufixo). O nome é, portanto,
> **colisor** entre as três e não serve para `route()`.

### 1.2 Categorias de arquivos (`/cms/drive/*`)

Definidas em `v5/routes/basepro.php:387-390`:

```php
//Categorias de Arquivos
Route::get('/drive/categorias', 'Prefecture\ArchiveCategoryController@getCategories')->name('categories.drive');
Route::post('/drive/cadastrar/categoria', 'Prefecture\ArchiveCategoryController@store')->name('categories.drive.store');
Route::post('/drive/atualizar-ordem', 'Prefecture\ArchiveController@updateOrder')->name('drive.update.order');
```

| Método | URI | Nome | Controller@action | Middleware | Descrição |
|---|---|---|---|---|---|
| GET | `/cms/drive/categorias` | `categories.drive` | `ArchiveCategoryController@getCategories` | `web` | Lista categorias (JSON). |
| POST | `/cms/drive/cadastrar/categoria` | `categories.drive.store` | `ArchiveCategoryController@store` | `web` | Cria categoria (`name`). |
| POST | `/cms/drive/atualizar-ordem` | `drive.update.order` | `ArchiveController@updateOrder` | `web` | Troca `order` entre dois arquivos. |

> A terceira rota está no bloco de "Categorias de Arquivos" mas pertence ao
> `ArchiveController` — é a reordenação por drag-and-drop do Drive.

### 1.3 Ordenação de galeria de conteúdo (`/order`)

Definida **fora** do grupo `cms`, em `v5/routes/basepro.php:553-555`:

```php
// Ordenando imamgens na galeria
Route::post('/order', 'Prefecture\ArchiveController@defaultOrder')->name('fetch.default.order');
Route::put('/order', 'Prefecture\ArchiveController@putOrder')->name('fetch.set.order');
```

| Método | URI | Nome | Controller@action | Middleware | Descrição |
|---|---|---|---|---|---|
| POST | `/order` | `fetch.default.order` | `ArchiveController@defaultOrder` | `web` | Atribui `order` aos itens ainda sem ordenação. |
| PUT | `/order` | `fetch.set.order` | `ArchiveController@putOrder` | `web` | Reatribui `order = índice + 1`. |

> Estas duas rotas são **raiz do site** (`/order`), não `/cms/order` — e o
> chamador (`v5/public/assets/js/custom/utilities/helpers/sortable-galerias.js`)
> usa exatamente `fetch('/order', …)`. O arquivo JS **não é incluído por
> nenhuma view** (verificado por `grep -rn "sortable-galerias" v5/resources/`),
> portanto o par de rotas é **código morto** na v5.

### 1.4 Rotas correlatas (fora do módulo, mas usam `archives`)

| Método | URI | Controller@action | Nome | Origem |
|---|---|---|---|---|
| DELETE | `/cms/secretarias_paginas/arquivo/{id}` | `PagesSecretaryController@deleteArchive` | — | `v5/routes/basepro.php:375` |
| POST | `/cms/ajax/ajax-update-path` | `ContentController@pathUpdate` | `ajax.update.path.archive` | `v5/routes/basepro.php:121` |
| POST | `/cms/ajax-upload-files` | `ContentController@ajaxUploadFiles` | `ajax.upload.files` | `v5/routes/basepro.php:113` |
| POST | `/cms/ajax-static-upload-files` | `StaticPageController@ajaxUploadFiles` | `ajax.static.upload.files` | `v5/routes/basepro.php:124` |
| POST | `/cms/galerias-obras` | `Works\WorkController@saveArchivesFromGallery` | `galeria-obras.` | `v5/routes/basepro.php:538-541` |
| GET | `/cms/galerias-obras/{id}` | `Works\WorkController@getArchivesFromGallery` | `galeria-obras.` | idem |
| GET | `/deletar-arquivo/{id}` | `RequerimentoDiariasController@delete_archives` | `deletar-arquivo` | `v5/routes/basepro.php:568` |
| GET | `/deletar-arquivo-padrao` | `RequerimentoDiariasController@destroy_archive` | `deletar-arquivo-padrao` | `v5/routes/basepro.php:567` |

## 2. Ausência de rotas no portal

`v5/routes/web.php` **não contém nenhuma rota de arquivo** (verificado).
O público nunca acessa `archives` por API; ele consome o resultado de
controllers de portal que já filtram os arquivos (ex.
`v5/app/Http/Controllers/Portal/NewsController.php:75` →
`Archive::where('contents_id', …)->where('view_pdf', true)->first()`).

## 3. `ArchiveController`

**Arquivo:** `v5/app/Http/Controllers/Prefecture/ArchiveController.php` (236 linhas)
**Base:** `App\Http\Controllers\Prefecture\Controller`
**Traits:** `FileTrait`

### 3.1 Construtor

```php
public function __construct()
{
    $this->repository = new ArchiveRepository();
}
```

Não chama `parent::__construct()` → **não registra `check.route.permission`**
(ver `01-ARQUITETURA.md` §5 e `08-GAPS-E-DIVIDAS.md` B01).

### 3.2 Métodos

| Método | Linha | Assinatura | Retorno | Observações |
|---|---|---|---|---|
| `defaultOrder` | 51 | `defaultOrder(Request $request)` | `response(['status' => 'success'])` | Lê `$request->list` e `$request->contents_id`; delega a `AppTrait::order()`. |
| `putOrder` | 69 | `putOrder(Request $request)` | `response(['status' => 'success'])` | Delega a `AppTrait::reorder()`. |
| `saveArchivesFromGalleryOrDriver` | 87 | `…(Request $request): JsonResponse` | JSON `{success, data}` | Ver §3.3. |
| `getArchivesFromGalleryOrDriver` | 167 | `…(string $colunm = null, int $id = null): JsonResponse` | JSON `{data: [...]}` | Ver §3.4. |
| `deleteArchives` | 185 | `deleteArchives($id)` | JSON `{success}` / `{erro}` | `destroy($id)` (soft delete). |
| `updateOrder` | 199 | `updateOrder(Request $request)` | JSON `{success}` | Ver §3.5. |

### 3.3 `saveArchivesFromGalleryOrDriver` — regras

Entrada aceita:

| Campo | Tipo | Obrigatório | Uso |
|---|---|---|---|
| `file` | UploadedFile | **sim** (não há validação explícita; erro 500 se ausente) | Arquivo a gravar. |
| `table` | string | não | `content` \| `calendar_event` \| `static_pages`. Ausente → bucket "midias". |
| `id` | int | sim se `table` presente | id do conteúdo/evento/página. |
| `nameFile` | string | não | Nome desejado (sem extensão). |
| `category` | int | não | `archive_categories.id`. |

Derivação do diretório:

```php
if ($table === 'content') {
    $content = Content::find($id);
    $category = CategoryContent::find($content->category_contents_id);
    $dir = '/content/' . $category->slug . '/' . $content->id . '/galeria';
    $path = 'storage' . $dir;
    $column = 'contents_id';
} else if ($table === 'calendar_event') {
    $content = CalendarEvent::find($id);
    $category = CategoryCalendarEvent::find($content->category_calendar_events_id);
    $dir = '/calendar-events/' . $category->slug . '/' . $content->id . '/galeria';
    $path = 'storage' . $dir;
    $column = 'calendar_events_id';
} else if ($table === 'static_pages') {
    $content = StaticPage::find($id);
    $dir = '/statics/' . $content->slug . '/galeria';
    $path = 'storage' . $dir;
    $column = 'static_pages_id';
}

if (!isset($dir)) {
    $dir = '/content/midias';
    $path = 'storage/content/midias';
}
```

Gravação:

```php
$name = $this->saveUpload($request->file, $dir, null, $nameFile, true);

$data = [
    'name' => $name,
    'path' => "$path/$name",
    'extension' => $extension,
    'type' => $file->getMimeType(),
    'hash' => md5_file($file->getRealPath()),
    'category_id' => $category
];

if (isset($column)) {
    $data[$column] = $content->id;
}

if ($archive = Archive::create($data)) {
    if (strpos($path, 'midias') !== false) {
        $archive->order = $archive->id;
        $archive->save();
    }
    return response()->json([
        'success' => "Imagem foi cadastrada com sucesso!",
        'data' => $archive->load('category:id,name')
    ], 200);
}
```

Observações críticas:

- **`category_id` não é validado**: se a categoria não existir, o `INSERT`
  viola a FK `archives_category_id_foreign` e a exceção não é capturada
  (o `catch (Exception)` chama `dd($exception)`, que **interrompe a
  requisição com um dump de debug**).
- **`dd($exception)` no `catch`** — vaza stack trace e URL para o cliente.
- **`$nameFile` do usuário** é concatenado com a extensão; `saveUpload()`
  **não sanitiza** o nome (risco de `../` no nome — o `storeAs` do Laravel
  normaliza o path com `trim($path.'/'.$name, '/')`, mas não resolve
  `..`).
- `order = id` só para o bucket "midias"; os demais ficam `order = NULL`.

### 3.4 `getArchivesFromGalleryOrDriver` — regras

```php
public function getArchivesFromGalleryOrDriver(string $colunm = null, int $id = null): JsonResponse
{
    if ($id) {
        $data =  $this->repository->getModel()
            ->where($colunm, $id)
            ->with('category')
            ->whereNull('highlight')
            ->whereIn('extension', ["jpg", "jpeg", "png", "gif", "bmp", "tiff", "svg", "webp", "ico", "heic", "raw"])
            ->orderBy('order', 'DESC')
            ->orderBy('id', 'DESC');
    } else {
        $data =  $this->repository->getModel()->with('category')->where('path', 'like', '%midias%')->orderBy('order', 'DESC')->orderBy('id', 'DESC');
    }

    return response()->json(['data' => $data->where('extension', "!=", null)->with('category:id,name')->get(['id', 'path', 'extension', 'name', 'order', 'category_id'])], 200);
}
```

- O **nome do parâmetro está grafado errado** (`$colunm`), mas o Laravel
  resolve por posição, então funciona.
- **O nome da coluna vem da URL** (`{column?}`) e é interpolado em
  `->where($colunm, $id)`. O grammar do Laravel 5.5 **envolve o nome em
  aspas** (`wrapValue`), então **não há SQL injection** — verificado:
  `where('id) or 1=1 --', 1)` gera `"id) or 1=1 --" = ?` e o PostgreSQL
  responde `column does not exist`. É, porém, um **enumeração de colunas**
  não autorizada: qualquer nome de coluna da tabela pode ser consultado.
- `where('extension', '!=', null)` é traduzido pelo Laravel 5.5 para
  `IS NOT NULL` (comportamento correto nesta versão, verificado em execução).
- `with('category')` seguido de `with('category:id,name')` — a segunda
  chamada sobrescreve a primeira; a duplicação é inofensiva.
- Sem `id`: o filtro é `path LIKE '%midias%'` — **depende do texto
  "midias" no caminho**, não de um escopo formal. Arquivos do bucket
  padrão ficam de fora se o `path` tiver outro formato.

### 3.5 `updateOrder` — regras (reordenação do Drive)

```php
$dragged = DB::table('archives')->where('id', $draggedId)->first();
$target = DB::table('archives')->where('id', $targetId)->first();

// se por acaso tiverem a mesma ordem, ajusta temporariamente
if ($dragged->order == $target->order) {
    DB::table('archives')->where('id', $draggedId)->update(['order' => $target->order + 1]);
}

// troca de valores
DB::transaction(function () use ($dragged, $target) {
    DB::table('archives')->where('id', $dragged->id)->update(['order' => $target->order]);
    DB::table('archives')->where('id', $target->id)->update(['order' => $dragged->order]);
});
```

Armadilhas:

- Usa `DB::table()` → **ignora `SoftDeletes`** (arquivos já deletados
  participates da reordenação).
- **Não há escopo por módulo**: trocar a ordem entre um arquivo do Drive e
  um arquivo de um conteúdo é permitido (e happens).
- A "correção temporária" de ordem duplicada **não é revertida** e a
  transação troca os valores **antes** de ler os valores atuais — o
  resultado final depende da ordem de execução.
- `order` NULL: `null == null` é verdadeiro, então a comparação
  "mesma ordem" dispara para arquivos sem ordenação e `null + 1 = 1`.

## 4. `ArchiveCategoryController`

**Arquivo:** `v5/app/Http/Controllers/Prefecture/ArchiveCategoryController.php` (41 linhas)

```php
class ArchiveCategoryController extends Controller
{
    use FileTrait;
    public $table;

    public function __construct()
    {
        $this->table = new ArchiveCategory();
    }

    public function getCategories()
    {
        $archive_categories = $this->table::where('deleted_at', null)->get();
        return response()->json($archive_categories);
    }

    public function store(Request $request)
    {
        $data = $request->all();

        $archive_category = $this->table::create([
            'name' => $data['name'],
            'slug' => slug_fix($data['name']),
        ]);

        return response()->json([
            'success' => "Categoria cadastrada com sucesso!",
            'data' => $archive_category
        ], 200);
    }
}
```

Observações:

- **Sem `Form Request`**: `$data['name']` é acessado diretamente — sem
  `name` a requisição gera erro 500 (`Undefined index`).
- **Sem tratamento de duplicidade**: `slug` tem constraint UNIQUE; um
  `slug` repetido gera `QueryException` não capturada → 500. Verificado
  em execução: `duplicate key value violates unique constraint
  "archive_categories_slug_unique"`.
- **`slug_fix()` não é minúsculo**: verificado — `"TESTE-PROBE"` vira
  `"TESTEPROBE"` (a função remove o hífen e preserva a caixa). O slug
  gerado pode, portanto, colidir com um slug existente em caixa alta.
- **Não há `update` nem `destroy`**: uma categoria criada por engano é
  irreversível pela aplicação.
- Sobrescreve o construtor → **sem `check.route.permission`**.
- `use FileTrait` é **sem uso** (nenhum método de arquivo é chamado).
