From c8e8b80b75b71fa0633f908362a29d8fe3646a84 Mon Sep 17 00:00:00 2001 From: mkelvers Date: Sat, 18 Apr 2026 06:26:13 +0200 Subject: [PATCH] watch: sync watchlist status between watch and details page --- internal/features/playback/handler.go | 15 ++++++++++++++- internal/features/playback/service.go | 19 +++++++++++++++++-- internal/features/playback/types.go | 17 +++++++++-------- internal/server/routes.go | 2 +- 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/internal/features/playback/handler.go b/internal/features/playback/handler.go index 99e91b1..b95a7c8 100644 --- a/internal/features/playback/handler.go +++ b/internal/features/playback/handler.go @@ -11,7 +11,9 @@ import ( "strings" "time" + "mal/internal/database" "mal/internal/jikan" + "mal/internal/shared/middleware" "mal/internal/templates" ) @@ -75,7 +77,8 @@ func (h *Handler) HandleWatchPage(w http.ResponseWriter, r *http.Request) { } title := anime.DisplayTitle() - data, err := h.svc.BuildWatchPageData(ctx, malID, title, episode, mode) + userID := watchlistUserIDFromRequest(r) + data, err := h.svc.BuildWatchPageData(ctx, malID, title, episode, mode, userID) if err != nil { log.Printf("watch page error for mal_id=%d: %v", malID, err) http.Error(w, "Failed to load playback", http.StatusBadGateway) @@ -87,6 +90,7 @@ func (h *Handler) HandleWatchPage(w http.ResponseWriter, r *http.Request) { MalID: data.MalID, Title: data.Title, CurrentEpisode: data.CurrentEpisode, + CurrentStatus: data.CurrentStatus, InitialMode: data.InitialMode, AvailableModes: data.AvailableModes, ModeSources: convertModeSources(data.ModeSources), @@ -96,6 +100,15 @@ func (h *Handler) HandleWatchPage(w http.ResponseWriter, r *http.Request) { templates.WatchPage(anime, pageData).Render(r.Context(), w) } +func watchlistUserIDFromRequest(r *http.Request) string { + user, ok := r.Context().Value(middleware.UserContextKey).(*database.User) + if !ok || user == nil { + return "" + } + + return user.ID +} + func convertModeSources(sources map[string]ModeSource) map[string]templates.ModeSource { result := make(map[string]templates.ModeSource, len(sources)) for k, v := range sources { diff --git a/internal/features/playback/service.go b/internal/features/playback/service.go index d5347c6..5f44a3a 100644 --- a/internal/features/playback/service.go +++ b/internal/features/playback/service.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "io" + "mal/internal/database" "net/http" "net/url" "sort" @@ -22,6 +23,7 @@ type Service struct { allAnimeClient *allAnimeClient jikanClient *jikan.Client httpClient *http.Client + db database.Querier } type sourceScore struct { @@ -33,15 +35,16 @@ type sourceScore struct { refererScore int } -func NewService(jikanClient *jikan.Client) *Service { +func NewService(jikanClient *jikan.Client, db database.Querier) *Service { return &Service{ allAnimeClient: newAllAnimeClient(), jikanClient: jikanClient, httpClient: &http.Client{Timeout: 12 * time.Second}, + db: db, } } -func (s *Service) BuildWatchPageData(ctx context.Context, malID int, title string, episode string, mode string) (WatchPageData, error) { +func (s *Service) BuildWatchPageData(ctx context.Context, malID int, title string, episode string, mode string, userID string) (WatchPageData, error) { if malID <= 0 { return WatchPageData{}, errors.New("invalid mal id") } @@ -94,6 +97,17 @@ func (s *Service) BuildWatchPageData(ctx context.Context, malID int, title strin segments := s.fetchSkipSegments(ctx, malID, normalizedEpisode) + currentStatus := "" + if userID != "" && s.db != nil { + entry, err := s.db.GetWatchListEntry(ctx, database.GetWatchListEntryParams{ + UserID: userID, + AnimeID: int64(malID), + }) + if err == nil { + currentStatus = entry.Status + } + } + watchTitle := strings.TrimSpace(resolvedTitle) if watchTitle == "" { watchTitle = strings.TrimSpace(title) @@ -106,6 +120,7 @@ func (s *Service) BuildWatchPageData(ctx context.Context, malID int, title strin MalID: malID, Title: watchTitle, CurrentEpisode: normalizedEpisode, + CurrentStatus: currentStatus, InitialMode: initialMode, AvailableModes: availableModes, ModeSources: modeSources, diff --git a/internal/features/playback/types.go b/internal/features/playback/types.go index 68dfa20..2952c61 100644 --- a/internal/features/playback/types.go +++ b/internal/features/playback/types.go @@ -41,12 +41,13 @@ type SkipSegment struct { } type WatchPageData struct { - MalID int - Title string - CurrentEpisode string - InitialMode string - AvailableModes []string - ModeSources map[string]ModeSource - Episodes []EpisodeListItem - Segments []SkipSegment + MalID int + Title string + CurrentEpisode string + CurrentStatus string + InitialMode string + AvailableModes []string + ModeSources map[string]ModeSource + Episodes []EpisodeListItem + Segments []SkipSegment } diff --git a/internal/server/routes.go b/internal/server/routes.go index e0ee88f..88a49d7 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -28,7 +28,7 @@ func NewRouter(cfg Config) http.Handler { animeSvc := anime.NewService(cfg.JikanClient, cfg.DB) animeHandler := anime.NewHandler(animeSvc) - playbackSvc := playback.NewService(cfg.JikanClient) + playbackSvc := playback.NewService(cfg.JikanClient, cfg.DB) playbackHandler := playback.NewHandler(playbackSvc, cfg.JikanClient) // Serve static files