# Arquivo (Drive) — Endpoints, Views e Operação na v7

> **Complemento de `06-MIGRACAO-MODERNA.md`.** Este arquivo cobre os
> endpoints REST/JSON, os API Resources, as views Blade modernas, a
> operação (fila/cache/storage), os riscos de equivalência funcional e o
> sequenciamento sugerido.

---

## 1. Endpoints REST/JSON sugeridos

| Método | URI | Descrição |
|---|---|---|
| `GET` | `/api/v1/archives` | Lista paginada. Query: `attachable_type`, `attachable_id`, `category_id`, `extension`, `search`. |
| `POST` | `/api/v1/archives` | Upload (`multipart`). |
| `PATCH` | `/api/v1/archives/{archive}` | Atualiza título/descrição/categoria. |
| `DELETE` | `/api/v1/archives/{archive}` | Soft delete + enfileira limpeza do binário. |
| `PUT` | `/api/v1/archives/order` | Reordenação em lote (substitui `/order` e `/cms/drive/atualizar-ordem`). |
| `GET` | `/api/v1/archive-categories` | Lista categorias. |
| `POST` | `/api/v1/archive-categories` | Cria categoria. |
| `PATCH` | `/api/v1/archive-categories/{category}` | Renomeia. |
| `DELETE` | `/api/v1/archive-categories/{category}` | Exclui (reatribui os arquivos para `null`). |

### 1.1 Exemplo de reordenação transacional

```php
public function reorder(ReorderArchivesRequest $request): JsonResponse
{
    $type = ArchiveAttachmentType::from($request->input('attachable_type'));

    DB::transaction(function () use ($request, $type) {
        foreach ($request->validated('items') as $i => $item) {
            Archive::query()
                ->whereKey($item['id'])
                ->whereMorphedTo('attachable', $type, $request->integer('attachable_id'))
                ->update(['position' => $i + 1]);
        }
    });

    return response()->json(['message' => 'Ordem atualizada com sucesso.']);
}
```

> Diferença-chave em relação à v5: o escopo (`attachable_type` +
> `attachable_id`) é **obrigatório**, o que elimina a armadilha A05
> (troca de ordem entre arquivos de contextos diferentes).

## 2. API Resources

```php
class ArchiveResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id'         => $this->id,
            'title'      => $this->title,
            'description'=> $this->description,
            'name'       => $this->name,
            'extension'  => $this->extension,
            'mime_type'  => $this->mime_type,
            'size_bytes' => $this->size_bytes,
            'url'        => $this->public_url,
            'position'   => $this->position,
            'view_pdf'   => $this->view_pdf,
            'width'      => $this->width,
            'height'     => $this->height,
            'category'   => new ArchiveCategoryResource($this->whenLoaded('category')),
            'attachable' => [
                'type' => $this->attachable_type,
                'id'   => $this->attachable_id,
            ],
            'created_at' => $this->created_at?->toIso8601String(),
        ];
    }
}
```

> Substitui o JSON manual da v5 (`{data: [...]}`) e elimina a
> serialização **inconsistente** da v5 (o `with('category')` seguido de
> `with('category:id,name')`).

## 3. Views Blade modernas

### 3.1 Componente `<x-archive-gallery>`

```blade
{{-- resources/views/components/admin/archive-gallery.blade.php --}}
<x-admin.card title="Arquivos">
    <div x-data="archiveGallery(@js($endpoint), @js($attachable))">
        <div class="flex items-center gap-2">
            <x-admin.input wire:model="search" placeholder="Buscar por nome ou categoria" />
            <x-admin.select wire:model="categoryId">
                <option value="">Todas as categorias</option>
                @foreach ($categories as $c)
                    <option value="{{ $c->id }}">{{ $c->name }}</option>
                @endforeach
            </x-admin.select>
            <x-admin.button wire:click="$dispatch('open-upload')">Adicionar arquivo</x-admin.button>
        </div>

        <div class="grid grid-cols-5 gap-3 mt-4" x-ref="grid">
            <template x-for="item in items" :key="item.id">
                <div class="group relative" :data-id="item.id">
                    <template x-if="item.mime_type?.startsWith('image/')">
                        <img :src="item.url" class="rounded object-cover w-full h-32" loading="lazy" />
                    </template>
                    <template x-if="!item.mime_type?.startsWith('image/')">
                        <div class="h-32 grid place-items-center bg-gray-100 rounded">
                            <i class="fa fa-file-archive-o fa-3x text-gray-400"></i>
                        </div>
                    </template>
                    <p class="text-sm truncate mt-1" x-text="item.name"></p>
                    <p class="text-xs text-gray-500" x-text="item.category?.name"></p>
                </div>
            </template>
        </div>
    </div>
</x-admin.card>
```

### 3.2 Página do Drive

| View | Descrição |
|---|---|
| `resources/views/admin/drive/index.blade.php` | Galeria + busca + filtro + upload. |
| `resources/views/admin/drive/_upload-modal.blade.php` | Modal de upload (nome, categoria, arquivo). |
| `resources/views/admin/partials/archive-card.blade.php` | Card reutilizável (usado também nos formulários de conteúdo). |

### 3.3 Portal

| View | Descrição |
|---|---|
| `resources/views/portal/components/files-list.blade.php` | **Única** versão (hoje há 2: `default` e `view_pdf`). Recebe `$archives` e `$featured`. |
| `resources/views/portal/components/gallery.blade.php` | Galeria de fotos com Alpine + `lightgallery`. |

> Unificar as duas versões do bloco de arquivos elimina a divergência
> entre templates (`04-VIEWS-E-UX.md` §2.3).

### 3.4 Compressão de imagens

Substituir `FileTrait::compress()` (recursiva, GD, reescreve o original)
por um **job** com Intervention Image v3:

```php
class GenerateImageVariants implements ShouldQueue
{
    public function handle(): void
    {
        $path = Storage::disk('public')->path($this->archive->path);
        foreach ([['min', 200], ['med', 380], ['larg', 1480]] as [$suffix, $w]) {
            Storage::disk('public')->put(
                $this->variantPath($suffix),
                Image::read($path)->scaleDown(width: $w)->toWebp(quality: 82)
            );
        }
    }
}
```

- **Não** sobrescrever o original (a v5 sobrescreve e perde qualidade).
- Gravar os caminhos das variantes em colunas (`path_min`, `path_med`,
  `path_larg`) em vez de depender da convenção de nome — mais rápido e
  mais seguro que `file_exists()` em request.

## 4. Fila, cache e storage

| Item | Recomendação |
|---|---|
| **Storage** | `FILESYSTEM_DISK=public` por padrão; abstrair via `Storage::` para permitir S3. Caminho do banco **sem** o prefixo `storage/`. |
| **Fila** | `GenerateImageVariants`, `DeleteArchiveFile` (limpeza do binário), `NormalizeArchivePath` (migração de storage). `QUEUE_CONNECTION=database` ou `redis`. |
| **Cache** | Cachear a listagem de categorias (`Cache::remember('archive-categories', 3600, …)`) — é consultada em todo upload. **Invalidar** ao criar/renomear. |
| **Cache de listagem** | Cachear a resposta da galeria por `attachable_id` por ~60 s (o portal é lido por anonimos). Invalidar ao alterar `position`. |
| **Rate limit** | `throttle` nas rotas de upload (`60/min` por usuário). |

## 5. Riscos e decisões de equivalência funcional

> O que **NÃO pode mudar** sem aprovação do negócio.

| # | Comportamento da v5 | Risco na v7 |
|---|---|---|
| E1 | O portal exibe o bloco "Arquivos para Download" **só** em alguns templates (7 layouts, 2 implementações distintas) | Unificar muda a aparência conforme o template ativo. **Precisa de aprovação.** |
| E2 | `file_exist($archive->path)` **omite** silenciosamente o arquivo se o binário sumiu | Preservar: o bloco nunca deve quebrar a página por arquivo faltante. |
| E3 | A descrição **substitui** o nome do arquivo na listagem do portal | UX estranho, mas é o comportamento publicado. |
| E4 | O PDF é embutido **apenas em desktop** (detecção por User-Agent) | Preservar ou trocar por `<object>` responsivo? |
| E5 | A galeria só mostra thumbnails de `png/jpeg/jpg`; o resto vira ícone | Preservar a lista de extensões. |
| E6 | `highlight = true` = imagem de destaque; `NULL` = galeria; `false` = item de página de secretaria | Mover para `is_featured` boolean **exige** regra para o estado `false` (→ `false`). Migrar `NULL` → `false` e `highlight_id` → `attachable`. |
| E7 | O `order` é **parcial e inconsistente** entre fluxos | Definir um comportamento único (reordenar sempre) e reindexar tudo na migração. |
| E8 | A exclusão **não** apaga o binário | Mudar para apagar imediatamente quebra links publicados. Manter + job de limpeza. |
| E9 | `path` pode ser URL absoluta (respostas de licitação, contratos) | A migração de `path` **precisa** tratar esses registros; se não, os downloads do portal quebram. |
| E10 | As categorias são **obrigatórias no front** quando o modo avançado está ligado (`advanced_drive_settings_enabled != 0`) | Manter a flag de configuração e o comportamento condicional. |
| E11 | O "Drive" é identificado por `path LIKE '%midias%'` | Na v7 isso vira o escopo `attachable_type = drive` — mudança de semântica que precisa de migração de dados. |
| E12 | `slug` de categoria é gerado por `slug_fix()` (sem hífen, com caixa) | Migrar para `Str::slug()` **quebra URLs antigas** que usem o slug. Verificar se algum consumidor usa o slug (na v5, nenhum — o filtro é por `id`). |

## 6. Sequenciamento sugerido

1. **Fase 1 — Fundação:** tabelas `archives`, `archive_categories`, pivots;
   models; `ArchivePolicy`; endpoints de leitura/escrita.
2. **Fase 2 — Upload:** `StoreArchiveRequest`, `Storage::disk('public')`,
   job de variantes, tela do Drive com busca/filtro/ordenação.
3. **Fase 3 — Migração de dados:** script de leitura da v5 → v7
   (path, colunas polimórficas, `order` → `position`, `highlight` →
   `is_featured`), idempotente e com relatório de divergências.
4. **Fase 4 — Reconsumo:** ajustar cada módulo que hoje escreve em
   `archives` (conteúdos, estáticas, agenda, licitações, contratos,
   respostas, obras, emendas, ouvidoria, diárias) para usar o novo
   `ArchiveService`.
5. **Fase 5 — Portal:** unificar o bloco de arquivos e a galeria.
6. **Fase 6 — Limpeza:** remover `status_create`, `biddings_id`, `hash`
   (ou reativá-lo), colunas `*_id` legadas; remover
   `FileTrait::compress()` e `AppTrait::order()/reorder()`.

## 7. Serviço de aplicação sugerido

```php
class ArchiveService
{
    public function store(StoreArchiveRequest $r, ?ArchiveAttachmentType $type, ?int $id): Archive
    {
        $disk = Storage::disk('public');
        $name = Str::uuid()->'.'.$r->file->guessExtension();   // nome seguro

        $path = $r->file->storeAs($this->dirFor($type, $id), $name, 'public');

        return Archive::create([
            'name'       => $r->file->getClientOriginalName(),
            'title'      => $r->input('title'),
            'description'=> $r->input('description'),
            'path'       => $path,
            'extension'  => $r->file->getClientOriginalExtension(),
            'mime_type'  => $r->file->getMimeType(),
            'size_bytes' => $r->file->getSize(),
            'hash'       => hash_file('sha256', $r->file->getRealPath()),
            'category_id'=> $r->input('category_id'),
            'attachable_type' => $type?->value,
            'attachable_id'   => $id,
            'position'   => $this->nextPosition($type, $id),
            'user_id'    => auth()->id(),
        ]);
    }
}
```

Um serviço centralizado substitui a duplicação de
`saveUpload()` + `compress()` + `Archive::create()` espalhada por 8
controllers.
