Files
neomovies-api/src/routes/movies.js

730 lines
22 KiB
JavaScript
Raw Normal View History

const express = require('express');
const router = express.Router();
const { formatDate } = require('../utils/date');
// Middleware для логирования запросов
router.use((req, res, next) => {
console.log('Movies API Request:', {
method: req.method,
path: req.path,
query: req.query,
params: req.params
});
next();
});
2025-05-28 10:27:38 +00:00
/**
* @swagger
* /movies/search:
* get:
* summary: Поиск фильмов
* description: Поиск фильмов по запросу с поддержкой русского языка
* tags: [movies]
* 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
* description: Текущая страница
* total_pages:
* type: integer
* description: Всего страниц
* total_results:
* type: integer
* description: Всего результатов
* results:
* type: array
* items:
* $ref: '#/components/schemas/Movie'
* 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 = 1 } = req.query;
if (!query) {
return res.status(400).json({ error: 'Query parameter is required' });
}
console.log('Search request:', { query, page });
const data = await req.tmdb.searchMovies(query, page);
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({
...data,
results: formattedResults
});
} catch (error) {
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]
*/
2025-05-28 12:21:52 +00:00
router.get('/search/multi', async (req, res) => { // Путь должен быть /search/multi, а не /movies/search/multi, т.к. мы уже находимся в movies.js
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 });
}
});
/**
* @swagger
* /movies/popular:
* get:
* summary: Популярные фильмы
* description: Получает список популярных фильмов с русскими названиями и описаниями
* tags: [movies]
* 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/Movie'
* 500:
* description: Ошибка сервера
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
router.get('/popular', async (req, res) => {
try {
2025-01-04 14:17:20 +00:00
const { page } = req.query;
const pageNum = parseInt(page, 10) || 1;
console.log('Popular movies request:', {
requestedPage: page,
parsedPage: pageNum,
rawQuery: req.query
});
if (pageNum < 1) {
return res.status(400).json({ error: 'Page must be greater than 0' });
}
const movies = await req.tmdb.getPopularMovies(pageNum);
console.log('Popular movies response:', {
2025-01-04 14:17:20 +00:00
requestedPage: pageNum,
returnedPage: movies.page,
totalPages: movies.total_pages,
2025-01-04 14:17:20 +00:00
resultsCount: movies.results?.length
});
if (!movies || !movies.results) {
throw new Error('Invalid response from TMDB');
}
const formattedResults = movies.results.map(movie => ({
...movie,
release_date: formatDate(movie.release_date)
}));
res.json({
...movies,
results: formattedResults
});
} catch (error) {
console.error('Popular movies error:', error);
2025-01-04 14:17:20 +00:00
res.status(500).json({
error: 'Failed to fetch popular movies',
details: error.message
});
}
});
/**
* @swagger
* /movies/top-rated:
* get:
* summary: Лучшие фильмы
* description: Получает список лучших фильмов с русскими названиями и описаниями
* tags: [movies]
* 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/Movie'
* 500:
* description: Ошибка сервера
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
router.get('/top-rated', async (req, res) => {
try {
2025-01-04 14:17:20 +00:00
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 movies = await req.tmdb.getTopRatedMovies(pageNum);
if (!movies || !movies.results) {
throw new Error('Invalid response from TMDB');
}
const formattedResults = movies.results.map(movie => ({
...movie,
release_date: formatDate(movie.release_date)
}));
res.json({
...movies,
results: formattedResults
});
} catch (error) {
2025-01-04 14:17:20 +00:00
console.error('Top rated movies error:', error);
res.status(500).json({
error: 'Failed to fetch top rated movies',
details: error.message
});
}
});
/**
* @swagger
2025-01-04 14:17:20 +00:00
* /movies/{id}:
* get:
2025-01-04 14:17:20 +00:00
* summary: Детали фильма
* description: Получает подробную информацию о фильме по его ID
* tags: [movies]
* parameters:
2025-01-04 14:17:20 +00:00
* - in: path
* name: id
* required: true
* description: ID фильма
* schema:
* type: integer
2025-01-04 14:17:20 +00:00
* example: 550
* responses:
* 200:
2025-01-04 14:17:20 +00:00
* description: Детали фильма
* content:
* application/json:
* schema:
2025-01-04 14:17:20 +00:00
* $ref: '#/components/schemas/Movie'
* 404:
* description: Фильм не найден
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* 500:
* description: Ошибка сервера
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
2025-01-04 14:17:20 +00:00
router.get('/:id', async (req, res) => {
try {
2025-01-04 14:17:20 +00:00
const { id } = req.params;
const movie = await req.tmdb.getMovie(id);
if (!movie) {
return res.status(404).json({ error: 'Movie not found' });
}
res.json({
...movie,
release_date: formatDate(movie.release_date)
});
} catch (error) {
2025-01-04 14:17:20 +00:00
console.error('Get movie error:', error);
res.status(500).json({
error: 'Failed to fetch movie details',
details: error.message
});
}
});
/**
* @swagger
* /movies/{id}/external-ids:
* get:
* summary: Внешние ID фильма
2025-01-04 14:17:20 +00:00
* description: Получает внешние идентификаторы фильма (IMDb, и т.д.)
* tags: [movies]
* parameters:
* - in: path
* name: id
* required: true
* description: ID фильма
* schema:
* type: integer
2025-01-04 14:17:20 +00:00
* example: 550
* responses:
* 200:
* description: Внешние ID фильма
* content:
* application/json:
* schema:
* type: object
* properties:
* imdb_id:
* type: string
* facebook_id:
* type: string
* instagram_id:
* type: string
* twitter_id:
* type: string
2025-01-04 14:17:20 +00:00
* 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 {
2025-01-04 14:17:20 +00:00
const { id } = req.params;
const externalIds = await req.tmdb.getMovieExternalIDs(id);
if (!externalIds) {
return res.status(404).json({ error: 'External IDs not found' });
}
res.json(externalIds);
} catch (error) {
2025-01-04 14:17:20 +00:00
console.error('Get external IDs error:', error);
res.status(500).json({
error: 'Failed to fetch external IDs',
details: error.message
});
}
});
/**
* @swagger
* /movies/upcoming:
* get:
* summary: Предстоящие фильмы
* description: Получает список предстоящих фильмов с русскими названиями и описаниями
* tags: [movies]
* 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/Movie'
* 500:
* description: Ошибка сервера
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
router.get('/upcoming', 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 movies = await req.tmdb.getUpcomingMovies(pageNum);
if (!movies || !movies.results) {
throw new Error('Invalid response from TMDB');
}
const formattedResults = movies.results.map(movie => ({
...movie,
release_date: formatDate(movie.release_date)
}));
res.json({
...movies,
results: formattedResults
});
} catch (error) {
console.error('Upcoming movies error:', error);
res.status(500).json({
error: 'Failed to fetch upcoming movies',
details: error.message
});
}
});
/**
* @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 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('Get videos error:', error);
res.status(500).json({
error: 'Failed to fetch videos',
details: error.message
});
}
});
/**
* @swagger
* /movies/genres:
* get:
* summary: Получение списка жанров
* description: Возвращает список всех доступных жанров фильмов
* tags: [movies]
* responses:
* 200:
* description: Список жанров
*/
router.get('/genres', async (req, res) => {
try {
console.log('Fetching genres...');
const response = await req.tmdb.getGenres();
if (!response?.data?.genres) {
console.error('Invalid genres response:', response);
return res.status(500).json({
error: 'Invalid response from TMDB',
details: 'Genres data is missing'
});
}
console.log('Genres response:', {
count: response.data.genres.length,
genres: response.data.genres
});
res.json(response.data);
} catch (error) {
console.error('Error fetching genres:', {
message: error.message,
response: error.response?.data,
stack: error.stack
});
res.status(500).json({
error: 'Failed to fetch genres',
details: error.response?.data?.status_message || error.message
});
}
});
/**
* @swagger
* /movies/discover:
* get:
* summary: Получение фильмов по жанру
* description: Возвращает список фильмов определенного жанра
* tags: [movies]
* parameters:
* - in: query
* name: with_genres
* required: true
* description: ID жанра
* schema:
* type: integer
* - in: query
* name: page
* description: Номер страницы
* schema:
* type: integer
* minimum: 1
* default: 1
*/
router.get('/discover', async (req, res) => {
try {
const { with_genres, page = 1 } = req.query;
if (!with_genres) {
return res.status(400).json({
error: 'Missing required parameter',
details: 'Genre ID is required'
});
}
console.log('Fetching movies by genre:', { with_genres, page });
const response = await req.tmdb.makeRequest('get', '/discover/movie', {
params: {
with_genres,
page,
language: 'ru-RU',
'vote_count.gte': 100,
'vote_average.gte': 1,
sort_by: 'popularity.desc',
include_adult: false
}
});
console.log('Movies by genre response:', {
page: response.data.page,
total_results: response.data.total_results,
results_count: response.data.results?.length
});
// Форматируем даты в результатах
const formattedResults = response.data.results.map(movie => ({
...movie,
release_date: movie.release_date ? formatDate(movie.release_date) : undefined
}));
res.json({
...response.data,
results: formattedResults
});
} catch (error) {
console.error('Error fetching movies by genre:', error.response?.data || error.message);
res.status(500).json({
error: 'Failed to fetch movies by genre',
details: error.response?.data?.status_message || error.message
});
}
});
module.exports = router;