refactor: populate watch page data before error return
This commit is contained in:
@@ -54,22 +54,32 @@ func (h *PlaybackHandler) HandleWatchPage(c *gin.Context) {
|
|||||||
|
|
||||||
data, err := h.svc.BuildWatchData(c.Request.Context(), id, []string{}, ep, mode, userID)
|
data, err := h.svc.BuildWatchData(c.Request.Context(), id, []string{}, ep, mode, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
anime, fetchErr := h.animeSvc.GetAnimeByID(c.Request.Context(), id)
|
if data.Anime.MalID == 0 && data.WatchData.MalID == 0 && len(data.Episodes) == 0 {
|
||||||
if fetchErr != nil {
|
anime, fetchErr := h.animeSvc.GetAnimeByID(c.Request.Context(), id)
|
||||||
fmt.Printf("error fetching anime for error page: %v\n", fetchErr)
|
if fetchErr != nil {
|
||||||
|
fmt.Printf("error fetching anime for error page: %v\n", fetchErr)
|
||||||
|
}
|
||||||
|
data = domain.WatchPageData{
|
||||||
|
Anime: anime,
|
||||||
|
CurrentEpID: ep,
|
||||||
|
WatchData: domain.WatchData{
|
||||||
|
MalID: id,
|
||||||
|
Title: anime.DisplayTitle(),
|
||||||
|
CurrentEpisode: ep,
|
||||||
|
Episodes: []domain.CanonicalEpisode{},
|
||||||
|
Providers: []domain.ProviderData{},
|
||||||
|
ModeSources: map[string]domain.ModeSource{},
|
||||||
|
AvailableModes: []string{},
|
||||||
|
Airing: anime.Airing,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
c.HTML(http.StatusOK, "watch.gohtml", domain.WatchPageData{
|
|
||||||
Error: err.Error(),
|
data.Error = err.Error()
|
||||||
Anime: anime,
|
data.User = user
|
||||||
Episodes: []domain.CanonicalEpisode{},
|
data.CurrentPath = c.Request.URL.Path
|
||||||
CurrentPath: c.Request.URL.Path,
|
|
||||||
User: user,
|
c.HTML(http.StatusOK, "watch.gohtml", data)
|
||||||
CurrentEpID: ep,
|
|
||||||
WatchData: domain.WatchData{
|
|
||||||
Episodes: []domain.CanonicalEpisode{},
|
|
||||||
Providers: []domain.ProviderData{},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
126
internal/playback/handler/watch_page_test.go
Normal file
126
internal/playback/handler/watch_page_test.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"mal/integrations/jikan"
|
||||||
|
"mal/internal/domain"
|
||||||
|
"mal/templates"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type watchPagePlaybackService struct {
|
||||||
|
data domain.WatchPageData
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *watchPagePlaybackService) BuildWatchData(context.Context, int, []string, string, string, string) (domain.WatchPageData, error) {
|
||||||
|
return s.data, s.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *watchPagePlaybackService) SaveProgress(context.Context, string, int64, int, float64) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *watchPagePlaybackService) CompleteAnime(context.Context, string, int64) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *watchPagePlaybackService) SignProxyToken(string, string, string) (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *watchPagePlaybackService) ResolveProxyToken(string, string) (string, string, error) {
|
||||||
|
return "", "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *watchPagePlaybackService) UpsertSkipSegmentOverride(context.Context, string, int64, int, string, float64, float64) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type watchPageAnimeService struct{}
|
||||||
|
|
||||||
|
func (watchPageAnimeService) GetAnimeByID(context.Context, int) (domain.Anime, error) {
|
||||||
|
return domain.Anime{}, errors.New("unexpected anime lookup")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (watchPageAnimeService) GetAllEpisodes(context.Context, int) ([]domain.EpisodeData, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func baseWatchPageData() domain.WatchPageData {
|
||||||
|
return domain.WatchPageData{
|
||||||
|
Anime: domain.Anime{Anime: jikan.Anime{MalID: 123, Title: "Example Anime"}},
|
||||||
|
Episodes: []domain.CanonicalEpisode{
|
||||||
|
{Number: 1, Title: "Episode 1", HasSub: true},
|
||||||
|
{Number: 2, Title: "Episode 2", HasSub: true},
|
||||||
|
},
|
||||||
|
CurrentEpID: "1",
|
||||||
|
WatchData: domain.WatchData{
|
||||||
|
MalID: 123,
|
||||||
|
Title: "Example Anime",
|
||||||
|
CurrentEpisode: "1",
|
||||||
|
Episodes: []domain.CanonicalEpisode{
|
||||||
|
{Number: 1, Title: "Episode 1", HasSub: true},
|
||||||
|
{Number: 2, Title: "Episode 2", HasSub: true},
|
||||||
|
},
|
||||||
|
ModeSources: map[string]domain.ModeSource{},
|
||||||
|
AvailableModes: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWatchPageRouter(t *testing.T, h *PlaybackHandler) *gin.Engine {
|
||||||
|
t.Helper()
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
renderer, err := templates.ProvideRenderer()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ProvideRenderer() error = %v", err)
|
||||||
|
}
|
||||||
|
router := gin.New()
|
||||||
|
router.HTMLRender = renderer
|
||||||
|
router.GET("/anime/:id/watch", h.HandleWatchPage)
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleWatchPagePreservesPartialDataOnPlaybackFailure(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
router := newWatchPageRouter(t, &PlaybackHandler{
|
||||||
|
svc: &watchPagePlaybackService{
|
||||||
|
data: baseWatchPageData(),
|
||||||
|
err: errors.New("no streams found"),
|
||||||
|
},
|
||||||
|
animeSvc: watchPageAnimeService{},
|
||||||
|
})
|
||||||
|
|
||||||
|
req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/anime/123/watch?ep=1", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
body := rec.Body.String()
|
||||||
|
if !strings.Contains(body, `data-mal-id="123"`) {
|
||||||
|
t.Fatalf("expected player MAL id in body, got:\n%s", body)
|
||||||
|
}
|
||||||
|
if !strings.Contains(body, `data-episode-id="1"`) {
|
||||||
|
t.Fatalf("expected episode list in body, got:\n%s", body)
|
||||||
|
}
|
||||||
|
if !strings.Contains(body, `data-playback-error="no streams found"`) {
|
||||||
|
t.Fatalf("expected playback error data attribute in body, got:\n%s", body)
|
||||||
|
}
|
||||||
|
if !strings.Contains(body, `/anime/123/watch?ep=2`) {
|
||||||
|
t.Fatalf("expected episode links to keep the anime id, got:\n%s", body)
|
||||||
|
}
|
||||||
|
if strings.Contains(body, "No episodes found") {
|
||||||
|
t.Fatalf("expected partial episode list instead of empty state, got:\n%s", body)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,15 +39,7 @@ func (s *playbackService) BuildWatchData(ctx context.Context, animeID int, title
|
|||||||
if resolvedModeSwitchedFrom != "" {
|
if resolvedModeSwitchedFrom != "" {
|
||||||
modeSwitchedFrom = resolvedModeSwitchedFrom
|
modeSwitchedFrom = resolvedModeSwitchedFrom
|
||||||
}
|
}
|
||||||
if len(modeSources) == 0 {
|
|
||||||
return domain.WatchPageData{}, fmt.Errorf("no streams found")
|
|
||||||
}
|
|
||||||
if result == nil {
|
|
||||||
return domain.WatchPageData{}, fmt.Errorf("no streams found for mode %s", mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
startTime, watchlistStatus, watchlistIDs := s.loadWatchProgress(ctx, userID, animeID, anime.Episodes, episode)
|
startTime, watchlistStatus, watchlistIDs := s.loadWatchProgress(ctx, userID, animeID, anime.Episodes, episode)
|
||||||
go s.warmStreamURL(result.URL, result.Referer)
|
|
||||||
seasons := s.loadSeasons(ctx, animeID)
|
seasons := s.loadSeasons(ctx, animeID)
|
||||||
segments, err := s.fetchSkipSegments(ctx, userID, animeID, episode)
|
segments, err := s.fetchSkipSegments(ctx, userID, animeID, episode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -57,7 +49,16 @@ func (s *playbackService) BuildWatchData(ctx context.Context, animeID int, title
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
watchData := buildWatchDataPayload(animeData, animeID, episode, startTime, canonicalEpisodes.Episodes, modeSources, mode, modeSwitchedFrom, segments)
|
watchData := buildWatchDataPayload(animeData, animeID, episode, startTime, canonicalEpisodes.Episodes, modeSources, mode, modeSwitchedFrom, segments)
|
||||||
return buildWatchPageData(animeData, canonicalEpisodes.Episodes, episode, watchlistStatus, watchlistIDs, seasons, watchData), nil
|
pageData := buildWatchPageData(animeData, canonicalEpisodes.Episodes, episode, watchlistStatus, watchlistIDs, seasons, watchData)
|
||||||
|
if len(modeSources) == 0 {
|
||||||
|
return pageData, fmt.Errorf("no streams found")
|
||||||
|
}
|
||||||
|
if result == nil {
|
||||||
|
return pageData, fmt.Errorf("no streams found for mode %s", mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
go s.warmStreamURL(result.URL, result.Referer)
|
||||||
|
return pageData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildWatchDataPayload(anime domain.Anime, animeID int, episode string, startTime float64, episodes []domain.CanonicalEpisode, modeSources map[string]domain.ModeSource, mode string, modeSwitchedFrom string, segments []domain.SkipSegment) domain.WatchData {
|
func buildWatchDataPayload(anime domain.Anime, animeID int, episode string, startTime float64, episodes []domain.CanonicalEpisode, modeSources map[string]domain.ModeSource, mode string, modeSwitchedFrom string, segments []domain.SkipSegment) domain.WatchData {
|
||||||
|
|||||||
Reference in New Issue
Block a user