Files
silo-server/internal/api
63d608f6fc fix(progress): filter continue-watching by viewer access scope (#167)
* fix(progress): filter continue-watching by viewer access scope

The continue-watching list (GET /progress) only filtered by library when an
explicit library_id query param was passed. The global call passes none, so a
restricted profile received progress rows for items outside its scope (e.g. an
XXX title, or a title above the profile's content-rating cap). The web client
then fans out a per-item GET /catalog/items/{id} detail fetch for each row, and
the inaccessible ones return 404 — surfacing as a dead Continue Watching tile
and stray 404s.

Always apply the viewer's access scope to the progress list. Adds
LibraryItemRepository.FilterAccessibleContentIDs, a batched, episode-aware
mirror of the detail endpoint's access predicate (library membership +
content-rating ceiling), and wires it into HandleListProgress for restricted
profiles only (unrestricted viewers are unaffected). ExcludedMediaTypes is
omitted intentionally: the viewer access.Scope does not carry it and the
request path never sets it.

* fix(progress): gate continue-watching episode access on the parent series

FilterAccessibleContentIDs keyed episode access off episode_libraries and
required a media_item_libraries membership even for rating-only viewers, both
of which diverge from the detail/watch path that masks inaccessible items
(DetailService.GetItemDetail → EnsureAccessible(episode.SeriesID)). For shows
whose episodes span multiple library folders this reintroduced the dead tile /
out-of-scope leak this filter exists to prevent, and rating-only profiles
could lose membership-less items the detail endpoint still serves.

Rewrite the predicate to mirror EnsureAccessible exactly: base the lookup on
media_items, join media_item_libraries only when the viewer is
library-restricted, and resolve episodes through their parent series. Extract a
pure buildFilterAccessibleContentIDsSQL so the query shape (placeholder
numbering, the parent-series join, the optional rating predicate) is
unit-tested without a database, and de-duplicate the two progress filter
helpers via progressContentIDs/keepAccessibleEntries.

AI-use disclosure: implemented with AI assistance (Claude).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Quick <31828688+Quick104@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 18:32:52 -04:00
..