2025-01-03 19:46:10 +00:00
|
|
|
|
const express = require('express');
|
|
|
|
|
|
const router = express.Router();
|
2025-07-08 16:43:41 +03:00
|
|
|
|
|
2025-01-03 19:46:10 +00:00
|
|
|
|
const { formatDate } = require('../utils/date');
|
|
|
|
|
|
|
2025-07-08 16:43:41 +03:00
|
|
|
|
// Helper to check if a title contains valid characters (Cyrillic, Latin, numbers, common punctuation)
|
|
|
|
|
|
const isValidTitle = (title = '') => {
|
|
|
|
|
|
if (!title) return true; // Allow items with no title (e.g., some TV episodes)
|
|
|
|
|
|
// Regular expression to match titles containing Cyrillic, Latin, numbers, and common punctuation.
|
|
|
|
|
|
const validTitleRegex = /^[\p{Script=Cyrillic}\p{Script=Latin}\d\s:!?'.,()-]+$/u;
|
|
|
|
|
|
return validTitleRegex.test(title.trim());
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Function to filter and format results
|
|
|
|
|
|
const filterAndFormat = (results = []) => {
|
|
|
|
|
|
if (!Array.isArray(results)) return [];
|
|
|
|
|
|
return results
|
|
|
|
|
|
.filter(item => {
|
|
|
|
|
|
if (!item) return false;
|
|
|
|
|
|
// Filter out items with a vote average of 0, unless they are upcoming (as they might not have votes yet)
|
|
|
|
|
|
if (item.vote_average === 0 && !item.release_date) return false;
|
|
|
|
|
|
// Filter based on title validity
|
|
|
|
|
|
return isValidTitle(item.title || item.name || '');
|
|
|
|
|
|
})
|
|
|
|
|
|
.map(item => ({
|
|
|
|
|
|
...item,
|
|
|
|
|
|
release_date: item.release_date ? formatDate(item.release_date) : null,
|
|
|
|
|
|
first_air_date: item.first_air_date ? formatDate(item.first_air_date) : null,
|
|
|
|
|
|
}));
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-01-04 12:54:12 +00:00
|
|
|
|
// 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
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-07-08 16:43:41 +03:00
|
|
|
|
const formattedResults = filterAndFormat(data.results);
|
|
|
|
|
|
res.json({ ...data, results: formattedResults });
|
2025-05-28 10:27:38 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error searching movies:', error);
|
|
|
|
|
|
res.status(500).json({ error: error.message });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-01-16 15:44:05 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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
|
2025-01-16 15:44:05 +00:00
|
|
|
|
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)
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
// Объединяем и сортируем результаты по популярности
|
2025-07-08 16:43:41 +03:00
|
|
|
|
const combinedResults = filterAndFormat([
|
|
|
|
|
|
...moviesData.results.map(item => ({ ...item, media_type: 'movie' })),
|
|
|
|
|
|
...tvData.results.map(item => ({ ...item, media_type: 'tv' }))
|
|
|
|
|
|
]).sort((a, b) => b.popularity - a.popularity);
|
2025-01-16 15:44:05 +00:00
|
|
|
|
|
|
|
|
|
|
// Пагинация результатов
|
|
|
|
|
|
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
|
2025-01-03 19:46:10 +00:00
|
|
|
|
});
|
2025-01-16 15:44:05 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error in multi search:', error);
|
|
|
|
|
|
res.status(500).json({ error: error.message });
|
2025-01-03 19:46:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @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);
|
|
|
|
|
|
|
2025-01-04 12:54:12 +00:00
|
|
|
|
console.log('Popular movies response:', {
|
2025-01-04 14:17:20 +00:00
|
|
|
|
requestedPage: pageNum,
|
|
|
|
|
|
returnedPage: movies.page,
|
2025-01-04 12:54:12 +00:00
|
|
|
|
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');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-08 16:43:41 +03:00
|
|
|
|
const formattedResults = filterAndFormat(movies.results);
|
2025-01-04 14:17:20 +00:00
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
...movies,
|
|
|
|
|
|
results: formattedResults
|
2025-01-04 12:54:12 +00:00
|
|
|
|
});
|
2025-01-03 19:46:10 +00:00
|
|
|
|
} catch (error) {
|
2025-01-04 12:54:12 +00:00
|
|
|
|
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
|
|
|
|
|
|
});
|
2025-01-03 19:46:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @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');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-08 16:43:41 +03:00
|
|
|
|
const formattedResults = filterAndFormat(movies.results);
|
2025-01-04 14:17:20 +00:00
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
...movies,
|
|
|
|
|
|
results: formattedResults
|
|
|
|
|
|
});
|
2025-01-03 19:46:10 +00:00
|
|
|
|
} 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
|
|
|
|
|
|
});
|
2025-01-03 19:46:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
2025-01-04 14:17:20 +00:00
|
|
|
|
* /movies/{id}:
|
2025-01-03 19:46:10 +00:00
|
|
|
|
* get:
|
2025-01-04 14:17:20 +00:00
|
|
|
|
* summary: Детали фильма
|
|
|
|
|
|
* description: Получает подробную информацию о фильме по его ID
|
2025-01-03 19:46:10 +00:00
|
|
|
|
* tags: [movies]
|
|
|
|
|
|
* parameters:
|
2025-01-04 14:17:20 +00:00
|
|
|
|
* - in: path
|
|
|
|
|
|
* name: id
|
|
|
|
|
|
* required: true
|
|
|
|
|
|
* description: ID фильма
|
2025-01-03 19:46:10 +00:00
|
|
|
|
* schema:
|
|
|
|
|
|
* type: integer
|
2025-01-04 14:17:20 +00:00
|
|
|
|
* example: 550
|
2025-01-03 19:46:10 +00:00
|
|
|
|
* responses:
|
|
|
|
|
|
* 200:
|
2025-01-04 14:17:20 +00:00
|
|
|
|
* description: Детали фильма
|
2025-01-03 19:46:10 +00:00
|
|
|
|
* 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'
|
2025-01-03 19:46:10 +00:00
|
|
|
|
* 500:
|
|
|
|
|
|
* description: Ошибка сервера
|
|
|
|
|
|
* content:
|
|
|
|
|
|
* application/json:
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* $ref: '#/components/schemas/Error'
|
|
|
|
|
|
*/
|
2025-01-04 14:17:20 +00:00
|
|
|
|
router.get('/:id', async (req, res) => {
|
2025-01-03 19:46:10 +00:00
|
|
|
|
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)
|
|
|
|
|
|
});
|
2025-01-03 19:46:10 +00:00
|
|
|
|
} 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
|
|
|
|
|
|
});
|
2025-01-03 19:46:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
|
|
|
|
|
* /movies/{id}/external-ids:
|
|
|
|
|
|
* get:
|
|
|
|
|
|
* summary: Внешние ID фильма
|
2025-01-04 14:17:20 +00:00
|
|
|
|
* description: Получает внешние идентификаторы фильма (IMDb, и т.д.)
|
2025-01-03 19:46:10 +00:00
|
|
|
|
* tags: [movies]
|
|
|
|
|
|
* parameters:
|
|
|
|
|
|
* - in: path
|
|
|
|
|
|
* name: id
|
|
|
|
|
|
* required: true
|
|
|
|
|
|
* description: ID фильма
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: integer
|
2025-01-04 14:17:20 +00:00
|
|
|
|
* example: 550
|
2025-01-03 19:46:10 +00:00
|
|
|
|
* 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'
|
2025-01-03 19:46:10 +00:00
|
|
|
|
* 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' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-01-03 19:46:10 +00:00
|
|
|
|
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');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-08 16:43:41 +03:00
|
|
|
|
const formattedResults = filterAndFormat(movies.results);
|
2025-01-04 14:17:20 +00:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
});
|
2025-01-03 19:46:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-01-16 15:44:05 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @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) => {
|
2025-01-16 09:22:58 +00:00
|
|
|
|
try {
|
2025-01-16 15:44:05 +00:00
|
|
|
|
const { id } = req.params;
|
|
|
|
|
|
const videos = await req.tmdb.getMovieVideos(id);
|
2025-01-16 09:22:58 +00:00
|
|
|
|
|
2025-01-16 15:44:05 +00:00
|
|
|
|
if (!videos || !videos.results) {
|
|
|
|
|
|
return res.status(404).json({ error: 'Videos not found' });
|
2025-01-16 09:22:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-01-16 15:44:05 +00:00
|
|
|
|
res.json(videos);
|
2025-01-16 09:22:58 +00:00
|
|
|
|
} catch (error) {
|
2025-01-16 15:44:05 +00:00
|
|
|
|
console.error('Get videos error:', error);
|
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
|
error: 'Failed to fetch videos',
|
|
|
|
|
|
details: error.message
|
|
|
|
|
|
});
|
2025-01-16 09:22:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-01-16 15:44:05 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @swagger
|
2025-01-16 16:28:35 +00:00
|
|
|
|
* /movies/genres:
|
2025-01-16 15:44:05 +00:00
|
|
|
|
* get:
|
2025-01-16 16:28:35 +00:00
|
|
|
|
* 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: Возвращает список фильмов определенного жанра
|
2025-01-16 15:44:05 +00:00
|
|
|
|
* tags: [movies]
|
|
|
|
|
|
* parameters:
|
2025-01-16 16:28:35 +00:00
|
|
|
|
* - in: query
|
|
|
|
|
|
* name: with_genres
|
2025-01-16 15:44:05 +00:00
|
|
|
|
* required: true
|
|
|
|
|
|
* description: ID жанра
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* - in: query
|
|
|
|
|
|
* name: page
|
|
|
|
|
|
* description: Номер страницы
|
|
|
|
|
|
* schema:
|
|
|
|
|
|
* type: integer
|
|
|
|
|
|
* minimum: 1
|
|
|
|
|
|
* default: 1
|
|
|
|
|
|
*/
|
2025-01-16 16:28:35 +00:00
|
|
|
|
router.get('/discover', async (req, res) => {
|
2025-01-16 09:22:58 +00:00
|
|
|
|
try {
|
2025-01-16 16:28:35 +00:00
|
|
|
|
const { with_genres, page = 1 } = req.query;
|
2025-01-16 16:21:32 +00:00
|
|
|
|
|
2025-01-16 16:28:35 +00:00
|
|
|
|
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 });
|
2025-01-16 16:21:32 +00:00
|
|
|
|
|
|
|
|
|
|
const response = await req.tmdb.makeRequest('get', '/discover/movie', {
|
|
|
|
|
|
params: {
|
2025-01-16 16:28:35 +00:00
|
|
|
|
with_genres,
|
2025-01-16 16:21:32 +00:00
|
|
|
|
page,
|
|
|
|
|
|
language: 'ru-RU',
|
|
|
|
|
|
'vote_count.gte': 100,
|
|
|
|
|
|
'vote_average.gte': 1,
|
|
|
|
|
|
sort_by: 'popularity.desc',
|
|
|
|
|
|
include_adult: false
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2025-01-16 09:22:58 +00:00
|
|
|
|
|
2025-01-16 16:21:32 +00:00
|
|
|
|
console.log('Movies by genre response:', {
|
|
|
|
|
|
page: response.data.page,
|
|
|
|
|
|
total_results: response.data.total_results,
|
|
|
|
|
|
results_count: response.data.results?.length
|
|
|
|
|
|
});
|
2025-01-16 09:22:58 +00:00
|
|
|
|
|
2025-01-16 16:21:32 +00:00
|
|
|
|
// Форматируем даты в результатах
|
|
|
|
|
|
const formattedResults = response.data.results.map(movie => ({
|
2025-01-16 15:44:05 +00:00
|
|
|
|
...movie,
|
2025-01-16 16:21:32 +00:00
|
|
|
|
release_date: movie.release_date ? formatDate(movie.release_date) : undefined
|
2025-01-16 09:22:58 +00:00
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
2025-01-16 16:21:32 +00:00
|
|
|
|
...response.data,
|
2025-01-16 09:22:58 +00:00
|
|
|
|
results: formattedResults
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
2025-01-16 16:21:32 +00:00
|
|
|
|
console.error('Error fetching movies by genre:', error.response?.data || error.message);
|
2025-01-16 15:44:05 +00:00
|
|
|
|
res.status(500).json({
|
|
|
|
|
|
error: 'Failed to fetch movies by genre',
|
2025-01-16 16:21:32 +00:00
|
|
|
|
details: error.response?.data?.status_message || error.message
|
2025-01-16 09:22:58 +00:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-01-16 15:44:05 +00:00
|
|
|
|
module.exports = router;
|