From 72c825d2a5109688bcc1991a30249284bf281500 Mon Sep 17 00:00:00 2001 From: Danshil Kokil Mungur Date: Mon, 14 Mar 2022 17:37:58 +0400 Subject: [PATCH] feat(search): filter search results by year (#2460) * feat(search): filter search results by year * fix: typo in endpoint, blame it on new brand of coffee * feat(search): suggested changes --- server/api/themoviedb/index.ts | 56 +++++++++++++++++++++++ server/lib/search.ts | 83 ++++++++++++++++++++++++++-------- server/routes/search.ts | 7 +-- 3 files changed, 123 insertions(+), 23 deletions(-) diff --git a/server/api/themoviedb/index.ts b/server/api/themoviedb/index.ts index fceb07d23..cf5e280c2 100644 --- a/server/api/themoviedb/index.ts +++ b/server/api/themoviedb/index.ts @@ -28,6 +28,10 @@ interface SearchOptions { language?: string; } +interface SingleSearchOptions extends SearchOptions { + year?: number; +} + interface DiscoverMovieOptions { page?: number; includeAdult?: boolean; @@ -116,6 +120,58 @@ class TheMovieDb extends ExternalAPI { } }; + public searchMovies = async ({ + query, + page = 1, + includeAdult = false, + language = 'en', + year, + }: SingleSearchOptions): Promise => { + try { + const data = await this.get('/search/movie', { + params: { query, page, include_adult: includeAdult, language, year }, + }); + + return data; + } catch (e) { + return { + page: 1, + results: [], + total_pages: 1, + total_results: 0, + }; + } + }; + + public searchTvShows = async ({ + query, + page = 1, + includeAdult = false, + language = 'en', + year, + }: SingleSearchOptions): Promise => { + try { + const data = await this.get('/search/tv', { + params: { + query, + page, + include_adult: includeAdult, + language, + first_air_date_year: year, + }, + }); + + return data; + } catch (e) { + return { + page: 1, + results: [], + total_pages: 1, + total_results: 0, + }; + } + }; + public getPerson = async ({ personId, language = 'en', diff --git a/server/lib/search.ts b/server/lib/search.ts index b66079c83..c625f512d 100644 --- a/server/lib/search.ts +++ b/server/lib/search.ts @@ -4,7 +4,9 @@ import { TmdbMovieResult, TmdbPersonDetails, TmdbPersonResult, + TmdbSearchMovieResponse, TmdbSearchMultiResponse, + TmdbSearchTvResponse, TmdbTvDetails, TmdbTvResult, } from '../api/themoviedb/interfaces'; @@ -13,14 +15,19 @@ import { mapPersonDetailsToResult, mapTvDetailsToResult, } from '../models/Search'; -import { isMovieDetails, isTvDetails } from '../utils/typeHelpers'; - -type SearchProviderId = 'TMDb' | 'IMDb' | 'TVDB'; +import { isMovie, isMovieDetails, isTvDetails } from '../utils/typeHelpers'; interface SearchProvider { - id: SearchProviderId; pattern: RegExp; - search: (id: string, language?: string) => Promise; + search: ({ + id, + language, + query, + }: { + id: string; + language?: string; + query?: string; + }) => Promise; } const searchProviders: SearchProvider[] = []; @@ -32,12 +39,8 @@ export const findSearchProvider = ( }; searchProviders.push({ - id: 'TMDb', pattern: new RegExp(/(?<=tmdb:)\d+/), - search: async ( - id: string, - language?: string - ): Promise => { + search: async ({ id, language }) => { const tmdb = new TheMovieDb(); const moviePromise = tmdb.getMovie({ movieId: parseInt(id), language }); @@ -85,12 +88,8 @@ searchProviders.push({ }); searchProviders.push({ - id: 'IMDb', pattern: new RegExp(/(?<=imdb:)(tt|nm)\d+/), - search: async ( - id: string, - language?: string - ): Promise => { + search: async ({ id, language }) => { const tmdb = new TheMovieDb(); const responses = await tmdb.getByExternalId({ @@ -127,12 +126,8 @@ searchProviders.push({ }); searchProviders.push({ - id: 'TVDB', pattern: new RegExp(/(?<=tvdb:)\d+/), - search: async ( - id: string, - language?: string - ): Promise => { + search: async ({ id, language }) => { const tmdb = new TheMovieDb(); const responses = await tmdb.getByExternalId({ @@ -167,3 +162,51 @@ searchProviders.push({ }; }, }); + +searchProviders.push({ + pattern: new RegExp(/(?<=year:)\d{4}/), + search: async ({ id: year, query }) => { + const tmdb = new TheMovieDb(); + + const moviesPromise = tmdb.searchMovies({ + query: query?.replace(new RegExp(/year:\d{4}/), '') ?? '', + year: parseInt(year), + }); + const tvShowsPromise = tmdb.searchTvShows({ + query: query?.replace(new RegExp(/year:\d{4}/), '') ?? '', + year: parseInt(year), + }); + + const responses = await Promise.allSettled([moviesPromise, tvShowsPromise]); + + const successfulResponses = responses.filter( + (r) => r.status === 'fulfilled' + ) as + | ( + | PromiseFulfilledResult + | PromiseFulfilledResult + )[]; + + const results: (TmdbMovieResult | TmdbTvResult)[] = []; + + if (successfulResponses.length) { + successfulResponses.forEach((response) => { + response.value.results.forEach((result) => + // set the media_type here since the search endpoints don't return it + results.push( + isMovie(result) + ? { ...result, media_type: 'movie' } + : { ...result, media_type: 'tv' } + ) + ); + }); + } + + return { + page: 1, + total_pages: 1, + total_results: results.length, + results, + }; + }, +}); diff --git a/server/routes/search.ts b/server/routes/search.ts index ca59976ac..3f26a3939 100644 --- a/server/routes/search.ts +++ b/server/routes/search.ts @@ -18,10 +18,11 @@ searchRoutes.get('/', async (req, res, next) => { const [id] = queryString .toLowerCase() .match(searchProvider.pattern) as RegExpMatchArray; - results = await searchProvider.search( + results = await searchProvider.search({ id, - req.locale ?? (req.query.language as string) - ); + language: req.locale ?? (req.query.language as string), + query: queryString, + }); } else { const tmdb = new TheMovieDb();