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(), }) } // handleFileArchiveList devolve a listagem amigável do conteúdo de um // compactado (.zip, .tar, .tar.gz/.tgz, .gz) sem extrair nada. func (s *Server) handleFileArchiveList(w http.ResponseWriter, r *http.Request) { entries, total, truncated, err := s.st.ListArchive(r.PathValue("id")) if err != nil { mapStoreErrorFor(w, err, "arquivo") return } writeJSON(w, http.StatusOK, map[string]any{ "entries": entries, "total": total, "truncated": truncated, }) } 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) }