fix(jellycompat): populate episode season parent metadata

This commit is contained in:
Quick
2026-06-15 18:10:40 -04:00
parent 2e422b66df
commit ae65678ba4
3 changed files with 90 additions and 1 deletions
+16 -1
View File
@@ -16,6 +16,8 @@ import (
type compatEpisodeTarget struct {
Item upstreamListItem
SeriesImages seriesImageSet
SeasonID string
SeasonName string
}
type libraryMembershipChecker interface {
@@ -166,7 +168,11 @@ func (h *ItemsHandler) fetchCompatEpisodeTargetsByContentIDs(ctx context.Context
}
access := h.resolveAccessFilter(ctx, session)
fromClause := "episodes e JOIN media_items si ON e.series_id = si.content_id"
fromClause := strings.Join([]string{
"episodes e",
"JOIN media_items si ON e.series_id = si.content_id",
"LEFT JOIN seasons se ON se.content_id = e.season_id",
}, " ")
conditions := []string{"e.content_id = ANY($1)"}
args := []any{normalized}
argIdx := 2
@@ -208,6 +214,8 @@ func (h *ItemsHandler) fetchCompatEpisodeTargetsByContentIDs(ctx context.Context
e.updated_at,
e.season_number,
e.episode_number,
COALESCE(e.season_id, ''),
COALESCE(se.title, ''),
si.content_id,
si.title,
si.genres,
@@ -249,6 +257,8 @@ func (h *ItemsHandler) fetchCompatEpisodeTargetsByContentIDs(ctx context.Context
updatedAt time.Time
seasonNumber int
episodeNumber int
seasonID string
seasonName string
seriesID string
seriesTitle string
genres []string
@@ -275,6 +285,8 @@ func (h *ItemsHandler) fetchCompatEpisodeTargetsByContentIDs(ctx context.Context
&updatedAt,
&seasonNumber,
&episodeNumber,
&seasonID,
&seasonName,
&seriesID,
&seriesTitle,
&genres,
@@ -335,6 +347,8 @@ func (h *ItemsHandler) fetchCompatEpisodeTargetsByContentIDs(ctx context.Context
BackdropThumbhash: seriesBackdropTH,
UpdatedAt: seriesUpdatedAt,
},
SeasonID: seasonID,
SeasonName: seasonName,
}
}
if err := rows.Err(); err != nil {
@@ -446,6 +460,7 @@ func (h *ItemsHandler) fetchCompatEpisodeTargetsByContentIDsFallback(ctx context
BackdropThumbhash: series.BackdropThumbhash,
UpdatedAt: series.UpdatedAt,
},
SeasonID: episode.SeasonID,
}
}
+12
View File
@@ -2170,6 +2170,18 @@ func (h *ItemsHandler) rememberCompatEpisodeImages(dto baseItemDTO, stillURL str
}
func (h *ItemsHandler) applyCompatEpisodeTarget(dto *baseItemDTO, target compatEpisodeTarget) {
if target.SeasonID != "" && h.codec != nil {
encodedSeasonID := h.codec.EncodeStringID(EncodedIDSeason, target.SeasonID)
if dto.SeasonID == "" {
dto.SeasonID = encodedSeasonID
}
if dto.ParentID == "" {
dto.ParentID = encodedSeasonID
}
}
if dto.SeasonName == "" && target.SeasonName != "" {
dto.SeasonName = target.SeasonName
}
h.mapper.applySeriesImages(dto, target.SeriesImages)
h.rememberCompatEpisodeImages(*dto, firstNonEmpty(target.Item.StillURL, target.Item.PosterURL), target.SeriesImages)
}
@@ -152,3 +152,65 @@ func TestHydrateProgressItems_NoFieldsLeavesDetailFieldsNil(t *testing.T) {
t.Errorf("MediaSources should be nil with no Fields; got %v", got[0].dto.MediaSources)
}
}
func TestHydrateProgressItems_EpisodeIncludesSeasonParentMetadata(t *testing.T) {
codec := NewResourceIDCodec()
episodeID := "episode-1"
seasonID := "season-1"
seriesID := "series-1"
itemRepo := &countingItemRepo{
itemsByID: map[string]*models.MediaItem{
seriesID: {ContentID: seriesID, Type: "series", Title: "Test Series"},
},
}
episodeRepo := &countingEpisodeRepo{
episodesByID: map[string]*models.Episode{
episodeID: {
ContentID: episodeID,
SeriesID: seriesID,
SeasonID: seasonID,
Title: "Episode Four",
SeasonNumber: 1,
EpisodeNumber: 4,
},
},
}
h := &ItemsHandler{
content: &panicOnGetItemDetailContent{},
userData: &mockUserDataService{},
itemRepo: itemRepo,
episodeRepo: episodeRepo,
codec: codec,
mapper: newMapper(codec, &config.Config{}),
}
got, err := h.hydrateProgressItems(
context.Background(),
&Session{StreamAppUserID: 1, ProfileID: "profile-1"},
[]upstreamProgress{{MediaItemID: episodeID, PositionSeconds: 120, DurationSeconds: 1800}},
nil,
nil,
)
if err != nil {
t.Fatalf("hydrateProgressItems returned error: %v", err)
}
if len(got) != 1 {
t.Fatalf("expected 1 hydrated item; got %d (%v)", len(got), got)
}
wantSeasonID := codec.EncodeStringID(EncodedIDSeason, seasonID)
if got[0].dto.SeasonID != wantSeasonID {
t.Fatalf("SeasonId = %q, want %q", got[0].dto.SeasonID, wantSeasonID)
}
if got[0].dto.ParentID != wantSeasonID {
t.Fatalf("ParentId = %q, want season id %q", got[0].dto.ParentID, wantSeasonID)
}
if got[0].dto.IndexNumber == nil || *got[0].dto.IndexNumber != 4 {
t.Fatalf("IndexNumber = %v, want 4", got[0].dto.IndexNumber)
}
if got[0].dto.ParentIndexNumber == nil || *got[0].dto.ParentIndexNumber != 1 {
t.Fatalf("ParentIndexNumber = %v, want 1", got[0].dto.ParentIndexNumber)
}
}