mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-10-15 22:55:42 -04:00
feat(search): find titles by IMDb ID
Search queries that look like an IMDb ID (e.g. tt0133093) are now resolved through TMDB's find endpoint, respecting the movie/tv type filter. Supersedes #60. Co-authored-by: Gonçalo Fonseca <gcare.fonseca99@outlook.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2 files changed
+116
-2
No files matched your search
@@ -332,3 +332,94 @@ describe("discover.browse", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("search by IMDb ID", () => {
|
||||
const found = { movie_results: [movie], tv_results: [show] };
|
||||
|
||||
test("resolves an IMDb ID through the find endpoint instead of text search", async () => {
|
||||
tmdb.findByExternalId.mockResolvedValue(found);
|
||||
|
||||
const result = await call(
|
||||
implementedRouter.discover.search,
|
||||
{ query: "tt0133093", page: 1 },
|
||||
ctx,
|
||||
);
|
||||
|
||||
expect(tmdb.findByExternalId).toHaveBeenCalledWith("tt0133093", "imdb_id");
|
||||
expect(tmdb.searchMulti).not.toHaveBeenCalled();
|
||||
expect(result.results).toHaveLength(2);
|
||||
expect(result.results[0]).toMatchObject({ tmdbId: 603, type: "movie", title: "The Matrix" });
|
||||
expect(result.results[0]?.id).toBeTruthy();
|
||||
expect(result.results[1]).toMatchObject({
|
||||
tmdbId: 1399,
|
||||
type: "tv",
|
||||
title: "Game of Thrones",
|
||||
});
|
||||
expect(result.results[1]?.id).toBeTruthy();
|
||||
expect(result.page).toBe(1);
|
||||
expect(result.totalPages).toBe(1);
|
||||
expect(result.totalResults).toBe(2);
|
||||
});
|
||||
|
||||
test("lowercases the IMDb ID", async () => {
|
||||
tmdb.findByExternalId.mockResolvedValue(found);
|
||||
|
||||
await call(implementedRouter.discover.search, { query: "TT0133093", page: 1 }, ctx);
|
||||
|
||||
expect(tmdb.findByExternalId).toHaveBeenCalledWith("tt0133093", "imdb_id");
|
||||
});
|
||||
|
||||
test("respects the type filter", async () => {
|
||||
tmdb.findByExternalId.mockResolvedValue(found);
|
||||
|
||||
const result = await call(
|
||||
implementedRouter.discover.search,
|
||||
{ query: "tt0133093", type: "movie", page: 1 },
|
||||
ctx,
|
||||
);
|
||||
|
||||
expect(result.results).toHaveLength(1);
|
||||
expect(result.results[0]).toMatchObject({ tmdbId: 603, type: "movie" });
|
||||
});
|
||||
|
||||
test("returns no pages when the ID matches nothing", async () => {
|
||||
tmdb.findByExternalId.mockResolvedValue({ movie_results: [], tv_results: [] });
|
||||
|
||||
const result = await call(
|
||||
implementedRouter.discover.search,
|
||||
{ query: "tt0133093", page: 1 },
|
||||
ctx,
|
||||
);
|
||||
|
||||
expect(result.results).toEqual([]);
|
||||
expect(result.totalPages).toBe(0);
|
||||
expect(result.totalResults).toBe(0);
|
||||
});
|
||||
|
||||
test("treats a too-short ID as a regular text query", async () => {
|
||||
tmdb.searchMulti.mockResolvedValue({ page: 1, total_pages: 1, total_results: 0, results: [] });
|
||||
|
||||
await call(implementedRouter.discover.search, { query: "tt123", page: 1 }, ctx);
|
||||
|
||||
expect(tmdb.searchMulti).toHaveBeenCalledWith("tt123", 1);
|
||||
expect(tmdb.findByExternalId).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("person searches never use the find endpoint", async () => {
|
||||
tmdb.searchPerson.mockResolvedValue({
|
||||
page: 1,
|
||||
total_pages: 1,
|
||||
total_results: 0,
|
||||
results: [],
|
||||
});
|
||||
|
||||
await call(
|
||||
implementedRouter.discover.search,
|
||||
{ query: "tt0133093", type: "person", page: 1 },
|
||||
ctx,
|
||||
);
|
||||
|
||||
expect(tmdb.searchPerson).toHaveBeenCalledWith("tt0133093", 1);
|
||||
expect(tmdb.findByExternalId).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,7 @@ import type { DiscoverInput } from "@sofa/api/schemas";
|
||||
import { WATCH_REGION } from "@sofa/config";
|
||||
import {
|
||||
discover,
|
||||
findByExternalId,
|
||||
getGenres,
|
||||
getPopular,
|
||||
getTrending,
|
||||
@@ -281,6 +282,27 @@ async function searchPeople(query: string, requestedPage: number) {
|
||||
};
|
||||
}
|
||||
|
||||
// IMDb IDs (e.g. tt0133093) are resolved via TMDB's find endpoint instead of text search.
|
||||
const IMDB_ID_PATTERN = /^tt\d{7,10}$/i;
|
||||
|
||||
async function findByImdbId(imdbId: string, type: "movie" | "tv" | null): Promise<TmdbPage> {
|
||||
const found = await findByExternalId(imdbId.toLowerCase(), "imdb_id");
|
||||
const results = [
|
||||
...(type === "tv"
|
||||
? []
|
||||
: (found.movie_results ?? []).map((r) => Object.assign({}, r, { media_type: "movie" }))),
|
||||
...(type === "movie"
|
||||
? []
|
||||
: (found.tv_results ?? []).map((r) => Object.assign({}, r, { media_type: "tv" }))),
|
||||
];
|
||||
return {
|
||||
page: 1,
|
||||
total_pages: results.length > 0 ? 1 : 0,
|
||||
total_results: results.length,
|
||||
results,
|
||||
};
|
||||
}
|
||||
|
||||
export async function searchCatalog(input: {
|
||||
query: string;
|
||||
type?: SearchType | null;
|
||||
@@ -296,8 +318,9 @@ export async function searchCatalog(input: {
|
||||
return searchPeople(query, input.page);
|
||||
}
|
||||
|
||||
const raw: TmdbPage =
|
||||
type === "movie"
|
||||
const raw: TmdbPage = IMDB_ID_PATTERN.test(query)
|
||||
? await findByImdbId(query, type)
|
||||
: type === "movie"
|
||||
? await searchMovies(query, input.page)
|
||||
: type === "tv"
|
||||
? await searchTv(query, input.page)
|
||||
|
||||
Reference in new issue
Block a user