mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
Person API calls (#188)
* feat(frontend): person API call - details, combined credits * feat(frontend): add next for error handling + remove conditional * feat(frontend): add status code to next error
This commit is contained in:
@@ -803,6 +803,144 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
|
|
||||||
|
PersonDetail:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: number
|
||||||
|
example: 1
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
deathday:
|
||||||
|
type: string
|
||||||
|
knownForDepartment:
|
||||||
|
type: string
|
||||||
|
alsoKnownAs:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
gender:
|
||||||
|
type: string
|
||||||
|
biography:
|
||||||
|
type: string
|
||||||
|
popularity:
|
||||||
|
type: string
|
||||||
|
placeOfBirth:
|
||||||
|
type: string
|
||||||
|
profilePath:
|
||||||
|
type: string
|
||||||
|
adult:
|
||||||
|
type: boolean
|
||||||
|
imdbId:
|
||||||
|
type: string
|
||||||
|
homepage:
|
||||||
|
type: string
|
||||||
|
CreditCast:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: number
|
||||||
|
example: 1
|
||||||
|
originalLanguage:
|
||||||
|
type: string
|
||||||
|
episodeCount:
|
||||||
|
type: number
|
||||||
|
overview:
|
||||||
|
type: string
|
||||||
|
originCountry:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
originalName:
|
||||||
|
type: string
|
||||||
|
voteCount:
|
||||||
|
type: number
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
mediaType:
|
||||||
|
type: string
|
||||||
|
popularity:
|
||||||
|
type: number
|
||||||
|
creditId:
|
||||||
|
type: string
|
||||||
|
backdropPath:
|
||||||
|
type: string
|
||||||
|
firstAirDate:
|
||||||
|
type: string
|
||||||
|
voteAverage:
|
||||||
|
type: number
|
||||||
|
genreIds:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: number
|
||||||
|
posterPath:
|
||||||
|
type: string
|
||||||
|
originalTitle:
|
||||||
|
type: string
|
||||||
|
video:
|
||||||
|
type: boolean
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
adult:
|
||||||
|
type: boolean
|
||||||
|
releaseDate:
|
||||||
|
type: string
|
||||||
|
character:
|
||||||
|
type: string
|
||||||
|
CreditCrew:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: number
|
||||||
|
example: 1
|
||||||
|
originalLanguage:
|
||||||
|
type: string
|
||||||
|
episodeCount:
|
||||||
|
type: number
|
||||||
|
overview:
|
||||||
|
type: string
|
||||||
|
originCountry:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
originalName:
|
||||||
|
type: string
|
||||||
|
voteCount:
|
||||||
|
type: number
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
mediaType:
|
||||||
|
type: string
|
||||||
|
popularity:
|
||||||
|
type: number
|
||||||
|
creditId:
|
||||||
|
type: string
|
||||||
|
backdropPath:
|
||||||
|
type: string
|
||||||
|
firstAirDate:
|
||||||
|
type: string
|
||||||
|
voteAverage:
|
||||||
|
type: number
|
||||||
|
genreIds:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: number
|
||||||
|
posterPath:
|
||||||
|
type: string
|
||||||
|
originalTitle:
|
||||||
|
type: string
|
||||||
|
video:
|
||||||
|
type: boolean
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
adult:
|
||||||
|
type: boolean
|
||||||
|
releaseDate:
|
||||||
|
type: string
|
||||||
|
department:
|
||||||
|
type: string
|
||||||
|
job:
|
||||||
|
type: string
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
cookieAuth:
|
cookieAuth:
|
||||||
type: apiKey
|
type: apiKey
|
||||||
@@ -2177,6 +2315,68 @@ paths:
|
|||||||
criticsRating:
|
criticsRating:
|
||||||
type: string
|
type: string
|
||||||
enum: ['Rotten', 'Fresh']
|
enum: ['Rotten', 'Fresh']
|
||||||
|
/person/{personId}:
|
||||||
|
get:
|
||||||
|
summary: Request person details
|
||||||
|
description: Returns details of the person based on provided person ID in JSON format
|
||||||
|
tags:
|
||||||
|
- person
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: personId
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
example: 287
|
||||||
|
- in: query
|
||||||
|
name: language
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: en
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Returned person
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PersonDetail'
|
||||||
|
|
||||||
|
/person/{personId}/combined_credits:
|
||||||
|
get:
|
||||||
|
summary: Request combined credits of person
|
||||||
|
description: Returns the combined credits of the person based on the provided person ID in JSON format
|
||||||
|
tags:
|
||||||
|
- person
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: personId
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
example: 287
|
||||||
|
- in: query
|
||||||
|
name: language
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: en
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Returned combined credts
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
cast:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/CreditCast'
|
||||||
|
crew:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/CreditCrew"
|
||||||
|
id:
|
||||||
|
type: number
|
||||||
/media:
|
/media:
|
||||||
get:
|
get:
|
||||||
summary: Return all media
|
summary: Return all media
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import axios, { AxiosInstance } from 'axios';
|
import axios, { AxiosInstance } from 'axios';
|
||||||
import { number } from 'yup';
|
|
||||||
|
|
||||||
interface SearchOptions {
|
interface SearchOptions {
|
||||||
query: string;
|
query: string;
|
||||||
@@ -271,6 +270,60 @@ export interface TmdbTvDetails {
|
|||||||
external_ids: TmdbExternalIds;
|
external_ids: TmdbExternalIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TmdbPersonDetail {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
deathday: string;
|
||||||
|
known_for_department: string;
|
||||||
|
also_known_as?: string[];
|
||||||
|
gender: number;
|
||||||
|
biography: string;
|
||||||
|
popularity: string;
|
||||||
|
place_of_birth?: string;
|
||||||
|
profile_path?: string;
|
||||||
|
adult: boolean;
|
||||||
|
imdb_id?: string;
|
||||||
|
homepage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TmdbPersonCredit {
|
||||||
|
id: number;
|
||||||
|
original_language: string;
|
||||||
|
episode_count: number;
|
||||||
|
overview: string;
|
||||||
|
origin_country: string[];
|
||||||
|
original_name: string;
|
||||||
|
vote_count: number;
|
||||||
|
name: string;
|
||||||
|
media_type?: string;
|
||||||
|
popularity: number;
|
||||||
|
credit_id: string;
|
||||||
|
backdrop_path?: string;
|
||||||
|
first_air_date: string;
|
||||||
|
vote_average: number;
|
||||||
|
genre_ids?: number[];
|
||||||
|
poster_path?: string;
|
||||||
|
original_title: string;
|
||||||
|
video?: boolean;
|
||||||
|
title: string;
|
||||||
|
adult: boolean;
|
||||||
|
release_date: string;
|
||||||
|
}
|
||||||
|
export interface TmdbPersonCreditCast extends TmdbPersonCredit {
|
||||||
|
character: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TmdbPersonCreditCrew extends TmdbPersonCredit {
|
||||||
|
department: string;
|
||||||
|
job: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TmdbPersonCombinedCredits {
|
||||||
|
id: number;
|
||||||
|
cast: TmdbPersonCreditCast[];
|
||||||
|
crew: TmdbPersonCreditCrew[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface TmdbSeasonWithEpisodes extends TmdbTvSeasonResult {
|
export interface TmdbSeasonWithEpisodes extends TmdbTvSeasonResult {
|
||||||
episodes: TmdbTvEpisodeResult[];
|
episodes: TmdbTvEpisodeResult[];
|
||||||
external_ids: TmdbExternalIds;
|
external_ids: TmdbExternalIds;
|
||||||
@@ -310,6 +363,50 @@ class TheMovieDb {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public getPerson = async ({
|
||||||
|
personId,
|
||||||
|
language = 'en-US',
|
||||||
|
}: {
|
||||||
|
personId: number;
|
||||||
|
language?: string;
|
||||||
|
}): Promise<TmdbPersonDetail> => {
|
||||||
|
try {
|
||||||
|
const response = await this.axios.get<TmdbPersonDetail>(
|
||||||
|
`/person/${personId}`,
|
||||||
|
{
|
||||||
|
params: { language },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`[TMDB] Failed to fetch person details: ${e.message}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public getPersonCombinedCredits = async ({
|
||||||
|
personId,
|
||||||
|
language = 'en-US',
|
||||||
|
}: {
|
||||||
|
personId: number;
|
||||||
|
language?: string;
|
||||||
|
}): Promise<TmdbPersonCombinedCredits> => {
|
||||||
|
try {
|
||||||
|
const response = await this.axios.get<TmdbPersonCombinedCredits>(
|
||||||
|
`/person/${personId}/combined_credits`,
|
||||||
|
{
|
||||||
|
params: { language },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(
|
||||||
|
`[TMDB] Failed to fetch person combined credits: ${e.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public getMovie = async ({
|
public getMovie = async ({
|
||||||
movieId,
|
movieId,
|
||||||
language = 'en-US',
|
language = 'en-US',
|
||||||
|
131
server/models/Person.ts
Normal file
131
server/models/Person.ts
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import {
|
||||||
|
TmdbPersonCreditCast,
|
||||||
|
TmdbPersonCreditCrew,
|
||||||
|
TmdbPersonDetail,
|
||||||
|
} from '../api/themoviedb';
|
||||||
|
|
||||||
|
export interface PersonDetail {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
deathday: string;
|
||||||
|
knownForDepartment: string;
|
||||||
|
alsoKnownAs?: string[];
|
||||||
|
gender: number;
|
||||||
|
biography: string;
|
||||||
|
popularity: string;
|
||||||
|
placeOfBirth?: string;
|
||||||
|
profilePath?: string;
|
||||||
|
adult: boolean;
|
||||||
|
imdbId?: string;
|
||||||
|
homepage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PersonCredit {
|
||||||
|
id: number;
|
||||||
|
originalLanguage: string;
|
||||||
|
episodeCount: number;
|
||||||
|
overview: string;
|
||||||
|
originCountry: string[];
|
||||||
|
originalName: string;
|
||||||
|
voteCount: number;
|
||||||
|
name: string;
|
||||||
|
mediaType?: string;
|
||||||
|
popularity: number;
|
||||||
|
creditId: string;
|
||||||
|
backdropPath?: string;
|
||||||
|
firstAirDate: string;
|
||||||
|
voteAverage: number;
|
||||||
|
genreIds?: number[];
|
||||||
|
posterPath?: string;
|
||||||
|
originalTitle: string;
|
||||||
|
video?: boolean;
|
||||||
|
title: string;
|
||||||
|
adult: boolean;
|
||||||
|
releaseDate: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PersonCreditCast extends PersonCredit {
|
||||||
|
character: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PersonCreditCrew extends PersonCredit {
|
||||||
|
department: string;
|
||||||
|
job: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CombinedCredit {
|
||||||
|
id: number;
|
||||||
|
cast: PersonCreditCast[];
|
||||||
|
crew: PersonCreditCrew[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mapPersonDetails = (person: TmdbPersonDetail): PersonDetail => ({
|
||||||
|
id: person.id,
|
||||||
|
name: person.name,
|
||||||
|
deathday: person.deathday,
|
||||||
|
knownForDepartment: person.known_for_department,
|
||||||
|
alsoKnownAs: person.also_known_as,
|
||||||
|
gender: person.gender,
|
||||||
|
biography: person.biography,
|
||||||
|
popularity: person.popularity,
|
||||||
|
placeOfBirth: person.place_of_birth,
|
||||||
|
profilePath: person.profile_path,
|
||||||
|
adult: person.adult,
|
||||||
|
imdbId: person.imdb_id,
|
||||||
|
homepage: person.homepage,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const mapCastCredits = (
|
||||||
|
cast: TmdbPersonCreditCast
|
||||||
|
): PersonCreditCast => ({
|
||||||
|
id: cast.id,
|
||||||
|
originalLanguage: cast.original_language,
|
||||||
|
episodeCount: cast.episode_count,
|
||||||
|
overview: cast.overview,
|
||||||
|
originCountry: cast.origin_country,
|
||||||
|
originalName: cast.original_name,
|
||||||
|
voteCount: cast.vote_count,
|
||||||
|
name: cast.name,
|
||||||
|
mediaType: cast.media_type,
|
||||||
|
popularity: cast.popularity,
|
||||||
|
creditId: cast.credit_id,
|
||||||
|
backdropPath: cast.backdrop_path,
|
||||||
|
firstAirDate: cast.first_air_date,
|
||||||
|
voteAverage: cast.vote_average,
|
||||||
|
genreIds: cast.genre_ids,
|
||||||
|
posterPath: cast.poster_path,
|
||||||
|
originalTitle: cast.original_title,
|
||||||
|
video: cast.video,
|
||||||
|
title: cast.title,
|
||||||
|
adult: cast.adult,
|
||||||
|
releaseDate: cast.release_date,
|
||||||
|
character: cast.character,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const mapCrewCredits = (
|
||||||
|
crew: TmdbPersonCreditCrew
|
||||||
|
): PersonCreditCrew => ({
|
||||||
|
id: crew.id,
|
||||||
|
originalLanguage: crew.original_language,
|
||||||
|
episodeCount: crew.episode_count,
|
||||||
|
overview: crew.overview,
|
||||||
|
originCountry: crew.origin_country,
|
||||||
|
originalName: crew.original_name,
|
||||||
|
voteCount: crew.vote_count,
|
||||||
|
name: crew.name,
|
||||||
|
mediaType: crew.media_type,
|
||||||
|
popularity: crew.popularity,
|
||||||
|
creditId: crew.credit_id,
|
||||||
|
backdropPath: crew.backdrop_path,
|
||||||
|
firstAirDate: crew.first_air_date,
|
||||||
|
voteAverage: crew.vote_average,
|
||||||
|
genreIds: crew.genre_ids,
|
||||||
|
posterPath: crew.poster_path,
|
||||||
|
originalTitle: crew.original_title,
|
||||||
|
video: crew.video,
|
||||||
|
title: crew.title,
|
||||||
|
adult: crew.adult,
|
||||||
|
releaseDate: crew.release_date,
|
||||||
|
department: crew.department,
|
||||||
|
job: crew.job,
|
||||||
|
});
|
@@ -11,6 +11,7 @@ import requestRoutes from './request';
|
|||||||
import movieRoutes from './movie';
|
import movieRoutes from './movie';
|
||||||
import tvRoutes from './tv';
|
import tvRoutes from './tv';
|
||||||
import mediaRoutes from './media';
|
import mediaRoutes from './media';
|
||||||
|
import personRoutes from './person';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ router.use('/request', isAuthenticated(), requestRoutes);
|
|||||||
router.use('/movie', isAuthenticated(), movieRoutes);
|
router.use('/movie', isAuthenticated(), movieRoutes);
|
||||||
router.use('/tv', isAuthenticated(), tvRoutes);
|
router.use('/tv', isAuthenticated(), tvRoutes);
|
||||||
router.use('/media', isAuthenticated(), mediaRoutes);
|
router.use('/media', isAuthenticated(), mediaRoutes);
|
||||||
|
router.use('/person', isAuthenticated(), personRoutes);
|
||||||
router.use('/auth', authRoutes);
|
router.use('/auth', authRoutes);
|
||||||
|
|
||||||
router.get('/', (_req, res) => {
|
router.get('/', (_req, res) => {
|
||||||
|
43
server/routes/person.ts
Normal file
43
server/routes/person.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { Router } from 'express';
|
||||||
|
import next from 'next';
|
||||||
|
import TheMovieDb from '../api/themoviedb';
|
||||||
|
import logger from '../logger';
|
||||||
|
import {
|
||||||
|
mapCastCredits,
|
||||||
|
mapCrewCredits,
|
||||||
|
mapPersonDetails,
|
||||||
|
} from '../models/Person';
|
||||||
|
|
||||||
|
const personRoutes = Router();
|
||||||
|
|
||||||
|
personRoutes.get('/:id', async (req, res, next) => {
|
||||||
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const person = await tmdb.getPerson({
|
||||||
|
personId: Number(req.params.id),
|
||||||
|
language: req.query.language as string,
|
||||||
|
});
|
||||||
|
return res.status(200).json(mapPersonDetails(person));
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(e.message);
|
||||||
|
next({ status: 404, message: 'Person not found' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
personRoutes.get('/:id/combined_credits', async (req, res) => {
|
||||||
|
const tmdb = new TheMovieDb();
|
||||||
|
|
||||||
|
const combinedCredits = await tmdb.getPersonCombinedCredits({
|
||||||
|
personId: Number(req.params.id),
|
||||||
|
language: req.query.language as string,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
cast: combinedCredits.cast.map((result) => mapCastCredits(result)),
|
||||||
|
crew: combinedCredits.crew.map((result) => mapCrewCredits(result)),
|
||||||
|
id: combinedCredits.id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default personRoutes;
|
Reference in New Issue
Block a user