diff --git a/internal/anime/catalog_handler.go b/internal/anime/catalog_handler.go index aed5eabc..18c65268 100644 --- a/internal/anime/catalog_handler.go +++ b/internal/anime/catalog_handler.go @@ -4,10 +4,39 @@ import ( "mal/internal/observability" "mal/internal/server" "net/http" + "strings" + "time" "github.com/gin-gonic/gin" ) +func (h *AnimeHandler) HandleSimulcast(c *gin.Context) { + now := time.Now() + current := calendarSeason(now.Year(), int(now.Month())) + latest := h.discoverySvc.LatestAvailableSeason(c.Request.Context(), current) + selected := seasonSelection(c.Query("season"), c.Query("year"), current, latest) + data, err := h.discoverySvc.GetSimulcast(c.Request.Context(), selected) + if err != nil { + observability.WarnContext(c.Request.Context(), "simulcast_fetch_failed", "anime", "", nil, err) + c.AbortWithStatus(http.StatusInternalServerError) + return + } + watchlistMap := h.watchlistMapForAnimes(c.Request.Context(), server.CurrentUserID(c), data.Animes) + previous, next := seasonNavigation(selected, 2018, latest) + c.HTML(http.StatusOK, "simulcast.gohtml", gin.H{ + "CurrentPath": "/simulcast", + "User": server.CurrentUser(c), + "Animes": data.Animes, + "Season": data.Season, + "SeasonLabel": strings.ToUpper(data.Season[:1]) + data.Season[1:], + "Year": data.Year, + "SeasonOptions": seasonOptions(2018, latest), + "Previous": previous, + "Next": next, + "WatchlistMap": watchlistMap, + }) +} + func (h *AnimeHandler) HandleSearch(c *gin.Context) { c.HTML(http.StatusOK, "search.gohtml", gin.H{ "User": server.CurrentUser(c), diff --git a/internal/anime/handler.go b/internal/anime/handler.go index 1e2aa3a8..1c9ad2ae 100644 --- a/internal/anime/handler.go +++ b/internal/anime/handler.go @@ -11,6 +11,7 @@ type AnimeHandler struct { svc Service watchlistSvc domain.WatchlistService episodeSvc domain.EpisodeService + discoverySvc *SeasonDiscoveryService } type Service interface { @@ -20,11 +21,12 @@ type Service interface { WarmDetailSections(id int) } -func NewAnimeHandler(svc Service, watchlistSvc domain.WatchlistService, episodeSvc domain.EpisodeService) *AnimeHandler { +func NewAnimeHandler(svc Service, watchlistSvc domain.WatchlistService, episodeSvc domain.EpisodeService, discoverySvc *SeasonDiscoveryService) *AnimeHandler { return &AnimeHandler{ svc: svc, watchlistSvc: watchlistSvc, episodeSvc: episodeSvc, + discoverySvc: discoverySvc, } } @@ -59,6 +61,7 @@ func (h *AnimeHandler) Register(r *gin.Engine) { r.GET("/search", h.HandleSearch) r.GET("/top-picks", h.HandleTopPicks) r.GET("/browse", h.HandleBrowse) + r.GET("/simulcast", h.HandleSimulcast) r.GET("/anime/:id", h.HandleAnimeDetails) r.GET("/anime/:id/reviews", h.HandleAnimeReviews) r.GET("/api/watch-order", h.HandleHTMLWatchOrder) diff --git a/internal/anime/handler_test.go b/internal/anime/handler_test.go index 06d8bc1f..12d2ad74 100644 --- a/internal/anime/handler_test.go +++ b/internal/anime/handler_test.go @@ -140,7 +140,7 @@ func TestAnimeEpisodeCountUsesCanonicalEpisodes(t *testing.T) { }, }, } - handler := NewAnimeHandler(nil, nil, episodeSvc) + handler := NewAnimeHandler(nil, nil, episodeSvc, nil) got := handler.animeEpisodeCount(context.Background(), domain.Anime{Anime: jikan.Anime{ MalID: 59970, @@ -162,7 +162,7 @@ func TestAnimeEpisodeCountUsesCanonicalEpisodes(t *testing.T) { func TestAnimeEpisodeCountFallsBackToMetadata(t *testing.T) { episodeSvc := &stubEpisodeService{err: errors.New("provider unavailable")} - handler := NewAnimeHandler(nil, nil, episodeSvc) + handler := NewAnimeHandler(nil, nil, episodeSvc, nil) got := handler.animeEpisodeCount(context.Background(), domain.Anime{Anime: jikan.Anime{ MalID: 59970, @@ -279,7 +279,7 @@ func TestAnimeAudioAvailabilityRequiresAllAnimeSource(t *testing.T) { }, err: tt.err, } - handler := NewAnimeHandler(nil, nil, episodeSvc) + handler := NewAnimeHandler(nil, nil, episodeSvc, nil) got := handler.animeAudioAvailability(context.Background(), domain.Anime{ Anime: jikan.Anime{MalID: 52991}, diff --git a/internal/anime/module.go b/internal/anime/module.go index 8e36f39f..76af9517 100644 --- a/internal/anime/module.go +++ b/internal/anime/module.go @@ -10,6 +10,7 @@ import ( var Module = fx.Options( fx.Provide( NewAnimeRepository, + NewSeasonDiscoveryService, fx.Annotate( NewAnimeService, fx.As(new(Service)), diff --git a/internal/anime/service_test.go b/internal/anime/service_test.go index cc8f003e..305a7cc6 100644 --- a/internal/anime/service_test.go +++ b/internal/anime/service_test.go @@ -7,13 +7,14 @@ import ( ) type catalogRepoStub struct { + watchlist []db.GetUserWatchListRow allContinueRows []db.GetContinueWatchingEntriesRow carouselContinueRows []db.GetContinueWatchingEntriesRow carouselLimit int64 } func (r *catalogRepoStub) GetUserWatchList(ctx context.Context, userID string) ([]db.GetUserWatchListRow, error) { - return nil, nil + return r.watchlist, nil } func (r *catalogRepoStub) GetWatchListEntry(ctx context.Context, params db.GetWatchListEntryParams) (db.WatchListEntry, error) { diff --git a/templates/components/navigation.gohtml b/templates/components/navigation.gohtml index 6d62bfd2..0f10b7a4 100644 --- a/templates/components/navigation.gohtml +++ b/templates/components/navigation.gohtml @@ -104,6 +104,7 @@ +