From a3beeede7e72e99c7595673a27e38611ca4bb0cd Mon Sep 17 00:00:00 2001 From: sct Date: Wed, 16 Sep 2020 06:11:29 +0000 Subject: [PATCH] feat(api): tv details endpoint --- server/api/themoviedb.ts | 22 ++--- server/entity/MediaRequest.ts | 6 +- server/models/Movie.ts | 13 +-- server/models/Tv.ts | 144 ++++++++++++++++++++++++++++++++ server/models/common.ts | 11 +++ server/overseerr-api.yml | 150 +++++++++++++++++++++++++++++++++- server/routes/index.ts | 2 + server/routes/tv.ts | 18 ++++ 8 files changed, 342 insertions(+), 24 deletions(-) create mode 100644 server/models/Tv.ts create mode 100644 server/models/common.ts create mode 100644 server/routes/tv.ts diff --git a/server/api/themoviedb.ts b/server/api/themoviedb.ts index 68fc39429..f59653c32 100644 --- a/server/api/themoviedb.ts +++ b/server/api/themoviedb.ts @@ -140,7 +140,7 @@ export interface TmdbMovieDetails { vote_count: number; } -interface TmdbTvEpisodeDetails { +export interface TmdbTvEpisodeDetails { id: number; air_date: string; episode_number: number; @@ -154,6 +154,16 @@ interface TmdbTvEpisodeDetails { vote_cuont: number; } +export interface TmdbTvSeasonDetails { + id: number; + air_date: string; + episode_count: number; + name: string; + overview: string; + poster_path: string; + season_number: number; +} + export interface TmdbTvDetails { id: number; backdrop_path?: string; @@ -197,15 +207,7 @@ export interface TmdbTvDetails { name: string; origin_country: string; }[]; - seasons: { - id: number; - air_date: string; - episode_count: number; - name: string; - overview: string; - poster_path: string; - season_number: number; - }[]; + seasons: TmdbTvSeasonDetails[]; status: string; type: string; vote_average: number; diff --git a/server/entity/MediaRequest.ts b/server/entity/MediaRequest.ts index af53a299b..fba077b1f 100644 --- a/server/entity/MediaRequest.ts +++ b/server/entity/MediaRequest.ts @@ -38,7 +38,9 @@ export class MediaRequest { finalIds = mediaIds; } - const requests = await requestRepository.find({ mediaId: In(finalIds) }); + const requests = await requestRepository.find({ + mediaId: In(finalIds), + }); return requests; } catch (e) { @@ -54,7 +56,7 @@ export class MediaRequest { try { const request = await requestRepository.findOneOrFail({ - where: { tmdbId: id }, + where: { mediaId: id }, }); return request; diff --git a/server/models/Movie.ts b/server/models/Movie.ts index 97f5a3ab5..8bb35c7cc 100644 --- a/server/models/Movie.ts +++ b/server/models/Movie.ts @@ -1,12 +1,6 @@ import { TmdbMovieDetails } from '../api/themoviedb'; import { MediaRequest } from '../entity/MediaRequest'; - -interface ProductionCompany { - id: number; - logoPath?: string; - originCountry: string; - name: string; -} +import { ProductionCompany, Genre } from './common'; export interface MovieDetails { id: number; @@ -14,10 +8,7 @@ export interface MovieDetails { adult: boolean; backdropPath?: string; budget: number; - genres: { - id: number; - name: string; - }[]; + genres: Genre[]; homepage?: string; originalLanguage: string; originalTitle: string; diff --git a/server/models/Tv.ts b/server/models/Tv.ts new file mode 100644 index 000000000..e06fe06e8 --- /dev/null +++ b/server/models/Tv.ts @@ -0,0 +1,144 @@ +import { Genre, ProductionCompany } from './common'; +import { MediaRequest } from '../entity/MediaRequest'; +import { + TmdbTvEpisodeDetails, + TmdbTvSeasonDetails, + TmdbTvDetails, +} from '../api/themoviedb'; + +interface Episode { + id: number; + name: string; + airDate: string; + episodeNumber: number; + overview: string; + productionCode: string; + seasonNumber: number; + showId: number; + stillPath?: string; + voteAverage: number; + voteCount: number; +} + +interface Season { + airDate: string; + id: number; + episodeCount: number; + name: string; + overview: string; + posterPath?: string; + seasonNumber: number; +} + +export interface TvDetails { + id: number; + backdropPath?: string; + posterPath?: string; + createdBy: { + id: number; + name: string; + gender: number; + profilePath?: string; + }[]; + episodeRunTime: number[]; + firstAirDate: string; + genres: Genre[]; + homepage: string; + inProduction: boolean; + languages: string[]; + lastAirDate: string; + lastEpisodeToAir?: Episode; + name: string; + nextEpisodeToAir?: Episode; + networks: ProductionCompany[]; + numberOfEpisodes: number; + numberOfSeasons: number; + originCountry: string[]; + originalLanguage: string; + originalName: string; + overview: string; + popularity: number; + productionCompanies: ProductionCompany[]; + seasons: Season[]; + status: string; + type: string; + voteAverage: number; + voteCount: number; + request?: MediaRequest; +} + +const mapEpisodeDetails = (episode: TmdbTvEpisodeDetails): Episode => ({ + id: episode.id, + airDate: episode.air_date, + episodeNumber: episode.episode_number, + name: episode.name, + overview: episode.overview, + productionCode: episode.production_code, + seasonNumber: episode.season_number, + showId: episode.show_id, + voteAverage: episode.vote_average, + voteCount: episode.vote_cuont, + stillPath: episode.still_path, +}); + +const mapSeasonDetails = (season: TmdbTvSeasonDetails): Season => ({ + airDate: season.air_date, + episodeCount: season.episode_count, + id: season.id, + name: season.name, + overview: season.overview, + seasonNumber: season.season_number, + posterPath: season.poster_path, +}); + +export const mapTvDetails = ( + show: TmdbTvDetails, + request?: MediaRequest +): TvDetails => ({ + createdBy: show.created_by, + episodeRunTime: show.episode_run_time, + firstAirDate: show.first_air_date, + genres: show.genres.map((genre) => ({ + id: genre.id, + name: genre.name, + })), + homepage: show.homepage, + id: show.id, + inProduction: show.in_production, + languages: show.languages, + lastAirDate: show.last_air_date, + name: show.name, + networks: show.networks.map((network) => ({ + id: network.id, + name: network.name, + originCountry: network.origin_country, + logoPath: network.logo_path, + })), + numberOfEpisodes: show.number_of_episodes, + numberOfSeasons: show.number_of_seasons, + originCountry: show.origin_country, + originalLanguage: show.original_language, + originalName: show.original_name, + overview: show.overview, + popularity: show.popularity, + productionCompanies: show.production_companies.map((company) => ({ + id: company.id, + name: company.name, + originCountry: company.origin_country, + logoPath: company.logo_path, + })), + seasons: show.seasons.map(mapSeasonDetails), + status: show.status, + type: show.type, + voteAverage: show.vote_average, + voteCount: show.vote_count, + backdropPath: show.backdrop_path, + lastEpisodeToAir: show.last_episode_to_air + ? mapEpisodeDetails(show.last_episode_to_air) + : undefined, + nextEpisodeToAir: show.next_episode_to_air + ? mapEpisodeDetails(show.next_episode_to_air) + : undefined, + posterPath: show.poster_path, + request, +}); diff --git a/server/models/common.ts b/server/models/common.ts new file mode 100644 index 000000000..344e0e82d --- /dev/null +++ b/server/models/common.ts @@ -0,0 +1,11 @@ +export interface ProductionCompany { + id: number; + logoPath?: string; + originCountry: string; + name: string; +} + +export interface Genre { + id: number; + name: string; +} diff --git a/server/overseerr-api.yml b/server/overseerr-api.yml index f628d3a01..6afa48614 100644 --- a/server/overseerr-api.yml +++ b/server/overseerr-api.yml @@ -421,7 +421,135 @@ components: type: number request: $ref: '#/components/schemas/MediaRequest' - + Episode: + type: object + properties: + id: + type: number + name: + type: string + airDate: + type: string + episodeNumber: + type: number + overview: + type: string + productionCode: + type: string + seasonNumber: + type: string + showId: + type: number + stillPath: + type: string + voteAverage: + type: number + voteCount: + type: number + Season: + type: object + properties: + id: + type: number + airDate: + type: string + episodeCount: + type: number + name: + type: string + overview: + type: string + posterPath: + type: string + seasonNumber: + type: number + TvDetails: + type: object + properties: + id: + type: number + example: 123 + backdropPath: + type: string + posterPath: + type: string + createdBy: + type: array + items: + type: object + properties: + id: + type: number + name: + type: string + gender: + type: number + profilePath: + type: string + episodeRunTime: + type: array + items: + type: number + firstAirDate: + type: string + genres: + type: array + items: + $ref: '#/components/schemas/Genre' + homepage: + type: string + inProduction: + type: boolean + languages: + type: array + items: + type: string + lastAirDate: + type: string + lastEpisodeToAir: + $ref: '#/components/schemas/Episode' + name: + type: string + nextEpisodeToAir: + $ref: '#/components/schemas/Episode' + networks: + type: array + items: + $ref: '#/components/schemas/ProductionCompany' + numberOfEpisodes: + type: number + numberOfSeason: + type: number + originCountry: + type: array + items: + type: string + originalLanguage: + type: string + originalName: + type: string + overview: + type: string + popularity: + type: number + productionCompanies: + type: array + items: + $ref: '#/components/schemas/ProductionCompany' + seasons: + type: array + items: + $ref: '#/components/schemas/Season' + status: + type: string + type: + type: string + voteAverage: + type: number + voteCount: + type: number + request: + $ref: '#/components/schemas/MediaRequest' MediaRequest: type: object properties: @@ -1108,6 +1236,26 @@ paths: application/json: schema: $ref: '#/components/schemas/MovieDetails' + /tv/{tvId}: + get: + summary: Request tv details + description: Returns back full tv details in JSON format + tags: + - tv + parameters: + - in: path + name: tvId + required: true + schema: + type: number + example: 76479 + responses: + '200': + description: TV details + content: + application/json: + schema: + $ref: '#/components/schemas/TvDetails' security: - cookieAuth: [] diff --git a/server/routes/index.ts b/server/routes/index.ts index e46aa1719..88e800917 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -9,6 +9,7 @@ import searchRoutes from './search'; import discoverRoutes from './discover'; import requestRoutes from './request'; import movieRoutes from './movie'; +import tvRoutes from './tv'; const router = Router(); @@ -23,6 +24,7 @@ router.use('/search', isAuthenticated(), searchRoutes); router.use('/discover', isAuthenticated(), discoverRoutes); router.use('/request', isAuthenticated(), requestRoutes); router.use('/movie', isAuthenticated(), movieRoutes); +router.use('/tv', isAuthenticated(), tvRoutes); router.use('/auth', authRoutes); router.get('/settings/public', (_req, res) => { diff --git a/server/routes/tv.ts b/server/routes/tv.ts new file mode 100644 index 000000000..7eb3b19e5 --- /dev/null +++ b/server/routes/tv.ts @@ -0,0 +1,18 @@ +import { Router } from 'express'; +import TheMovieDb from '../api/themoviedb'; +import { MediaRequest } from '../entity/MediaRequest'; +import { mapTvDetails } from '../models/Tv'; + +const tvRoutes = Router(); + +tvRoutes.get('/:id', async (req, res) => { + const tmdb = new TheMovieDb(); + + const tv = await tmdb.getTvShow({ tvId: Number(req.params.id) }); + + const request = await MediaRequest.getRequest(tv.id); + + return res.status(200).json(mapTvDetails(tv, request)); +}); + +export default tvRoutes;