Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions seerr-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,38 @@ components:
oneOf:
- $ref: '#/components/schemas/MovieResult'
- $ref: '#/components/schemas/TvResult'
CollectionResult:
type: object
required:
- id
- mediaType
- title
properties:
id:
type: number
example: 10
mediaType:
type: string
default: 'collection'
title:
type: string
example: Star Wars Collection
originalTitle:
type: string
example: Star Wars Collection
adult:
type: boolean
example: false
posterPath:
type: string
backdropPath:
type: string
overview:
type: string
example: Overview of the collection
originalLanguage:
type: string
example: 'en'
Genre:
type: object
properties:
Expand Down Expand Up @@ -5491,8 +5523,8 @@ paths:
type: number
/search:
get:
summary: Search for movies, TV shows, or people
description: Returns a list of movies, TV shows, or people a JSON object.
summary: Search for movies, TV shows, people, or collections
description: Returns a list of movies, TV shows, people, or collections as a JSON object.
tags:
- search
parameters:
Expand Down Expand Up @@ -5537,6 +5569,7 @@ paths:
- $ref: '#/components/schemas/MovieResult'
- $ref: '#/components/schemas/TvResult'
- $ref: '#/components/schemas/PersonResult'
- $ref: '#/components/schemas/CollectionResult'
/search/keyword:
get:
summary: Search for keywords
Expand Down
26 changes: 26 additions & 0 deletions server/api/themoviedb/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type {
TmdbPersonDetails,
TmdbProductionCompany,
TmdbRegion,
TmdbSearchCollectionResponse,
TmdbSearchMovieResponse,
TmdbSearchMultiResponse,
TmdbSearchTvResponse,
Expand Down Expand Up @@ -280,6 +281,31 @@ class TheMovieDb extends ExternalAPI implements TvShowProvider {
}
};

public searchCollections = async ({
query,
page = 1,
includeAdult = false,
language = this.locale,
}: SearchOptions): Promise<TmdbSearchCollectionResponse> => {
try {
const data = await this.get<TmdbSearchCollectionResponse>(
'/search/collection',
{
params: { query, page, include_adult: includeAdult, language },
}
);

return data;
} catch {
return {
page: 1,
results: [],
total_pages: 1,
total_results: 0,
};
}
};

public getPerson = async ({
personId,
language = this.locale,
Expand Down
15 changes: 15 additions & 0 deletions server/api/themoviedb/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ export interface TmdbCollectionResult {
original_language: string;
}

export interface TmdbCollectionSearchResult {
id: number;
adult: boolean;
name: string;
original_name: string;
original_language: string;
overview: string;
poster_path?: string;
backdrop_path?: string;
}

export interface TmdbPersonResult {
id: number;
name: string;
Expand Down Expand Up @@ -73,6 +84,10 @@ export interface TmdbSearchTvResponse extends TmdbPaginatedResponse {
results: TmdbTvResult[];
}

export interface TmdbSearchCollectionResponse extends TmdbPaginatedResponse {
results: TmdbCollectionSearchResult[];
}

export interface TmdbUpcomingMoviesResponse extends TmdbPaginatedResponse {
dates: {
maximum: string;
Expand Down
82 changes: 76 additions & 6 deletions server/routes/search.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import TheMovieDb from '@server/api/themoviedb';
import type { TmdbSearchMultiResponse } from '@server/api/themoviedb/interfaces';
import type {
TmdbCollectionResult,
TmdbSearchCollectionResponse,
TmdbSearchMultiResponse,
} from '@server/api/themoviedb/interfaces';
import Media from '@server/entity/Media';
import cacheManager from '@server/lib/cache';
import { findSearchProvider } from '@server/lib/search';
import logger from '@server/logger';
import { mapSearchResults } from '@server/models/Search';
Expand All @@ -25,12 +30,77 @@ searchRoutes.get('/', async (req, res, next) => {
});
} else {
const tmdb = new TheMovieDb();
const page = Number(req.query.page) || 1;
const language = (req.query.language as string) ?? req.locale;

results = await tmdb.searchMulti({
query: queryString,
page: Number(req.query.page),
language: (req.query.language as string) ?? req.locale,
});
const tmdbCache = cacheManager.getCache('tmdb').data;
const pagesKey = `search-collections-pages:${language}:${queryString}`;
const knownCollections = tmdbCache.get<{
total_pages: number;
total_results: number;
}>(pagesKey);

const fetchCollections =
async (): Promise<TmdbSearchCollectionResponse> => {
if (knownCollections && page > knownCollections.total_pages) {
return {
page,
results: [],
total_pages: knownCollections.total_pages,
total_results: knownCollections.total_results,
};
}

const collections = await tmdb.searchCollections({
query: queryString,
page,
language,
});

tmdbCache.set(
pagesKey,
{
total_pages: collections.total_pages,
total_results: collections.total_results,
},
300
);
return collections;
};

const [multi, collections] = await Promise.all([
tmdb.searchMulti({
query: queryString,
page,
language,
}),
fetchCollections(),
]);

const collectionResults: TmdbCollectionResult[] = collections.results.map(
(collection) => ({
id: collection.id,
media_type: 'collection',
adult: collection.adult,
title: collection.name,
original_title: collection.original_name,
overview: collection.overview,
original_language: collection.original_language,
poster_path: collection.poster_path,
backdrop_path: collection.backdrop_path,
})
);

const multiWithoutCollections = multi.results.filter(
(result) => result.media_type !== 'collection'
);

results = {
page,
total_pages: Math.max(multi.total_pages, collections.total_pages),
total_results: multi.total_results + collections.total_results,
results: [...collectionResults, ...multiWithoutCollections],
Comment thread
0xSysR3ll marked this conversation as resolved.
};
}

const media = await Media.getRelatedMedia(
Expand Down
3 changes: 2 additions & 1 deletion src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import useDiscover from '@app/hooks/useDiscover';
import ErrorPage from '@app/pages/_error';
import defineMessages from '@app/utils/defineMessages';
import type {
CollectionResult,
MovieResult,
PersonResult,
TvResult,
Expand All @@ -29,7 +30,7 @@ const Search = () => {
titles,
fetchMore,
error,
} = useDiscover<MovieResult | TvResult | PersonResult>(
} = useDiscover<MovieResult | TvResult | PersonResult | CollectionResult>(
`/api/v1/search`,
{
query: router.query.query,
Expand Down
9 changes: 6 additions & 3 deletions src/hooks/useDiscover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,14 @@ const useDiscover = <
}

const isEmpty = !isLoadingInitialData && titles?.length === 0;
const lastPageData = data?.[data.length - 1];
const totalPages = lastPageData?.totalPages ?? 0;
const isReachingEnd =
isEmpty ||
(!!data && (data[data?.length - 1]?.results.length ?? 0) < 20) ||
(!!data && (data[data?.length - 1]?.totalResults ?? 0) <= size * 20) ||
(!!data && (data[data?.length - 1]?.totalResults ?? 0) < 41);
(!!lastPageData &&
(totalPages > 0
? size >= totalPages
: (lastPageData.results.length ?? 0) < 20));

useEffect(() => {
if (error && titles.length) {
Expand Down
Loading