summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/api/handlers.go22
-rw-r--r--internal/api/handlers_admin.go16
-rw-r--r--internal/api/handlers_auth.go5
-rw-r--r--internal/api/handlers_file.go4
-rw-r--r--internal/api/handlers_media.go34
-rw-r--r--internal/api/handlers_podcast.go11
-rw-r--r--internal/api/handlers_share.go8
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)