feat(files): gerenciador de arquivos/anexos com árvore de pastas, upload, preview e picker
- store/files.go: pastas (parent, anti-ciclo), FileMeta (nota opcional = avulso),
blobs com extensão neutralizada p/ tipos perigosos, sanitização de nome
- api/files.go: upload multipart (64MiB), tree, static com ETag/Range + CSP
sandbox + attachment p/ html/svg/js (stored-XSS mitigado), move/rename/delete,
CRUD de pastas
- web: overlay Ctrl+J / 📎 (modo seletor insere sintaxe md/html/txt e associa),
filtros Todos/Anexos/Avulsos/Nota atual, busca, lightbox img/video/audio/pdf/texto,
colar-e-arrastar no editor e no gerenciador, ações por card (preview/associar/
mover/renomear/excluir), mobile-first (ações sempre visíveis)
- fix: api() já serializa o body — chamadas do gerenciador passavam JSON pré-string
(dupla codificação); auth por cookie (sem Bearer manual no fetch)
- sw v10; testes store+api (traversal, ciclo, XSS headers, range); README
This commit is contained in:
1 parent
ea6b3b915d
commit
119e6cefe9
10 files changed
+1605
-4
No files matched your search
@@ -301,6 +301,55 @@ No actix-web, o `fs::Files` já define MIME por extensão, mas registre antes:
|
||||
"application/manifest+json")` e um `wrap_fn` que sobrepõe `Cache-Control` quando o caminho for
|
||||
`/sw.js`. (No `tower-http` `SetResponseHeaderLayer` com `Match` por path é o caminho limpo.)
|
||||
|
||||
## Gerenciador de arquivos (anexos)
|
||||
|
||||
Árvore de pastas estilo sistema de arquivos + arquivos com **associação opcional
|
||||
a uma nota** (sem nota = avulso). Abra com **Ctrl+J**, menu **Arquivo →
|
||||
Gerenciador de arquivos…** ou o botão **📎 Anexo** (modo seletor: clicar num
|
||||
arquivo insere no editor e vincula à nota).
|
||||
|
||||
- **Persistência (disc)**: `data/folders.json` (árvore `parent`), `data/files.json`
|
||||
(metadados: nome, pasta, nota, tamanho, MIME, SHA-256) e blobs em
|
||||
`data/files/<id>.<ext>` — a extensão física de tipos perigosos (html/js/svg) é
|
||||
neutralizada (`.bin`/`.img`); o MIME real só vive no metadado.
|
||||
- **API**:
|
||||
- `POST /api/files/upload` — multipart (`file`, opcionais `folder_id`, `note_id`) → 201 com `url`
|
||||
- `GET /api/files/tree` — hierarquia JSON (pastas aninhadas + arquivos + raiz)
|
||||
- `GET /api/files/static/{id}` — serve com MIME real, **ETag (SHA-256) e Range**
|
||||
(vídeos fazem seek), `CSP: sandbox` + `nosniff`; tipos não-inline-seguros
|
||||
(html/svg/js…) vão como **`Content-Disposition: attachment`** — anexar um
|
||||
`.html` malicioso **não executa same-origin** (stored-XSS mitigado)
|
||||
- `PUT /api/files/{id}/move` — `{folder, note, name}` (mover/associar/desvincular/renomear)
|
||||
- `DELETE /api/files/{id}` · `POST/PUT/DELETE /api/folders[/{id}]` (com anti-ciclo)
|
||||
- Limite: 64 MiB/arquivo; nomes sanitizados (`filepath.Base` + whitelist); ids
|
||||
gerados — path do disco nunca aparece na URL.
|
||||
- **Editor**: colar (Ctrl+V) imagem/arquivo do clipboard faz upload automático e
|
||||
insere `` / `![]()` / link conforme o formato; arrastar-e-soltar
|
||||
funciona no editor e no gerenciador; no WYSIWYG a inserção é visual (`<img>`/
|
||||
`<video>`) e volta a Markdown na serialização; preview no gerenciador abre
|
||||
lightbox com `<img>`/`<video>`/`<audio>`/PDF (iframe) ou texto puro.
|
||||
|
||||
### Instruções de teste
|
||||
|
||||
```bash
|
||||
# 1) subir o servidor e criar uma nota pela UI (ou curl)
|
||||
export T=seu-token; B=http://127.0.0.1:8080
|
||||
# 2) upload avulso e em pasta
|
||||
FID=$(curl -s -X POST $B/api/files/folders -H "Authorization: Bearer $T" \
|
||||
-H 'Content-Type: application/json' -d '{"name":"docs","parent":""}' | jq -r .id)
|
||||
curl -s -X POST $B/api/files/upload -H "Authorization: Bearer $T" \
|
||||
-F file=@foto.png -F folder_id=$FID | jq .
|
||||
# 3) árvore + estático com Range/headers
|
||||
curl -s $B/api/files/tree -H "Authorization: Bearer $T" | jq
|
||||
curl -sI -H "Authorization: Bearer $T" -H 'Range: bytes=0-9' $B/api/files/static/<id>
|
||||
# 4) XSS guard: anexar .html deve vir attachment
|
||||
printf '<script>alert(1)</script>' > x.html
|
||||
curl -s -X POST $B/api/files/upload -H "Authorization: Bearer $T" -F file=@x.html | jq -r .id
|
||||
curl -sI -H "Authorization: Bearer $T" $B/api/files/static/<id> | grep -i disposition
|
||||
# 5) UI: Ctrl+J abre o gerenciador; cole uma imagem (Ctrl+V) no editor e arraste
|
||||
# arquivos para dentro dele; use 📎 para inserir um arquivo existente.
|
||||
```
|
||||
|
||||
## Como funciona o versionamento
|
||||
|
||||
- O conteúdo é guardado em `data/blobs/<aa>/<hash-sha256>` (content-addressed, como objetos do
|
||||
|
||||
Reference in new issue
Block a user