ci / check (push) Successful in 1m7s
Document Bearer-authenticated multipart uploads with curl and Wget2. Return 413 for oversized multipart bodies and report attachment size limits accurately.
139 lines
4.1 KiB
Go
139 lines
4.1 KiB
Go
package api
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
|
|
"github.com/lgcosta/notas-online/internal/store"
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Gerenciador de arquivos: upload, árvore, static seguro, mover/renomear/excluir
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// handleFileUpload recebe multipart/form-data com campo "file" e parâmetros
|
|
// opcionais folder_id e note_id. O corpo inteiro é limitado a MaxFileBytes +
|
|
// folga de multipart (64 MiB + 64 KiB): sem o cap, o parser do multipart
|
|
// grava o corpo inteiro em temporários antes de qualquer checagem.
|
|
const maxUploadBody = store.MaxFileBytes + 64<<10
|
|
|
|
func (s *Server) handleFileUpload(w http.ResponseWriter, r *http.Request) {
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxUploadBody)
|
|
if err := r.ParseMultipartForm(8 << 20); err != nil {
|
|
var maxErr *http.MaxBytesError
|
|
if errors.As(err, &maxErr) {
|
|
writeError(w, http.StatusRequestEntityTooLarge, "upload excede o limite de 64 MiB")
|
|
} else {
|
|
writeError(w, http.StatusBadRequest, "multipart inválido")
|
|
}
|
|
return
|
|
}
|
|
defer r.MultipartForm.RemoveAll()
|
|
f, hdr, err := r.FormFile("file")
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, `campo "file" é obrigatório`)
|
|
return
|
|
}
|
|
defer f.Close()
|
|
meta, err := s.st.AddFile(hdr.Filename, r.FormValue("folder_id"), r.FormValue("note_id"), f)
|
|
if err != nil {
|
|
mapStoreErrorFor(w, err, "arquivo")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusCreated, map[string]any{
|
|
"id": meta.ID, "name": meta.Name, "folder": meta.Folder, "note": meta.Note,
|
|
"size": meta.Size, "mime": meta.MIME, "sha256": meta.SHA256,
|
|
"created_at": meta.CreatedAt, "url": "/api/files/static/" + meta.ID,
|
|
})
|
|
}
|
|
|
|
// handleFileTree devolve a hierarquia de pastas com arquivos + raiz solta.
|
|
func (s *Server) handleFileTree(w http.ResponseWriter, r *http.Request) {
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"tree": s.st.FileTree(),
|
|
"rootFiles": s.st.RootFiles(),
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleFileStatic(w http.ResponseWriter, r *http.Request) {
|
|
if err := s.st.ServeFile(w, r, r.PathValue("id")); err != nil {
|
|
mapStoreErrorFor(w, err, "arquivo")
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleFileDelete(w http.ResponseWriter, r *http.Request) {
|
|
if err := s.st.DeleteFile(r.PathValue("id")); err != nil {
|
|
mapStoreErrorFor(w, err, "arquivo")
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
type fileMoveInput struct {
|
|
Folder *string `json:"folder"` // ausente = mantém; "" = raiz
|
|
Note *string `json:"note"` // ausente = mantém; "" = avulso
|
|
Name string `json:"name"` // opcional (renomear junto)
|
|
}
|
|
|
|
func (s *Server) handleFileMove(w http.ResponseWriter, r *http.Request) {
|
|
var in fileMoveInput
|
|
if r.ContentLength > 0 {
|
|
if err := decodeJSON(w, r, &in, 1<<20); err != nil {
|
|
return
|
|
}
|
|
}
|
|
m, err := s.st.MoveFile(r.PathValue("id"), in.Name, in.Folder, in.Note)
|
|
if err != nil {
|
|
mapStoreErrorFor(w, err, "arquivo")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"id": m.ID, "name": m.Name, "folder": m.Folder, "note": m.Note,
|
|
"size": m.Size, "mime": m.MIME, "sha256": m.SHA256,
|
|
"created_at": m.CreatedAt, "url": "/api/files/static/" + m.ID,
|
|
})
|
|
}
|
|
|
|
type folderInput struct {
|
|
Name string `json:"name"`
|
|
Parent *string `json:"parent"`
|
|
}
|
|
|
|
func (s *Server) handleFolderCreate(w http.ResponseWriter, r *http.Request) {
|
|
var in folderInput
|
|
if err := decodeJSON(w, r, &in, 1<<20); err != nil {
|
|
return
|
|
}
|
|
parent := ""
|
|
if in.Parent != nil {
|
|
parent = *in.Parent
|
|
}
|
|
f, err := s.st.CreateFolder(in.Name, parent)
|
|
if err != nil {
|
|
mapStoreErrorFor(w, err, "pasta")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusCreated, f)
|
|
}
|
|
|
|
func (s *Server) handleFolderUpdate(w http.ResponseWriter, r *http.Request) {
|
|
var in folderInput
|
|
if err := decodeJSON(w, r, &in, 1<<20); err != nil {
|
|
return
|
|
}
|
|
f, err := s.st.UpdateFolder(r.PathValue("id"), in.Name, in.Parent)
|
|
if err != nil {
|
|
mapStoreErrorFor(w, err, "pasta")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, f)
|
|
}
|
|
|
|
func (s *Server) handleFolderDelete(w http.ResponseWriter, r *http.Request) {
|
|
if err := s.st.DeleteFolder(r.PathValue("id")); err != nil {
|
|
mapStoreErrorFor(w, err, "pasta")
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|