-
-
Notifications
You must be signed in to change notification settings - Fork 987
feat(discover): filter movie discovery by minimum release #3384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5595,6 +5595,19 @@ paths: | |
| schema: | ||
| type: string | ||
| example: 2023-01-01 | ||
| - in: query | ||
| name: releaseType | ||
| schema: | ||
| type: string | ||
| enum: ['2', '3', '4', '5', '6'] | ||
| example: '4' | ||
| description: >- | ||
| Minimum release availability for movie discovery. The selected type | ||
| and everything above it are matched (e.g. 4 = digital also matches 5 | ||
| = physical and 6 = TV, sent to TMDB as with_release_type=4|5|6). | ||
| When set, results are filtered on release_date instead of | ||
| primary_release_date. 2 = limited theatrical, 3 = theatrical, | ||
| 4 = digital, 5 = physical, 6 = TV | ||
|
Comment on lines
+5604
to
+5610
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -u
for file in \
"src/components/Discover/FilterSlideover/index.tsx" \
"server/routes/discover.ts" \
"server/api/themoviedb/index.ts"; do
printf '\n== %s ==\n' "$file"
rg -n -C 10 \
'releaseType|primaryReleaseDateGte|primaryReleaseDateLte|release_date|defaultFutureDate|new Date' \
"$file" || true
doneRepository: seerr-team/seerr Length of output: 14899 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
route = Path("server/routes/discover.ts").read_text()
client = Path("server/api/themoviedb/index.ts").read_text()
ui = Path("src/components/Discover/FilterSlideover/index.tsx").read_text()
assert "releaseType: query.releaseType" in route
assert "primaryReleaseDateLte: query.primaryReleaseDateLte" in route
assert "primaryReleaseDateGte: query.primaryReleaseDateGte" in route
assert "with_release_type: releaseTypeFilter" in client
assert "if (value && !currentFilters[dateLte])" in ui
assert "[dateLte]: today" in ui
def tmdb_dates(gte=None, lte=None):
# Mirrors the date-bound logic in getDiscoverMovies.
past = "1900-01-01"
future = "<defaultFutureDate>"
return (
past if not gte and lte else gte,
future if not lte and gte else lte,
)
print("route forwards releaseType and both date fields without adding a default")
print("no dates ->", tmdb_dates())
print("gte only ->", tmdb_dates("2025-01-01"))
print("lte only ->", tmdb_dates(lte="2025-01-01"))
print("UI adds today's dateLte only when the user selects releaseType")
PYRepository: seerr-team/seerr Length of output: 406 Apply or document the The UI sets 🤖 Prompt for AI Agents |
||
| - in: query | ||
| name: withRuntimeGte | ||
| schema: | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -89,6 +89,7 @@ interface DiscoverMovieOptions { | |||||||||||||||||||||||||||||||||||||
| keywords?: string; | ||||||||||||||||||||||||||||||||||||||
| excludeKeywords?: string; | ||||||||||||||||||||||||||||||||||||||
| sortBy?: SortOptions; | ||||||||||||||||||||||||||||||||||||||
| releaseType?: string; | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||
| watchRegion?: string; | ||||||||||||||||||||||||||||||||||||||
| watchProviders?: string; | ||||||||||||||||||||||||||||||||||||||
| certification?: string; | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -601,6 +602,7 @@ class TheMovieDb extends ExternalAPI implements TvShowProvider { | |||||||||||||||||||||||||||||||||||||
| voteAverageLte, | ||||||||||||||||||||||||||||||||||||||
| voteCountGte, | ||||||||||||||||||||||||||||||||||||||
| voteCountLte, | ||||||||||||||||||||||||||||||||||||||
| releaseType, | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||
| watchProviders, | ||||||||||||||||||||||||||||||||||||||
| watchRegion, | ||||||||||||||||||||||||||||||||||||||
| certification, | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -619,6 +621,21 @@ class TheMovieDb extends ExternalAPI implements TvShowProvider { | |||||||||||||||||||||||||||||||||||||
| .toISOString() | ||||||||||||||||||||||||||||||||||||||
| .split('T')[0]; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // "Minimum availability" is cumulative: picking Digital (4) should also | ||||||||||||||||||||||||||||||||||||||
| // match Physical (5) and TV (6), so expand the selected type up to 6. | ||||||||||||||||||||||||||||||||||||||
| // with_release_type is a no-op on TMDB unless it's paired with a | ||||||||||||||||||||||||||||||||||||||
| // release_date range, so when it's set we filter on release_date instead | ||||||||||||||||||||||||||||||||||||||
| // of primary_release_date (the field the plain Release Date filter uses). | ||||||||||||||||||||||||||||||||||||||
| const releaseTypeFilter = releaseType | ||||||||||||||||||||||||||||||||||||||
| ? Array.from( | ||||||||||||||||||||||||||||||||||||||
| { length: 6 - Number(releaseType) + 1 }, | ||||||||||||||||||||||||||||||||||||||
| (_, i) => Number(releaseType) + i | ||||||||||||||||||||||||||||||||||||||
| ).join('|') | ||||||||||||||||||||||||||||||||||||||
| : undefined; | ||||||||||||||||||||||||||||||||||||||
| const releaseDateField = releaseType | ||||||||||||||||||||||||||||||||||||||
| ? 'release_date' | ||||||||||||||||||||||||||||||||||||||
| : 'primary_release_date'; | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+629
to
+637
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const data = await this.get<TmdbSearchMovieResponse>('/discover/movie', { | ||||||||||||||||||||||||||||||||||||||
| params: { | ||||||||||||||||||||||||||||||||||||||
| sort_by: sortBy, | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -635,14 +652,15 @@ class TheMovieDb extends ExternalAPI implements TvShowProvider { | |||||||||||||||||||||||||||||||||||||
| : this.originalLanguage, | ||||||||||||||||||||||||||||||||||||||
| // Set our release date values, but check if one is set and not the other, | ||||||||||||||||||||||||||||||||||||||
| // so we can force a past date or a future date. TMDB Requires both values if one is set! | ||||||||||||||||||||||||||||||||||||||
| 'primary_release_date.gte': | ||||||||||||||||||||||||||||||||||||||
| [`${releaseDateField}.gte`]: | ||||||||||||||||||||||||||||||||||||||
| !primaryReleaseDateGte && primaryReleaseDateLte | ||||||||||||||||||||||||||||||||||||||
| ? defaultPastDate | ||||||||||||||||||||||||||||||||||||||
| : primaryReleaseDateGte, | ||||||||||||||||||||||||||||||||||||||
| 'primary_release_date.lte': | ||||||||||||||||||||||||||||||||||||||
| [`${releaseDateField}.lte`]: | ||||||||||||||||||||||||||||||||||||||
| !primaryReleaseDateLte && primaryReleaseDateGte | ||||||||||||||||||||||||||||||||||||||
| ? defaultFutureDate | ||||||||||||||||||||||||||||||||||||||
| : primaryReleaseDateLte, | ||||||||||||||||||||||||||||||||||||||
| with_release_type: releaseTypeFilter, | ||||||||||||||||||||||||||||||||||||||
| with_genres: genre, | ||||||||||||||||||||||||||||||||||||||
| with_companies: studio, | ||||||||||||||||||||||||||||||||||||||
| with_keywords: keywords, | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -65,6 +65,7 @@ const QueryFilterOptions = z.object({ | |||||
| sortBy: z.coerce.string().optional(), | ||||||
| primaryReleaseDateGte: z.coerce.string().optional(), | ||||||
| primaryReleaseDateLte: z.coerce.string().optional(), | ||||||
|
Comment on lines
66
to
67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be worth renaming these to |
||||||
| releaseType: z.enum(['2', '3', '4', '5', '6']).optional(), | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| firstAirDateGte: z.coerce.string().optional(), | ||||||
| firstAirDateLte: z.coerce.string().optional(), | ||||||
| studio: z.coerce.string().optional(), | ||||||
|
|
@@ -115,6 +116,7 @@ discoverRoutes.get('/movies', async (req, res, next) => { | |||||
| primaryReleaseDateGte: query.primaryReleaseDateGte | ||||||
| ? new Date(query.primaryReleaseDateGte).toISOString().split('T')[0] | ||||||
| : undefined, | ||||||
| releaseType: query.releaseType, | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| keywords, | ||||||
| excludeKeywords, | ||||||
| withRuntimeGte: query.withRuntimeGte, | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -27,6 +27,13 @@ const messages = defineMessages('components.Discover.FilterSlideover', { | |||||||||||||||||||||||||||||||||||||||||||||||
| activefilters: | ||||||||||||||||||||||||||||||||||||||||||||||||
| '{count, plural, one {# Active Filter} other {# Active Filters}}', | ||||||||||||||||||||||||||||||||||||||||||||||||
| releaseDate: 'Release Date', | ||||||||||||||||||||||||||||||||||||||||||||||||
| minimumRelease: 'Minimum Release', | ||||||||||||||||||||||||||||||||||||||||||||||||
| anyRelease: 'Any', | ||||||||||||||||||||||||||||||||||||||||||||||||
| limitedTheatrical: 'Theatrical (Limited)', | ||||||||||||||||||||||||||||||||||||||||||||||||
| theatrical: 'Theatrical', | ||||||||||||||||||||||||||||||||||||||||||||||||
| digital: 'Digital', | ||||||||||||||||||||||||||||||||||||||||||||||||
| physical: 'Physical', | ||||||||||||||||||||||||||||||||||||||||||||||||
| tvRelease: 'TV', | ||||||||||||||||||||||||||||||||||||||||||||||||
| firstAirDate: 'First Air Date', | ||||||||||||||||||||||||||||||||||||||||||||||||
| from: 'From', | ||||||||||||||||||||||||||||||||||||||||||||||||
| to: 'To', | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -133,6 +140,49 @@ const FilterSlideover = ({ | |||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||
| {type === 'movie' && ( | ||||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="text-lg font-semibold"> | ||||||||||||||||||||||||||||||||||||||||||||||||
| {intl.formatMessage(messages.minimumRelease)} | ||||||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+143
to
+145
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||
| <select | ||||||||||||||||||||||||||||||||||||||||||||||||
| id="releaseType" | ||||||||||||||||||||||||||||||||||||||||||||||||
| name="releaseType" | ||||||||||||||||||||||||||||||||||||||||||||||||
| value={currentFilters.releaseType ?? ''} | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+143
to
+149
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Associate the selector with its visible label. Line 143 renders the control label as a Proposed fix- <span className="text-lg font-semibold">
+ <label
+ htmlFor="releaseType"
+ className="text-lg font-semibold"
+ >
{intl.formatMessage(messages.minimumRelease)}
- </span>
+ </label>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Comment on lines
+147
to
+149
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={(e) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const value = e.target.value || undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||
| // with_release_type only filters when paired with a date range, | ||||||||||||||||||||||||||||||||||||||||||||||||
| // so default the "To" date to today if the user hasn't set one. | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (value && !currentFilters[dateLte]) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const now = new Date(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| const today = `${now.getFullYear()}-${String( | ||||||||||||||||||||||||||||||||||||||||||||||||
| now.getMonth() + 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
| ).padStart(2, '0')}-${String(now.getDate()).padStart( | ||||||||||||||||||||||||||||||||||||||||||||||||
| 2, | ||||||||||||||||||||||||||||||||||||||||||||||||
| '0' | ||||||||||||||||||||||||||||||||||||||||||||||||
| )}`; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+156
to
+161
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||
| batchUpdateQueryParams({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| releaseType: value, | ||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||
| [dateLte]: today, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||
| updateQueryParams('releaseType', value); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||
| <option value=""> | ||||||||||||||||||||||||||||||||||||||||||||||||
| {intl.formatMessage(messages.anyRelease)} | ||||||||||||||||||||||||||||||||||||||||||||||||
| </option> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <option value="2"> | ||||||||||||||||||||||||||||||||||||||||||||||||
| {intl.formatMessage(messages.limitedTheatrical)} | ||||||||||||||||||||||||||||||||||||||||||||||||
| </option> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <option value="3"> | ||||||||||||||||||||||||||||||||||||||||||||||||
| {intl.formatMessage(messages.theatrical)} | ||||||||||||||||||||||||||||||||||||||||||||||||
| </option> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <option value="4">{intl.formatMessage(messages.digital)}</option> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <option value="5">{intl.formatMessage(messages.physical)}</option> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <option value="6"> | ||||||||||||||||||||||||||||||||||||||||||||||||
| {intl.formatMessage(messages.tvRelease)} | ||||||||||||||||||||||||||||||||||||||||||||||||
| </option> | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+171
to
+184
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Comment on lines
+171
to
+184
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The mapping between Release Message Name (e.g. I would also add a link to the TMDB source https://developer.themoviedb.org/reference/movie-release-dates |
||||||||||||||||||||||||||||||||||||||||||||||||
| </select> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="text-lg font-semibold"> | ||||||||||||||||||||||||||||||||||||||||||||||||
| {intl.formatMessage(messages.studio)} | ||||||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -94,6 +94,7 @@ export const QueryFilterOptions = z.object({ | |||||||||||||
| sortBy: z.string().optional(), | ||||||||||||||
| primaryReleaseDateGte: z.string().optional(), | ||||||||||||||
| primaryReleaseDateLte: z.string().optional(), | ||||||||||||||
| releaseType: z.enum(['2', '3', '4', '5', '6']).optional(), | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
| firstAirDateGte: z.string().optional(), | ||||||||||||||
| firstAirDateLte: z.string().optional(), | ||||||||||||||
| studio: z.string().optional(), | ||||||||||||||
|
|
@@ -138,6 +139,10 @@ export const prepareFilterValues = ( | |||||||||||||
| filterValues.primaryReleaseDateLte = values.primaryReleaseDateLte; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (values.releaseType) { | ||||||||||||||
| filterValues.releaseType = values.releaseType; | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+142
to
+144
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
|
||||||||||||||
| if (values.firstAirDateGte) { | ||||||||||||||
| filterValues.firstAirDateGte = values.firstAirDateGte; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.