diff --git a/internal/server/files.go b/internal/server/files.go new file mode 100644 index 0000000..b75800d --- /dev/null +++ b/internal/server/files.go @@ -0,0 +1,88 @@ +package server + +import ( + "encoding/json" + "io" + "net/http" + "os" + "path/filepath" + "time" + "yoresee_dropbox/internal/store" +) + +func (s *Server) handleUpload(w http.ResponseWriter, r *http.Request) { + r.Body = http.MaxBytesReader(w, r.Body, 100<<20) + if err := r.ParseMultipartForm(32 << 20); err != nil { + http.Error(w, "File too large", http.StatusRequestEntityTooLarge) + return + } + + file, header, err := r.FormFile("file") + if err != nil { + http.Error(w, "Missing file", http.StatusBadRequest) + return + } + defer file.Close() + + storageName := generateUUID() + destPath := filepath.Join(s.workspace, "inbox", storageName) + dest, err := os.Create(destPath) + if err != nil { + http.Error(w, "Failed to save", http.StatusInternalServerError) + return + } + defer dest.Close() + + if _, err := io.Copy(dest, file); err != nil { + http.Error(w, "Failed to save", http.StatusInternalServerError) + return + } + + f := &store.File{ + ID: generateUUID(), + OriginalName: header.Filename, + StorageName: storageName, + Dir: "inbox", + Size: header.Size, + CreatedAt: time.Now().Unix(), + } + if err := s.store.FileCreate(f); err != nil { + http.Error(w, "Failed to save metadata", http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(map[string]any{"file": f}) +} + +func (s *Server) handleListFiles(w http.ResponseWriter, r *http.Request) { + dir := r.URL.Query().Get("dir") + files, err := s.store.FileList(dir) + if err != nil { + http.Error(w, "Failed to list", http.StatusInternalServerError) + return + } + json.NewEncoder(w).Encode(map[string]any{"files": files}) +} + +func (s *Server) handleDownload(w http.ResponseWriter, r *http.Request) { + id := r.PathValue("id") + f, err := s.store.FileGet(id) + if err != nil { + http.Error(w, "Not found", http.StatusNotFound) + return + } + + path := filepath.Join(s.workspace, f.Dir, f.StorageName) + w.Header().Set("Content-Disposition", "attachment; filename="+f.OriginalName) + http.ServeFile(w, r, path) +} + +func (s *Server) handleDeleteFile(w http.ResponseWriter, r *http.Request) { + id := r.PathValue("id") + if err := s.store.FileDelete(id); err != nil { + http.Error(w, "Failed to delete", http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusNoContent) +} diff --git a/internal/server/files_test.go b/internal/server/files_test.go new file mode 100644 index 0000000..ae70c16 --- /dev/null +++ b/internal/server/files_test.go @@ -0,0 +1,41 @@ +package server + +import ( + "bytes" + "mime/multipart" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + "yoresee_dropbox/internal/store" +) + +func TestUpload(t *testing.T) { + dir := t.TempDir() + workspace := filepath.Join(dir, "workspace") + os.MkdirAll(filepath.Join(workspace, "inbox"), 0755) + + s, _ := store.Open(filepath.Join(dir, "test.db")) + defer s.Close() + + srv := &Server{store: s, accessToken: "token", sessions: make(map[string]int64), workspace: workspace} + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, _ := writer.CreateFormFile("file", "test.txt") + part.Write([]byte("hello")) + writer.Close() + + req := httptest.NewRequest("POST", "/api/upload", body) + req.Header.Set("Content-Type", writer.FormDataContentType()) + req.AddCookie(&http.Cookie{Name: "ydropbox_session", Value: "valid"}) + srv.sessions["valid"] = 1 + + w := httptest.NewRecorder() + srv.handleUpload(w, req) + + if w.Code != http.StatusCreated { + t.Errorf("Status = %d, want %d", w.Code, http.StatusCreated) + } +}