Skip to content

feat: enhance parental controls with bulk edit, improved cert lookup, trending filtering, and blockAdult#1

Open
mcfalld wants to merge 3 commits intoProgenyAlpha:feature/parental-controlsfrom
mcfalld:combined-parental-controls
Open

feat: enhance parental controls with bulk edit, improved cert lookup, trending filtering, and blockAdult#1
mcfalld wants to merge 3 commits intoProgenyAlpha:feature/parental-controlsfrom
mcfalld:combined-parental-controls

Conversation

@mcfalld
Copy link
Copy Markdown

@mcfalld mcfalld commented Feb 20, 2026

Hey! Following up on my comment on seerr-team#2415 — here are the enhancements I mentioned, built on top of your branch.

What this adds

Enhanced certification lookup

The existing filterMovieBatch grabs the first US release date cert it finds. This can pick up an "NR" from an unrated director's cut instead of the theatrical "R" rating. This change collects all US release date certifications, excludes NR/unrated entries, and returns the most restrictive one. Falls back to international ratings if no US cert exists. Same getMovie() API call — just smarter parsing of the response.

Trending route filtering

/trending was the only discover route without parental controls filtering. This splits trending results into movies/tv/other, runs the existing postFilterDiscoverMovies and postFilterDiscoverTv on the respective sets. Only activates when the user has parental controls set — zero overhead otherwise.

Block Adult content (optional)

Adds a blockAdult boolean checkbox alongside blockUnrated. This filters on TMDB's adult flag, which covers explicit/pornographic content and is separate from the MPAA rating system. It's a free in-memory results.filter(m => !m.adult) — no extra API calls since the field is already in every TMDB response.

Bulk edit parental controls

Admins can now set parental controls (movie rating, TV rating, block unrated, block adult) on multiple users at once via the existing bulk edit modal in the user list. The backend creates UserSettings if needed and only updates the fields that are provided.

Debug logging

Blocked items now log the title, ID, certification, and max rating at debug level for easier troubleshooting.

Files changed

  • server/constants/contentRatings.ts — added blockAdult to interface
  • server/entity/UserSettings.ts — added blockAdult column
  • server/interfaces/api/userSettingsInterfaces.ts — added blockAdult to response type
  • server/routes/discover.ts — enhanced cert lookup, adult pre-filter, trending filtering, logging
  • server/routes/user/index.ts — bulk edit with parental controls
  • server/routes/user/usersettings.tsblockAdult in GET/POST endpoints
  • server/migration/postgres/1770627987305-AddBlockAdult.ts — new migration
  • server/migration/sqlite/1770627987305-AddBlockAdult.ts — new migration
  • src/components/UserList/BulkEditModal.tsx — parental controls UI in bulk edit
  • src/components/UserProfile/UserSettings/UserParentalControlsSettings/index.tsx — blockAdult checkbox

ProgenyAlpha and others added 2 commits February 14, 2026 01:53
Admin-enforced content rating limits per user:
- Max movie rating (MPAA: G through NC-17)
- Max TV rating (US Parental Guidelines: TV-Y through TV-MA)
- Block unrated content toggle (fail-closed)

Filtering applied to all discover routes and search with parallel
TMDB certification lookups. Backfills from next page when filtering
drops results below 15.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mcfalld mcfalld force-pushed the combined-parental-controls branch from 0f47a8e to c2cdbad Compare February 20, 2026 07:04
@mcfalld
Copy link
Copy Markdown
Author

mcfalld commented Feb 20, 2026

Found this while testing — postFilterDiscoverMovies and postFilterDiscoverTv both had an early return gated on blockUnrated:

if (!limits.blockUnrated) return filtered;  ← skips ALL rating filtering
This meant the filterMovieBatch/filterTvBatch calls — which check maxMovieRating/maxTvRating — only ran when blockUnrated was also enabled. For routes like /trending that don't support TMDB's certification.lte query param, the post-filter is the only line of defense. With blockUnrated: false and maxMovieRating: "PG-13", R-rated movies (Scream 7, 28 Years Later, etc.) were passing through completely unfiltered.

Also fixed filterMovieBatch/filterTvBatch, which hardcoded true for the blockUnrated parameter in shouldFilterMovie/shouldFilterTv, meaning unrated content would be blocked even when the user hadn't enabled that option.

Repro: Set a user to PG-13 max, leave "block unrated" unchecked → R-rated movies visible on trending/discover.

Critical bug: postFilterDiscoverMovies and postFilterDiscoverTv had an early return that only ran the rating post-filter when blockUnrated was enabled. Routes like /trending that lack TMDB certification.lte pre-filter rely entirely on post-filtering, so R-rated movies and TV-MA shows passed through unfiltered when a user had a max rating set but blockUnrated was false.

Fixes: postFilterDiscoverMovies runs when maxMovieRating OR blockUnrated is set. postFilterDiscoverTv runs when maxTvRating OR blockUnrated is set. filterMovieBatch and filterTvBatch use actual limits.blockUnrated instead of hardcoded true.
@mcfalld mcfalld force-pushed the combined-parental-controls branch from c2cdbad to 415d4ff Compare February 20, 2026 07:39
@mcfalld
Copy link
Copy Markdown
Author

mcfalld commented Feb 20, 2026

Follow-up: pushed a fix for two issues I found during local testing.

Bug — R-rated content visible to restricted users
During testing I noticed Scream 7 (rated R) was showing up on the trending page for a user with a PG-13 limit. The root cause was in postFilterDiscoverMovies/postFilterDiscoverTv — the post-filter was gated behind if (!limits.blockUnrated) return, so it would short-circuit and never actually filter by rating unless blockUnrated was enabled. Fixed the gate to also check maxMovieRating/maxTvRating. Also fixed a related issue in filterMovieBatch/filterTvBatch where blockUnrated was hardcoded to true instead of using limits.blockUnrated.

Performance — avoid redundant post-filtering on pre-filtered routes
The bug fix above caused post-filtering to run on every discover route, even the ones that already pass certification.lte to the TMDB API. That meant ~260 extra TMDB detail lookups per page load — unnecessary since those routes already exclude content above the user's rating at the API level.

Fix: added a preFiltered parameter (default true) to both post-filter functions. When preFiltered is true (all routes that use certification.lte), the post-filter only activates for blockUnrated (which TMDB can't handle). When preFiltered is false (only the trending route, since /trending/all doesn't support certification filtering), it also filters by maxRating. Net result: trending is the only route that does item-level TMDB lookups for rating checks — everything else skips it.

@mcfalld mcfalld force-pushed the combined-parental-controls branch from ba9d386 to 415d4ff Compare February 20, 2026 07:59
@ProgenyAlpha
Copy link
Copy Markdown
Owner

Hey! Sorry, saw your email but it's been a busy week so I didn't get a chance to reply. Just saw your PR, so I'll dig into your comments and review everything this weekend. Thanks for putting this together!

@mcfalld
Copy link
Copy Markdown
Author

mcfalld commented Feb 21, 2026

No problem! I'm just really glad someone else wants this as bad as I do! I hope you get to look at it and give it a test.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 21, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Comment @coderabbitai help to get the list of available commands and usage tips.

@mcfalld
Copy link
Copy Markdown
Author

mcfalld commented Feb 21, 2026

Just doing some follow-up testing.

When I change a user's max rating to no restriction, and go to log in, it's still filtering those items. Then I log back in to the admin account and review that user's parental control settings, and it's back to pg-13. So it seems like it's not saving the change I'm making. Could it be because of a database change? -- This only happens when going back to no restriction after setting a different value.

The other thing, trending is filtering properly now, but when you open the trending page (and have ratings applied), it seems like it has a fixed number of results. So, it can't be scrolled. Is that expected?
image

@mcfalld
Copy link
Copy Markdown
Author

mcfalld commented Feb 25, 2026

should i close this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants