diff options
| -rw-r--r-- | internal/api/handlers.go | 22 | ||||
| -rw-r--r-- | internal/api/handlers_admin.go | 16 | ||||
| -rw-r--r-- | internal/api/handlers_auth.go | 5 | ||||
| -rw-r--r-- | internal/api/handlers_file.go | 4 | ||||
| -rw-r--r-- | internal/api/handlers_media.go | 34 | ||||
| -rw-r--r-- | internal/api/handlers_podcast.go | 11 | ||||
| -rw-r--r-- | internal/api/handlers_share.go | 8 |
7 files changed, 62 insertions, 38 deletions
diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 9fcb780..029d78b 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -41,6 +41,28 @@ func forbidden(w http.ResponseWriter, message string) { writeError(w, http.StatusForbidden, message) } +// handleError maps service sentinel errors to the appropriate HTTP status +// and writes a JSON error response. It falls back to 500 for unknown errors. +func handleError(w http.ResponseWriter, err error) { + switch { + case errors.Is(err, service.ErrNotFound), + errors.Is(err, service.ErrShareNotFound), + errors.Is(err, service.ErrMediaNotFound): + notFound(w) + case errors.Is(err, service.ErrForbidden): + forbidden(w, "forbidden") + case errors.Is(err, service.ErrAlreadyBootstrapped): + forbidden(w, "bootstrap already complete") + case errors.Is(err, service.ErrInvalidCredentials): + writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid credentials"}) + case errors.Is(err, service.ErrUnsupportedExtension), + errors.Is(err, service.ErrInvalidFeed): + badRequest(w, err.Error()) + default: + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + } +} + func readJSON(r *http.Request, dst interface{}) error { if r.Body == nil { return errors.New("missing body") diff --git a/internal/api/handlers_admin.go b/internal/api/handlers_admin.go index b1c3005..77e5b9f 100644 --- a/internal/api/handlers_admin.go +++ b/internal/api/handlers_admin.go @@ -16,7 +16,7 @@ func (s *Server) handleListTrash(w http.ResponseWriter, r *http.Request) { } items, err := s.adminSvc.ListTrash(r.Context()) if err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, items) @@ -27,7 +27,7 @@ func (s *Server) handleRescan(w http.ResponseWriter, r *http.Request) { return } if err := s.adminSvc.TriggerRescan(r.Context()); err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) @@ -46,7 +46,7 @@ func (s *Server) handleListUsers(w http.ResponseWriter, r *http.Request) { } users, err := s.adminSvc.ListUsers(r.Context()) if err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, users) @@ -67,7 +67,7 @@ func (s *Server) handleCreateUser(w http.ResponseWriter, r *http.Request) { } user, err := s.adminSvc.CreateUser(r.Context(), req.Username, req.Password, req.IsAdmin) if err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, user) @@ -84,7 +84,7 @@ func (s *Server) handleDeleteUser(w http.ResponseWriter, r *http.Request) { return } if err := s.adminSvc.DeleteUser(r.Context(), id); err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) @@ -96,7 +96,7 @@ func (s *Server) handleListPermissions(w http.ResponseWriter, r *http.Request) { } perms, err := s.adminSvc.ListPermissions(r.Context()) if err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, perms) @@ -116,7 +116,7 @@ func (s *Server) handleGrantPermission(w http.ResponseWriter, r *http.Request) { return } if err := s.adminSvc.GrantPermission(r.Context(), req.SetID, req.UserID, req.Role); err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) @@ -135,7 +135,7 @@ func (s *Server) handleRevokePermission(w http.ResponseWriter, r *http.Request) return } if err := s.adminSvc.RevokePermission(r.Context(), req.SetID, req.UserID); err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) diff --git a/internal/api/handlers_auth.go b/internal/api/handlers_auth.go index b0a1874..ed6b3e9 100644 --- a/internal/api/handlers_auth.go +++ b/internal/api/handlers_auth.go @@ -2,6 +2,7 @@ package api import ( "errors" + "fmt" "net/http" "time" @@ -42,7 +43,7 @@ func (s *Server) handleBootstrap(w http.ResponseWriter, r *http.Request) { forbidden(w, "bootstrap already complete") return } - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "internal server error"}) + handleError(w, fmt.Errorf("internal server error: %w", err)) return } @@ -70,7 +71,7 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid credentials"}) return } - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "internal server error"}) + handleError(w, fmt.Errorf("internal server error: %w", err)) return } diff --git a/internal/api/handlers_file.go b/internal/api/handlers_file.go index 386ec6e..ecf55e0 100644 --- a/internal/api/handlers_file.go +++ b/internal/api/handlers_file.go @@ -66,7 +66,7 @@ func (s *Server) handleDownload(w http.ResponseWriter, r *http.Request) { forbidden(w, "forbidden") return } - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } if res == nil { @@ -102,7 +102,7 @@ func (s *Server) handleRegenThumbnail(w http.ResponseWriter, r *http.Request) { forbidden(w, "forbidden") return } - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) diff --git a/internal/api/handlers_media.go b/internal/api/handlers_media.go index ef3e149..6396a39 100644 --- a/internal/api/handlers_media.go +++ b/internal/api/handlers_media.go @@ -24,7 +24,7 @@ func (s *Server) handleListSets(w http.ResponseWriter, r *http.Request) { } sets, err := s.browseSvc.ListSets(r.Context(), userIDFromContext(r)) if err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, sets) @@ -50,7 +50,7 @@ func (s *Server) handleGetSetCover(w http.ResponseWriter, r *http.Request) { forbidden(w, "forbidden") return } - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } w.Header().Set("Cache-Control", "no-cache") @@ -76,7 +76,7 @@ func (s *Server) handlePostSetCover(w http.ResponseWriter, r *http.Request) { forbidden(w, "forbidden") return } - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) @@ -98,7 +98,7 @@ func (s *Server) handleBrowseSet(w http.ResponseWriter, r *http.Request) { forbidden(w, "forbidden") return } - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, result) @@ -148,7 +148,7 @@ func (s *Server) handleUpload(w http.ResponseWriter, r *http.Request) { badRequest(w, err.Error()) return } - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, media) @@ -242,7 +242,7 @@ func (s *Server) handleListMedia(w http.ResponseWriter, r *http.Request) { dur := time.Since(start) if err != nil { s.logger.Error("api list media failed", "path", path, "set_id", setID, "set_ids", setIDs, "search", search, "type", typ, "favorites", fav, "min_duration", minDur, "max_duration", maxDur, "duration", dur, "err", err) - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } s.logger.Info("api list media", "path", path, "set_id", setID, "set_ids", setIDs, "search", search, "type", typ, "favorites", fav, "min_duration", minDur, "max_duration", maxDur, "returned", len(media), "duration", dur) @@ -260,7 +260,7 @@ func (s *Server) handleGetMedia(w http.ResponseWriter, r *http.Request) { } detail, err := s.browseSvc.GetMediaDetail(r.Context(), id, userIDFromContext(r)) if err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } if detail == nil { @@ -281,7 +281,7 @@ func (s *Server) handleFavorite(w http.ResponseWriter, r *http.Request) { } fav, err := s.favSvc.ToggleFavorite(r.Context(), userIDFromContext(r), id) if err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, map[string]bool{"favorite": fav}) @@ -293,7 +293,7 @@ func (s *Server) handleListTags(w http.ResponseWriter, r *http.Request) { } tags, err := s.tagSvc.ListTags(r.Context(), userIDFromContext(r)) if err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, tags) @@ -316,7 +316,7 @@ func (s *Server) handleAddTag(w http.ResponseWriter, r *http.Request) { return } if err := s.tagSvc.AssignTag(r.Context(), id, userIDFromContext(r), req.Tag); err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) @@ -333,7 +333,7 @@ func (s *Server) handleRemoveTag(w http.ResponseWriter, r *http.Request) { return } if err := s.tagSvc.RemoveTag(r.Context(), id, userIDFromContext(r), tagName); err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) @@ -357,7 +357,7 @@ func (s *Server) handleSoftDelete(w http.ResponseWriter, r *http.Request) { forbidden(w, "forbidden") return } - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) @@ -381,7 +381,7 @@ func (s *Server) handleRestore(w http.ResponseWriter, r *http.Request) { forbidden(w, "forbidden") return } - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) @@ -402,7 +402,7 @@ func (s *Server) handleGetNote(w http.ResponseWriter, r *http.Request) { } note, err := s.noteSvc.GetNote(r.Context(), id, userIDFromContext(r)) if err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } if note == nil { @@ -430,7 +430,7 @@ func (s *Server) handleUpsertNote(w http.ResponseWriter, r *http.Request) { } note := &model.Note{MediaID: id, UserID: userIDFromContext(r), Content: req.Content} if err := s.noteSvc.UpsertNote(r.Context(), note); err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, note) @@ -446,7 +446,7 @@ func (s *Server) handleDeleteNote(w http.ResponseWriter, r *http.Request) { return } if err := s.noteSvc.DeleteNote(r.Context(), id, userIDFromContext(r)); err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) @@ -485,7 +485,7 @@ func (s *Server) handleProgress(w http.ResponseWriter, r *http.Request) { req.Position, ) if err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) diff --git a/internal/api/handlers_podcast.go b/internal/api/handlers_podcast.go index 721e83e..13ea0d6 100644 --- a/internal/api/handlers_podcast.go +++ b/internal/api/handlers_podcast.go @@ -2,6 +2,7 @@ package api import ( "errors" + "fmt" "net/http" "strconv" @@ -19,7 +20,7 @@ func (s *Server) handleListPodcasts(w http.ResponseWriter, r *http.Request) { feeds, err := s.podcastSvc.ListFeeds(r.Context(), userIDFromContext(r)) if err != nil { s.logger.Error("list podcasts", "err", err) - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to list podcasts"}) + handleError(w, fmt.Errorf("failed to list podcasts: %w", err)) return } @@ -55,7 +56,7 @@ func (s *Server) handleSubscribePodcast(w http.ResponseWriter, r *http.Request) return } s.logger.Error("subscribe podcast", "err", err) - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to subscribe"}) + handleError(w, fmt.Errorf("failed to subscribe: %w", err)) return } @@ -95,7 +96,7 @@ func (s *Server) handleListEpisodes(w http.ResponseWriter, r *http.Request) { return } s.logger.Error("list episodes", "err", err) - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to list episodes"}) + handleError(w, fmt.Errorf("failed to list episodes: %w", err)) return } @@ -124,7 +125,7 @@ func (s *Server) handleDownloadEpisode(w http.ResponseWriter, r *http.Request) { return } s.logger.Error("download episode", "err", err) - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to download episode"}) + handleError(w, fmt.Errorf("failed to download episode: %w", err)) return } @@ -152,7 +153,7 @@ func (s *Server) handleToggleComplete(w http.ResponseWriter, r *http.Request) { return } s.logger.Error("toggle complete", "err", err) - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to toggle completion"}) + handleError(w, fmt.Errorf("failed to toggle completion: %w", err)) return } diff --git a/internal/api/handlers_share.go b/internal/api/handlers_share.go index 54ce60b..5c7db83 100644 --- a/internal/api/handlers_share.go +++ b/internal/api/handlers_share.go @@ -28,7 +28,7 @@ func (s *Server) handleCreateShare(w http.ResponseWriter, r *http.Request) { expiresAt := time.Now().Add(time.Duration(s.cfg.ShareDefaultExpiryDays) * 24 * time.Hour) share, err := s.shareSvc.CreateShare(r.Context(), userIDFromContext(r), id, expiresAt) if err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, share) @@ -45,7 +45,7 @@ func (s *Server) handleListShares(w http.ResponseWriter, r *http.Request) { } shares, err := s.shareSvc.ListShares(r.Context(), id, userIDFromContext(r)) if err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, shares) @@ -61,7 +61,7 @@ func (s *Server) handleRevokeShare(w http.ResponseWriter, r *http.Request) { return } if err := s.shareSvc.RevokeShare(r.Context(), token, userIDFromContext(r)); err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) @@ -208,7 +208,7 @@ func (s *Server) handleMyShares(w http.ResponseWriter, r *http.Request) { } shares, err := s.shareSvc.ListMyShares(r.Context(), userIDFromContext(r)) if err != nil { - writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + handleError(w, err) return } writeJSON(w, http.StatusOK, shares) |
