Update 6 files

- /src/index.js
- /src/config/tmdb.js
- /src/routes/movies.js
- /src/routes/images.js
- /src/routes/tv.js
- /package-lock.json
This commit is contained in:
2025-01-16 15:44:05 +00:00
parent 5361894af1
commit c25d4e5d87
6 changed files with 3770 additions and 267 deletions

75
src/routes/images.js Normal file
View File

@@ -0,0 +1,75 @@
const express = require('express');
const router = express.Router();
const axios = require('axios');
const path = require('path');
// Базовый URL для изображений TMDB
const TMDB_IMAGE_BASE_URL = 'https://image.tmdb.org/t/p';
// Путь к placeholder изображению
const PLACEHOLDER_PATH = path.join(__dirname, '..', 'public', 'images', 'placeholder.jpg');
/**
* @swagger
* /images/{size}/{path}:
* get:
* summary: Прокси для изображений TMDB
* description: Получает изображения с TMDB и отдает их клиенту
* tags: [images]
* parameters:
* - in: path
* name: size
* required: true
* description: Размер изображения (w500, original и т.д.)
* schema:
* type: string
* - in: path
* name: path
* required: true
* description: Путь к изображению
* schema:
* type: string
* responses:
* 200:
* description: Изображение
* content:
* image/*:
* schema:
* type: string
* format: binary
*/
router.get('/:size/:path(*)', async (req, res) => {
try {
const { size, path: imagePath } = req.params;
// Если запрашивается placeholder, возвращаем локальный файл
if (imagePath === 'placeholder.jpg') {
return res.sendFile(PLACEHOLDER_PATH);
}
// Проверяем размер изображения
const validSizes = ['w92', 'w154', 'w185', 'w342', 'w500', 'w780', 'original'];
const imageSize = validSizes.includes(size) ? size : 'original';
// Формируем URL изображения
const imageUrl = `${TMDB_IMAGE_BASE_URL}/${imageSize}/${imagePath}`;
// Получаем изображение
const response = await axios.get(imageUrl, {
responseType: 'stream',
validateStatus: status => status === 200
});
// Устанавливаем заголовки
res.set('Content-Type', response.headers['content-type']);
res.set('Cache-Control', 'public, max-age=31536000'); // кэшируем на 1 год
// Передаем изображение клиенту
response.data.pipe(res);
} catch (error) {
console.error('Image proxy error:', error.message);
res.sendFile(PLACEHOLDER_PATH);
}
});
module.exports = router;

View File

@@ -72,40 +72,129 @@ router.use((req, res, next) => {
*/
router.get('/search', async (req, res) => {
try {
const { query, page } = req.query;
const pageNum = parseInt(page, 10) || 1;
const { query, page = 1 } = req.query;
if (!query) {
return res.status(400).json({ error: 'Query parameter is required' });
}
if (pageNum < 1) {
return res.status(400).json({ error: 'Page must be greater than 0' });
}
console.log('Search request:', { query, page });
const response = await req.tmdb.searchMovies(query, pageNum);
if (!response || !response.data) {
throw new Error('Failed to fetch data from TMDB');
}
const data = await req.tmdb.searchMovies(query, page);
const { results, ...rest } = response.data;
const formattedResults = results.map(movie => ({
console.log('Search response:', {
page: data.page,
total_results: data.total_results,
total_pages: data.total_pages,
results_count: data.results?.length
});
// Форматируем даты в результатах
const formattedResults = data.results.map(movie => ({
...movie,
release_date: formatDate(movie.release_date)
}));
res.json({
...rest,
...data,
results: formattedResults
});
} catch (error) {
console.error('Search movies error:', error);
res.status(500).json({
error: 'Failed to search movies',
details: error.message
console.error('Error searching movies:', error);
res.status(500).json({ error: error.message });
}
});
/**
* @swagger
* /search/multi:
* get:
* summary: Мультипоиск
* description: Поиск фильмов и сериалов по запросу
* tags: [search]
* parameters:
* - in: query
* name: query
* required: true
* description: Поисковый запрос
* schema:
* type: string
* - in: query
* name: page
* description: Номер страницы
* schema:
* type: integer
* minimum: 1
* default: 1
* responses:
* 200:
* description: Успешный поиск
* content:
* application/json:
* schema:
* type: object
* properties:
* page:
* type: integer
* results:
* type: array
* items:
* type: object
* properties:
* id:
* type: integer
* title:
* type: string
* name:
* type: string
* media_type:
* type: string
* enum: [movie, tv]
*/
router.get('/search/multi', async (req, res) => {
try {
const { query, page = 1 } = req.query;
if (!query) {
return res.status(400).json({ error: 'Query parameter is required' });
}
console.log('Multi search request:', { query, page });
// Параллельный поиск фильмов и сериалов
const [moviesData, tvData] = await Promise.all([
req.tmdb.searchMovies(query, page),
req.tmdb.searchTVShows(query, page)
]);
// Объединяем и сортируем результаты по популярности
const combinedResults = [
...moviesData.results.map(movie => ({
...movie,
media_type: 'movie',
release_date: formatDate(movie.release_date)
})),
...tvData.results.map(show => ({
...show,
media_type: 'tv',
first_air_date: formatDate(show.first_air_date)
}))
].sort((a, b) => b.popularity - a.popularity);
// Пагинация результатов
const itemsPerPage = 20;
const startIndex = (parseInt(page) - 1) * itemsPerPage;
const paginatedResults = combinedResults.slice(startIndex, startIndex + itemsPerPage);
res.json({
page: parseInt(page),
results: paginatedResults,
total_pages: Math.ceil(combinedResults.length / itemsPerPage),
total_results: combinedResults.length
});
} catch (error) {
console.error('Error in multi search:', error);
res.status(500).json({ error: error.message });
}
});
@@ -452,57 +541,129 @@ router.get('/upcoming', async (req, res) => {
}
});
// TV Shows Routes
router.get('/tv/search', async (req, res) => {
try {
const { query, page } = req.query;
const pageNum = parseInt(page, 10) || 1;
if (!query) {
return res.status(400).json({ error: 'Query parameter is required' });
}
if (pageNum < 1) {
return res.status(400).json({ error: 'Page must be greater than 0' });
}
const response = await req.tmdb.searchTVShows(query, pageNum);
if (!response || !response.data) {
throw new Error('Failed to fetch data from TMDB');
}
const { results, ...rest } = response.data;
const formattedResults = results.map(show => ({
...show,
first_air_date: formatDate(show.first_air_date)
}));
res.json({
...rest,
results: formattedResults
});
} catch (error) {
console.error('Error searching TV shows:', error);
res.status(500).json({ error: error.message });
}
});
router.get('/tv/:id', async (req, res) => {
/**
* @swagger
* /movies/{id}/videos:
* get:
* summary: Видео фильма
* description: Получает список видео для фильма (трейлеры, тизеры и т.д.)
* tags: [movies]
* parameters:
* - in: path
* name: id
* required: true
* description: ID фильма
* schema:
* type: integer
* example: 550
* responses:
* 200:
* description: Список видео
* content:
* application/json:
* schema:
* type: object
* properties:
* results:
* type: array
* items:
* type: object
* properties:
* id:
* type: string
* key:
* type: string
* name:
* type: string
* site:
* type: string
* type:
* type: string
* 404:
* description: Видео не найдены
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* 500:
* description: Ошибка сервера
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
router.get('/:id/videos', async (req, res) => {
try {
const { id } = req.params;
const show = await req.tmdb.getTVShow(id);
show.first_air_date = formatDate(show.first_air_date);
res.json(show);
const videos = await req.tmdb.getMovieVideos(id);
if (!videos || !videos.results) {
return res.status(404).json({ error: 'Videos not found' });
}
res.json(videos);
} catch (error) {
console.error('Error fetching TV show:', error);
res.status(500).json({ error: error.message });
console.error('Get videos error:', error);
res.status(500).json({
error: 'Failed to fetch videos',
details: error.message
});
}
});
router.get('/tv/popular', async (req, res) => {
/**
* @swagger
* /movies/genre/{id}:
* get:
* summary: Фильмы по жанру
* description: Получает список фильмов определенного жанра
* tags: [movies]
* parameters:
* - in: path
* name: id
* required: true
* description: ID жанра
* schema:
* type: integer
* example: 28
* - in: query
* name: page
* description: Номер страницы
* schema:
* type: integer
* minimum: 1
* default: 1
* example: 1
* responses:
* 200:
* description: Список фильмов
* content:
* application/json:
* schema:
* type: object
* properties:
* page:
* type: integer
* results:
* type: array
* items:
* $ref: '#/components/schemas/Movie'
* 404:
* description: Жанр не найден
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* 500:
* description: Ошибка сервера
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
router.get('/genre/:id', async (req, res) => {
try {
const { id } = req.params;
const { page } = req.query;
const pageNum = parseInt(page, 10) || 1;
@@ -510,59 +671,28 @@ router.get('/tv/popular', async (req, res) => {
return res.status(400).json({ error: 'Page must be greater than 0' });
}
const response = await req.tmdb.getPopularTVShows(pageNum);
const movies = await req.tmdb.getMoviesByGenre(id, pageNum);
if (!response || !response.data) {
throw new Error('Failed to fetch data from TMDB');
if (!movies || !movies.results) {
return res.status(404).json({ error: 'Movies not found for this genre' });
}
const { results, ...rest } = response.data;
const formattedResults = results.map(show => ({
...show,
first_air_date: formatDate(show.first_air_date)
const formattedResults = movies.results.map(movie => ({
...movie,
release_date: formatDate(movie.release_date)
}));
res.json({
...rest,
...movies,
results: formattedResults
});
} catch (error) {
console.error('Error fetching popular TV shows:', error);
res.status(500).json({ error: error.message });
}
});
router.get('/tv/top-rated', async (req, res) => {
try {
const { page } = req.query;
const pageNum = parseInt(page, 10) || 1;
if (pageNum < 1) {
return res.status(400).json({ error: 'Page must be greater than 0' });
}
const response = await req.tmdb.getTopRatedTVShows(pageNum);
if (!response || !response.data) {
throw new Error('Failed to fetch data from TMDB');
}
const { results, ...rest } = response.data;
const formattedResults = results.map(show => ({
...show,
first_air_date: formatDate(show.first_air_date)
}));
res.json({
...rest,
results: formattedResults
console.error('Get movies by genre error:', error);
res.status(500).json({
error: 'Failed to fetch movies by genre',
details: error.message
});
} catch (error) {
console.error('Error fetching top rated TV shows:', error);
res.status(500).json({ error: error.message });
}
});
module.exports = router;
module.exports = router;

310
src/routes/tv.js Normal file
View File

@@ -0,0 +1,310 @@
const express = require('express');
const router = express.Router();
const { formatDate } = require('../utils/date');
// Middleware для логирования запросов
router.use((req, res, next) => {
console.log('TV Shows API Request:', {
method: req.method,
path: req.path,
query: req.query,
params: req.params
});
next();
});
/**
* @swagger
* /tv/popular:
* get:
* summary: Популярные сериалы
* description: Получает список популярных сериалов с русскими названиями и описаниями
* tags: [tv]
* parameters:
* - in: query
* name: page
* description: Номер страницы
* schema:
* type: integer
* minimum: 1
* default: 1
* example: 1
* responses:
* 200:
* description: Список популярных сериалов
* content:
* application/json:
* schema:
* type: object
* properties:
* page:
* type: integer
* results:
* type: array
* items:
* $ref: '#/components/schemas/TVShow'
* 500:
* description: Ошибка сервера
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
router.get('/popular', async (req, res) => {
try {
const { page } = req.query;
const pageNum = parseInt(page, 10) || 1;
if (pageNum < 1) {
return res.status(400).json({ error: 'Page must be greater than 0' });
}
const response = await req.tmdb.getPopularTVShows(pageNum);
if (!response || !response.results) {
throw new Error('Invalid response from TMDB');
}
const formattedResults = response.results.map(show => ({
...show,
first_air_date: formatDate(show.first_air_date)
}));
res.json({
...response,
results: formattedResults
});
} catch (error) {
console.error('Popular TV shows error:', error);
res.status(500).json({
error: 'Failed to fetch popular TV shows',
details: error.message
});
}
});
/**
* @swagger
* /tv/search:
* get:
* summary: Поиск сериалов
* description: Поиск сериалов по запросу с поддержкой русского языка
* tags: [tv]
* parameters:
* - in: query
* name: query
* required: true
* description: Поисковый запрос
* schema:
* type: string
* example: Игра престолов
* - in: query
* name: page
* description: Номер страницы (по умолчанию 1)
* schema:
* type: integer
* minimum: 1
* default: 1
* example: 1
* responses:
* 200:
* description: Успешный поиск
* content:
* application/json:
* schema:
* type: object
* properties:
* page:
* type: integer
* results:
* type: array
* items:
* $ref: '#/components/schemas/TVShow'
* 400:
* description: Неверный запрос
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* 500:
* description: Ошибка сервера
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
router.get('/search', async (req, res) => {
try {
const { query, page } = req.query;
const pageNum = parseInt(page, 10) || 1;
if (!query) {
return res.status(400).json({ error: 'Query parameter is required' });
}
if (pageNum < 1) {
return res.status(400).json({ error: 'Page must be greater than 0' });
}
const response = await req.tmdb.searchTVShows(query, pageNum);
if (!response || !response.results) {
throw new Error('Failed to fetch data from TMDB');
}
const formattedResults = response.results.map(show => ({
...show,
first_air_date: formatDate(show.first_air_date)
}));
res.json({
...response,
results: formattedResults
});
} catch (error) {
console.error('Search TV shows error:', error);
res.status(500).json({
error: 'Failed to search TV shows',
details: error.message
});
}
});
/**
* @swagger
* /tv/{id}:
* get:
* summary: Детали сериала
* description: Получает подробную информацию о сериале по его ID
* tags: [tv]
* parameters:
* - in: path
* name: id
* required: true
* description: ID сериала
* schema:
* type: integer
* example: 1399
* responses:
* 200:
* description: Детали сериала
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/TVShow'
* 404:
* description: Сериал не найден
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* 500:
* description: Ошибка сервера
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
router.get('/:id', async (req, res) => {
try {
const { id } = req.params;
const show = await req.tmdb.getTVShow(id);
if (!show) {
return res.status(404).json({ error: 'TV show not found' });
}
// Ensure all required fields are present and formatted correctly
const formattedShow = {
id: show.id,
name: show.name,
overview: show.overview,
poster_path: show.poster_path,
backdrop_path: show.backdrop_path,
first_air_date: formatDate(show.first_air_date),
vote_average: show.vote_average,
vote_count: show.vote_count,
number_of_seasons: show.number_of_seasons,
number_of_episodes: show.number_of_episodes,
genres: show.genres || [],
genre_ids: show.genre_ids || show.genres?.map(g => g.id) || [],
credits: show.credits || { cast: [], crew: [] },
videos: show.videos || { results: [] }
};
res.json(formattedShow);
} catch (error) {
console.error('Get TV show error:', error);
res.status(500).json({
error: 'Failed to fetch TV show details',
details: error.message
});
}
});
/**
* @swagger
* /tv/{id}/external-ids:
* get:
* summary: Внешние ID сериала
* description: Получает внешние идентификаторы сериала (IMDb, и т.д.)
* tags: [tv]
* parameters:
* - in: path
* name: id
* required: true
* description: ID сериала
* schema:
* type: integer
* example: 1399
* responses:
* 200:
* description: Внешние ID сериала
* content:
* application/json:
* schema:
* type: object
* properties:
* imdb_id:
* type: string
* tvdb_id:
* type: integer
* facebook_id:
* type: string
* instagram_id:
* type: string
* twitter_id:
* type: string
* 404:
* description: Сериал не найден
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* 500:
* description: Ошибка сервера
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
router.get('/:id/external-ids', async (req, res) => {
try {
const { id } = req.params;
const externalIds = await req.tmdb.getTVShowExternalIDs(id);
if (!externalIds) {
return res.status(404).json({ error: 'External IDs not found' });
}
res.json(externalIds);
} catch (error) {
console.error('Get external IDs error:', error);
res.status(500).json({
error: 'Failed to fetch external IDs',
details: error.message
});
}
});
module.exports = router;