watch: sync watchlist status between watch and details page

This commit is contained in:
2026-04-18 06:26:13 +02:00
parent 9bddd30daf
commit c8e8b80b75
4 changed files with 41 additions and 12 deletions

View File

@@ -11,7 +11,9 @@ import (
"strings" "strings"
"time" "time"
"mal/internal/database"
"mal/internal/jikan" "mal/internal/jikan"
"mal/internal/shared/middleware"
"mal/internal/templates" "mal/internal/templates"
) )
@@ -75,7 +77,8 @@ func (h *Handler) HandleWatchPage(w http.ResponseWriter, r *http.Request) {
} }
title := anime.DisplayTitle() 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 { if err != nil {
log.Printf("watch page error for mal_id=%d: %v", malID, err) log.Printf("watch page error for mal_id=%d: %v", malID, err)
http.Error(w, "Failed to load playback", http.StatusBadGateway) 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, MalID: data.MalID,
Title: data.Title, Title: data.Title,
CurrentEpisode: data.CurrentEpisode, CurrentEpisode: data.CurrentEpisode,
CurrentStatus: data.CurrentStatus,
InitialMode: data.InitialMode, InitialMode: data.InitialMode,
AvailableModes: data.AvailableModes, AvailableModes: data.AvailableModes,
ModeSources: convertModeSources(data.ModeSources), 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) 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 { func convertModeSources(sources map[string]ModeSource) map[string]templates.ModeSource {
result := make(map[string]templates.ModeSource, len(sources)) result := make(map[string]templates.ModeSource, len(sources))
for k, v := range sources { for k, v := range sources {

View File

@@ -8,6 +8,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"mal/internal/database"
"net/http" "net/http"
"net/url" "net/url"
"sort" "sort"
@@ -22,6 +23,7 @@ type Service struct {
allAnimeClient *allAnimeClient allAnimeClient *allAnimeClient
jikanClient *jikan.Client jikanClient *jikan.Client
httpClient *http.Client httpClient *http.Client
db database.Querier
} }
type sourceScore struct { type sourceScore struct {
@@ -33,15 +35,16 @@ type sourceScore struct {
refererScore int refererScore int
} }
func NewService(jikanClient *jikan.Client) *Service { func NewService(jikanClient *jikan.Client, db database.Querier) *Service {
return &Service{ return &Service{
allAnimeClient: newAllAnimeClient(), allAnimeClient: newAllAnimeClient(),
jikanClient: jikanClient, jikanClient: jikanClient,
httpClient: &http.Client{Timeout: 12 * time.Second}, 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 { if malID <= 0 {
return WatchPageData{}, errors.New("invalid mal id") 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) 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) watchTitle := strings.TrimSpace(resolvedTitle)
if watchTitle == "" { if watchTitle == "" {
watchTitle = strings.TrimSpace(title) watchTitle = strings.TrimSpace(title)
@@ -106,6 +120,7 @@ func (s *Service) BuildWatchPageData(ctx context.Context, malID int, title strin
MalID: malID, MalID: malID,
Title: watchTitle, Title: watchTitle,
CurrentEpisode: normalizedEpisode, CurrentEpisode: normalizedEpisode,
CurrentStatus: currentStatus,
InitialMode: initialMode, InitialMode: initialMode,
AvailableModes: availableModes, AvailableModes: availableModes,
ModeSources: modeSources, ModeSources: modeSources,

View File

@@ -41,12 +41,13 @@ type SkipSegment struct {
} }
type WatchPageData struct { type WatchPageData struct {
MalID int MalID int
Title string Title string
CurrentEpisode string CurrentEpisode string
InitialMode string CurrentStatus string
AvailableModes []string InitialMode string
ModeSources map[string]ModeSource AvailableModes []string
Episodes []EpisodeListItem ModeSources map[string]ModeSource
Segments []SkipSegment Episodes []EpisodeListItem
Segments []SkipSegment
} }

View File

@@ -28,7 +28,7 @@ func NewRouter(cfg Config) http.Handler {
animeSvc := anime.NewService(cfg.JikanClient, cfg.DB) animeSvc := anime.NewService(cfg.JikanClient, cfg.DB)
animeHandler := anime.NewHandler(animeSvc) animeHandler := anime.NewHandler(animeSvc)
playbackSvc := playback.NewService(cfg.JikanClient) playbackSvc := playback.NewService(cfg.JikanClient, cfg.DB)
playbackHandler := playback.NewHandler(playbackSvc, cfg.JikanClient) playbackHandler := playback.NewHandler(playbackSvc, cfg.JikanClient)
// Serve static files // Serve static files