feat: add simulcast page with season picker and navigation
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
var Module = fx.Options(
|
||||
fx.Provide(
|
||||
NewAnimeRepository,
|
||||
NewSeasonDiscoveryService,
|
||||
fx.Annotate(
|
||||
NewAnimeService,
|
||||
fx.As(new(Service)),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user