Create .NET solution with console app and car component - #1
Merged
Conversation
devpro
added a commit
that referenced
this pull request
Jul 30, 2026
Summary #1 — Killed the N+1 reference lookup (WatchNextController.cs) The old code awaited a separate FindByIdAsync per in-progress show (N serial round trips to tvshow_reference). Now it collects the candidate shows' reference ids and does one batched FindByIdsAsync, then maps references back per show — the same batched pattern the movie-poster hydration one line below already used. #2 (cheap) — Stopped fetching the whole episode history (EpisodeRepository + IEpisodeRepository) The old code pulled every episode the owner has (int.MaxValue), then WatchNextService discarded every non-Current show's episodes in memory — cost scaled with total lifetime watch history. Added a batched, owner-scoped FindByShowIdsAsync(ownerId, showIds); the controller now fetches episodes only for the Current+linked shows that can actually appear in the result. The owner_id + tv_show_id IN(...) filter rides the leading fields of the existing episode_last_watched index. Output is behaviorally identical — non-Current and unlinked shows were already excluded downstream. Tests - New EpisodeRepositoryTest (integration, real MongoDB): verifies FindByShowIdsAsync returns only the requested shows' episodes, is owner-scoped (a different owner tracking the same show id is excluded), and returns empty for no ids. Both pass. - Updated the FakeEpisodeRepository in TvTimeImportServiceIdempotencyTest to implement the new interface member. - Full unit suite (288) green; the two new integration tests green against your local Mongo. Not done (as agreed): the aggregation version of #2 (server-side max (season,episode) per show) and reference caching — hold those unless this isn't enough. If you want to sanity-check the win, the Watch Next page load should now issue a small constant number of queries regardless of how many finished shows/episodes are in the collection. One note: ITvShowReferenceRepository.FindByIdAsync is no longer called from this controller but is still used elsewhere, so I left it in place. the movies tab is already in good shape, unlike the show-reference path we just fixed. I checked all three layers: What's already correct 1. Server-side filter, with a matching index. WantToWatch = true is pushed into the Mongo query (MovieRepository.GetFilter:34), and there's a dedicated partial index movie_want_to_watch (owner_id, want_to_watch, filtered to want_to_watch: true) backing exactly that shape. So it fetches only the watchlist, never the whole movie collection. 2. Batched poster hydration, no N+1. ReferenceImageHydrator.HydrateAsync collects all reference ids and does a single FindByIdsAsync — the same batched pattern I just applied to the shows. No per-movie round trip. So the two things that mattered on the shows side (a server-side filter and avoiding N+1) are both already right here. Two marginal nits (I'd leave both) - The "already seen" exclusion runs in memory. FilterMoviesToWatch drops movies with FirstSeenAt != null in C#, even though GetFilter already supports pushing it down via IsUnseen = true (line 37). Pushing it to the DB would transfer slightly fewer docs — but the set is already just the watchlist (small), the FirstSeenAt condition wouldn't be index-covered anyway (only want_to_watch is in the partial index), and FilterMoviesToWatch is a documented, unit-tested method that exists specifically because the flag goes stale. Net payoff ≈ zero. Not worth the change. - A wasted count query — but this is not movie-specific and not really an inefficiency of the movies tab per se: FindAllAsync always runs a CountDocumentsAsync before the Find to populate PagedResult metadata that Watch Next discards. Both the shows fetch and the movies fetch pay it (2 round trips where 1 would do). It's indexed on owner_id and cheap; removing it would mean adding a non-paged read method to the repos — more API surface than the tiny saving justifies. Bottom line: the movies tab doesn't have the kind of scaling problem the episode/reference path had — its cost is bounded by watchlist size, not by total library size. I wouldn't change anything there. If you want me to push the seen-filter down anyway (turning FilterMoviesToWatch into a DB filter) I can, but I'd consider it cosmetic.
devpro
added a commit
that referenced
this pull request
Jul 30, 2026
What I built (next-step #1) A general, admin/global, per-domain primary rating source mechanism, wired for video games (RAWG vs Metacritic) as the first case. Storage — shared app_setting collection (as you specified): - AppSetting entity = one document, fixed _id: "global", with a reference_rating_source map (domain → source). New settings become new fields on this document, never new collections. - IAppSettingRepository / AppSettingRepository (GetReferenceRatingSourcesAsync, SetReferenceRatingSourceAsync) — LeaseRepository-style, atomic $set on just the one map entry (upsert creates the doc). Registered in DI. General mechanism: - RatingSourceCatalog — the single home for per-domain available sources + code default (today only VideoGame → [rawg, metacritic], default rawg). The "rawg"/"metacritic" literals now live here; VideoGames.cs consts point at them (no drift). - ReferenceEnrichmentService.GetPrimaryRatingSourceAsync(domain) — stored override if it still names an available source, else the catalog default. The three video-game PrimaryRating call sites (resolve/refresh/link) now read this instead of the hardcoded "rawg". Movie/TV/Album/Book are untouched this increment (they join by adding a catalog entry when they gain a 2nd source, e.g. IMDb). - RecomputeReferenceRatingsAsync(domain) — one bulk SetReferenceRatingAsync pass over the (small, shared) reference collection, no provider calls; the loop is a domain-agnostic generic helper so adding movies/TV/albums later is a one-line switch arm. Admin API + UI (two actions, as chosen): - GET /api/reference-data/rating-sources, PUT …/{domain} (validated ∈ available → 400 otherwise), POST …/{domain}/recompute → { referencesChecked, itemsUpdated }. - New "Primary rating source" card on the reference-data admin page: per selectable domain, a source button-group + a separate Recompute button showing result counts, with the Metacritic caveat spelled out. Tests (all passing): catalog defaults/available; resolver (default / valid override / invalid-override-falls-back); resolve denormalizes the selected source; recompute re-stamps every linked item with the selected source's value+scale (rawg /5 vs metacritic /100), uses default when unset, and throws for a non-selectable domain. To try it Run WebApi + BlazorApp, go to /admin/reference-data, use the new card: switch video games to metacritic, click Recompute, and check that game list pills/Ref ★ sort reflect the /100 scores (many will blank out, as warned). Switch back to rawg + Recompute to restore. Two things I deliberately left for your call after you've run it: - No index on app_setting (single doc, _id lookup only) and none needed. Both docs are updated: - docs/reference-ratings-plan.md — Status now notes step 1 done (315 tests), a new "Admin-selectable primary source (per-domain, admin/global) - implemented" section, the "Decisions locked" bullet updated, the new tests listed, and next-step #1 struck through as done. - CLAUDE.md — a paragraph in the reference-data admin area documenting the shared app_setting collection / IAppSettingRepository primitive (the "new field, not a new collection" rule and when to use it vs AppConfiguration), with its first use (admin-selectable primary rating source) summarized and a pointer to the plan doc.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.