diff options
Diffstat (limited to 'internal/api')
| -rw-r--r-- | internal/api/handlers_config.go | 17 | ||||
| -rw-r--r-- | internal/api/handlers_media.go | 12 | ||||
| -rw-r--r-- | internal/api/handlers_more_test.go | 22 | ||||
| -rw-r--r-- | internal/api/handlers_podcast.go | 14 | ||||
| -rw-r--r-- | internal/api/handlers_podcast_test.go | 60 | ||||
| -rw-r--r-- | internal/api/handlers_test.go | 3 | ||||
| -rw-r--r-- | internal/api/server.go | 7 |
7 files changed, 113 insertions, 22 deletions
diff --git a/internal/api/handlers_config.go b/internal/api/handlers_config.go new file mode 100644 index 0000000..0fdefe7 --- /dev/null +++ b/internal/api/handlers_config.go @@ -0,0 +1,17 @@ +package api + +import ( + "net/http" + + "codeberg.org/snonux/player/internal" +) + +func (s *Server) handleConfig(w http.ResponseWriter, _ *http.Request) { + pageSize := internal.DefaultMediaPageSize + if s.cfg != nil && s.cfg.MediaPageSize > 0 { + pageSize = s.cfg.MediaPageSize + } + writeJSON(w, http.StatusOK, map[string]int{ + "media_page_size": pageSize, + }) +} diff --git a/internal/api/handlers_media.go b/internal/api/handlers_media.go index 8eed3cc..ef3e149 100644 --- a/internal/api/handlers_media.go +++ b/internal/api/handlers_media.go @@ -287,6 +287,18 @@ func (s *Server) handleFavorite(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]bool{"favorite": fav}) } +func (s *Server) handleListTags(w http.ResponseWriter, r *http.Request) { + if !requireService(w, s.tagSvc) { + return + } + tags, err := s.tagSvc.ListTags(r.Context(), userIDFromContext(r)) + if err != nil { + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + writeJSON(w, http.StatusOK, tags) +} + func (s *Server) handleAddTag(w http.ResponseWriter, r *http.Request) { if !requireService(w, s.tagSvc) { return diff --git a/internal/api/handlers_more_test.go b/internal/api/handlers_more_test.go index 5d210f2..bc0edf3 100644 --- a/internal/api/handlers_more_test.go +++ b/internal/api/handlers_more_test.go @@ -87,6 +87,28 @@ func TestNewGracefulServer(t *testing.T) { } } +func TestServer_Config(t *testing.T) { + store := buildSessionStore(1) + sm := auth.NewSessionManager(store, &clock.MockClock{T: time.Now()}, time.Hour) + cfg := &internal.Config{MediaPageSize: 37} + srv := newTestServer(t, store, nil, sm, cfg, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) + + req := httptest.NewRequest(http.MethodGet, "/api/config", nil) + req.AddCookie(addSessionCookie(t, store, sm, 1)) + rr := httptest.NewRecorder() + srv.ServeHTTP(rr, req) + if rr.Code != http.StatusOK { + t.Fatalf("expected %d, got %d", http.StatusOK, rr.Code) + } + var body map[string]int + if err := json.NewDecoder(rr.Body).Decode(&body); err != nil { + t.Fatalf("decode response: %v", err) + } + if body["media_page_size"] != 37 { + t.Fatalf("expected media_page_size 37, got %d", body["media_page_size"]) + } +} + func TestPingStore_nonPinger(t *testing.T) { store := &repository.MockStore{} srv := newTestServer(t, store, nil, nil, &internal.Config{}, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) diff --git a/internal/api/handlers_podcast.go b/internal/api/handlers_podcast.go index f93b15c..721e83e 100644 --- a/internal/api/handlers_podcast.go +++ b/internal/api/handlers_podcast.go @@ -13,25 +13,17 @@ import ( // ------------------------------------------------------------------ func (s *Server) handleListPodcasts(w http.ResponseWriter, r *http.Request) { - if !requireService(w, s.browseSvc) { + if !requireService(w, s.podcastSvc) { return } - userID := userIDFromContext(r) - sets, err := s.browseSvc.ListSets(r.Context(), userID) + 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"}) return } - // Filter to podcast sets only. - var podcasts []interface{} - for _, set := range sets { - if set.IsPodcast { - podcasts = append(podcasts, set) - } - } - writeJSON(w, http.StatusOK, podcasts) + writeJSON(w, http.StatusOK, feeds) } func (s *Server) handleSubscribePodcast(w http.ResponseWriter, r *http.Request) { diff --git a/internal/api/handlers_podcast_test.go b/internal/api/handlers_podcast_test.go index 963c2a0..12c6198 100644 --- a/internal/api/handlers_podcast_test.go +++ b/internal/api/handlers_podcast_test.go @@ -154,6 +154,25 @@ func TestPodcastE2E_FullFlow(t *testing.T) { })) defer rssServer.Close() + secondRSSBody := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?> +<rss version="2.0"> + <channel> + <title>Second Podcast</title> + <description>Another test podcast</description> + <item> + <title>Second Episode</title> + <guid>second-ep-1</guid> + <enclosure url="%s/second.mp3" length="4321" type="audio/mpeg"/> + </item> + </channel> +</rss>`, audioServer.URL) + secondRSSServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/rss+xml") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(secondRSSBody)) + })) + defer secondRSSServer.Close() + badRSSServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) @@ -189,6 +208,27 @@ func TestPodcastE2E_FullFlow(t *testing.T) { podcastSetID = feed.SetID }) + t.Run("subscribe second podcast uses same set", func(t *testing.T) { + body := fmt.Sprintf(`{"feed_url":"%s/rss.xml","set_name":"second-podcast"}`, secondRSSServer.URL) + req := httptest.NewRequest(http.MethodPost, "/api/podcasts", strings.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + req.AddCookie(cookie) + rr := httptest.NewRecorder() + srv.ServeHTTP(rr, req) + + if rr.Code != http.StatusOK { + t.Fatalf("expected %d, got %d: %s", http.StatusOK, rr.Code, rr.Body.String()) + } + + var feed model.PodcastFeed + if err := json.Unmarshal(rr.Body.Bytes(), &feed); err != nil { + t.Fatalf("unmarshal feed: %v", err) + } + if feed.SetID != podcastSetID { + t.Fatalf("expected set_id %d, got %d", podcastSetID, feed.SetID) + } + }) + t.Run("list podcasts", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/api/podcasts", nil) req.AddCookie(cookie) @@ -199,20 +239,18 @@ func TestPodcastE2E_FullFlow(t *testing.T) { t.Fatalf("expected %d, got %d: %s", http.StatusOK, rr.Code, rr.Body.String()) } - var sets []model.Set - if err := json.Unmarshal(rr.Body.Bytes(), &sets); err != nil { - t.Fatalf("unmarshal sets: %v", err) + var feeds []model.PodcastFeed + if err := json.Unmarshal(rr.Body.Bytes(), &feeds); err != nil { + t.Fatalf("unmarshal feeds: %v", err) } - found := false - for _, s := range sets { - if s.ID == podcastSetID && s.IsPodcast { - found = true - break - } + if len(feeds) != 2 { + t.Fatalf("expected 2 podcast feeds, got %d", len(feeds)) } - if !found { - t.Fatalf("expected podcast set %d in list", podcastSetID) + for _, feed := range feeds { + if feed.SetID != podcastSetID { + t.Fatalf("expected all feeds in set %d, got feed %+v", podcastSetID, feed) + } } }) diff --git a/internal/api/handlers_test.go b/internal/api/handlers_test.go index 6f9f75e..435a924 100644 --- a/internal/api/handlers_test.go +++ b/internal/api/handlers_test.go @@ -1515,6 +1515,9 @@ func (m *mockPingStore) GetFeedByID(ctx context.Context, id int64) (*model.Podca func (m *mockPingStore) GetFeedBySetID(ctx context.Context, setID int64) (*model.PodcastFeed, error) { return m.store.GetFeedBySetID(ctx, setID) } +func (m *mockPingStore) ListFeedsBySetID(ctx context.Context, setID int64) ([]model.PodcastFeed, error) { + return m.store.ListFeedsBySetID(ctx, setID) +} func (m *mockPingStore) ListFeeds(ctx context.Context) ([]model.PodcastFeed, error) { return m.store.ListFeeds(ctx) } diff --git a/internal/api/server.go b/internal/api/server.go index e545e94..1469626 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -165,6 +165,11 @@ func (s *Server) routesAuth() { s.mux.Handle("POST /api/logout", s.requireSession(s.handleLogout)) } +// routesConfig wires authenticated client configuration. +func (s *Server) routesConfig() { + s.mux.Handle("GET /api/config", s.requireSession(s.handleConfig)) +} + // routesSets wires the set-related API routes. func (s *Server) routesSets() { s.mux.Handle("GET /api/sets", s.requireSession(s.handleListSets)) @@ -183,6 +188,7 @@ func (s *Server) routesMedia() { s.mux.Handle("GET /api/media/{id}/thumbnail", s.requireSession(s.handleThumbnail)) s.mux.Handle("POST /api/media/{id}/thumbnail", s.requireSession(s.handleRegenThumbnail)) s.mux.Handle("POST /api/media/{id}/favorite", s.requireSession(s.handleFavorite)) + s.mux.Handle("GET /api/tags", s.requireSession(s.handleListTags)) s.mux.Handle("POST /api/media/{id}/tags", s.requireSession(s.handleAddTag)) s.mux.Handle("DELETE /api/media/{id}/tags/{tag}", s.requireSession(s.handleRemoveTag)) s.mux.Handle("DELETE /api/media/{id}", s.requireSession(s.handleSoftDelete)) @@ -228,6 +234,7 @@ func (s *Server) routes() { s.routesStatic() s.routesHTML() s.routesAuth() + s.routesConfig() s.routesSets() s.routesMedia() s.routesNotes() |
