From af29deb33ee25800976b7122236bf7895a5ff39e Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Fri, 1 May 2026 21:10:43 +0300 Subject: refactor(api): split monolithic handlers.go into domain-specific files (task 3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split internal/api/handlers.go (1045 lines) to improve KISS/SRP: - handlers_auth.go – bootstrap, login, logout, health, session cookies - handlers_media.go – sets, media CRUD, tags, favorites, notes, progress - handlers_share.go – create/list/revoke shares, share page, share stream - handlers_admin.go – trash, rescan, users, permissions - handlers_file.go – stream, download, thumbnail, regenerate thumbnail Shared helpers (writeJSON, readJSON, pathID, serveFileResult, mimeTypeForFilename, etc.) remain in handlers.go. All tests pass: go test ./... -race -cover. --- internal/api/handlers_file.go | 108 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 internal/api/handlers_file.go (limited to 'internal/api/handlers_file.go') diff --git a/internal/api/handlers_file.go b/internal/api/handlers_file.go new file mode 100644 index 0000000..7e04de7 --- /dev/null +++ b/internal/api/handlers_file.go @@ -0,0 +1,108 @@ +package api + +import ( + "context" + "errors" + "net/http" + + "codeberg.org/snonux/player/internal/service" +) + +// ------------------------------------------------------------------ +// File serving handlers +// ------------------------------------------------------------------ + +func (s *Server) fileHandler(fn func(context.Context, int64, int64) (*service.FileResult, error)) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id := pathID(r, "id") + if id == 0 { + http.Error(w, "invalid media id", http.StatusBadRequest) + return + } + res, err := fn(r.Context(), id, userIDFromContext(r)) + if err != nil { + if errors.Is(err, service.ErrNotFound) { + http.Error(w, "not found", http.StatusNotFound) + return + } + if errors.Is(err, service.ErrForbidden) { + http.Error(w, "forbidden", http.StatusForbidden) + return + } + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if res == nil { + http.Error(w, "not found", http.StatusNotFound) + return + } + s.serveFileResult(w, r, res, false) + } +} + +func (s *Server) handleStream(w http.ResponseWriter, r *http.Request) { + if !requireService(w, s.mediaSvc) { + return + } + s.fileHandler(s.mediaSvc.StreamMedia)(w, r) +} + +func (s *Server) handleDownload(w http.ResponseWriter, r *http.Request) { + if !requireService(w, s.mediaSvc) { + return + } + id := pathID(r, "id") + if id == 0 { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid media id"}) + return + } + res, err := s.mediaSvc.DownloadMedia(r.Context(), id, userIDFromContext(r)) + if err != nil { + if errors.Is(err, service.ErrNotFound) { + writeJSON(w, http.StatusNotFound, map[string]string{"error": "not found"}) + return + } + if errors.Is(err, service.ErrForbidden) { + writeJSON(w, http.StatusForbidden, map[string]string{"error": "forbidden"}) + return + } + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + if res == nil { + writeJSON(w, http.StatusNotFound, map[string]string{"error": "not found"}) + return + } + s.serveFileResult(w, r, res, true) +} + +func (s *Server) handleThumbnail(w http.ResponseWriter, r *http.Request) { + if !requireService(w, s.mediaSvc) { + return + } + s.fileHandler(s.mediaSvc.GetThumbnail)(w, r) +} + +func (s *Server) handleRegenThumbnail(w http.ResponseWriter, r *http.Request) { + if !requireService(w, s.mediaSvc) { + return + } + id := pathID(r, "id") + if id == 0 { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid media id"}) + return + } + if err := s.mediaSvc.RegenerateThumbnail(r.Context(), id, userIDFromContext(r)); err != nil { + if errors.Is(err, service.ErrNotFound) { + writeJSON(w, http.StatusNotFound, map[string]string{"error": "not found"}) + return + } + if errors.Is(err, service.ErrForbidden) { + writeJSON(w, http.StatusForbidden, map[string]string{"error": "forbidden"}) + return + } + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) +} -- cgit v1.2.3