Update 4 files

- /src/index.js
- /src/routes/movies.js
- /src/config/tmdb.js
- /vercel.json
This commit is contained in:
2025-01-03 20:08:13 +00:00
parent 0c1cfb1ac5
commit 8b11f89347
4 changed files with 106 additions and 69 deletions

View File

@@ -7,13 +7,27 @@ class TMDBClient {
headers: { headers: {
'Authorization': `Bearer ${accessToken}`, 'Authorization': `Bearer ${accessToken}`,
'Accept': 'application/json' 'Accept': 'application/json'
} },
timeout: 10000 // 10 секунд таймаут
}); });
// Добавляем интерцептор для обработки ошибок
this.client.interceptors.response.use(
response => response,
error => {
console.error('TMDB API Error:', {
status: error.response?.status,
data: error.response?.data,
message: error.message
});
throw error;
}
);
} }
async makeRequest(method, endpoint, params = {}) { async makeRequest(method, endpoint, params = {}) {
try { try {
const response = await this.client.request({ const response = await this.client({
method, method,
url: endpoint, url: endpoint,
params: { params: {
@@ -22,10 +36,12 @@ class TMDBClient {
region: 'RU' region: 'RU'
} }
}); });
return response.data; return response;
} catch (error) { } catch (error) {
console.error(`TMDB API Error: ${error.message}`); if (error.response) {
throw error; throw new Error(`TMDB API Error: ${error.response.data.status_message || error.message}`);
}
throw new Error(`Network Error: ${error.message}`);
} }
} }
@@ -35,71 +51,55 @@ class TMDBClient {
} }
async searchMovies(query, page = 1) { async searchMovies(query, page = 1) {
const data = await this.makeRequest('GET', '/search/movie', { const response = await this.makeRequest('GET', '/search/movie', {
query, query,
page, page,
include_adult: false include_adult: false
}); });
const data = response.data;
// Фильтруем результаты // Фильтруем результаты
data.results = data.results.filter(movie => data.results = data.results.filter(movie =>
movie.poster_path && movie.poster_path &&
movie.overview && movie.overview &&
movie.vote_average > 0 movie.vote_average > 0
); ).map(movie => ({
// Добавляем полные URL для изображений
data.results = data.results.map(movie => ({
...movie, ...movie,
poster_path: this.getImageURL(movie.poster_path, 'w500'), poster_path: this.getImageURL(movie.poster_path, 'w500'),
backdrop_path: this.getImageURL(movie.backdrop_path, 'w1280') backdrop_path: this.getImageURL(movie.backdrop_path, 'original')
})); }));
return data; return response;
} }
async getMovie(id) { async getMovie(id) {
const movie = await this.makeRequest('GET', `/movie/${id}`); const response = await this.makeRequest('GET', `/movie/${id}`);
const movie = response.data;
return { return {
...movie, ...movie,
poster_path: this.getImageURL(movie.poster_path, 'w500'), poster_path: this.getImageURL(movie.poster_path, 'w500'),
backdrop_path: this.getImageURL(movie.backdrop_path, 'w1280') backdrop_path: this.getImageURL(movie.backdrop_path, 'original')
}; };
} }
async getPopularMovies(page = 1) { async getPopularMovies(page = 1) {
const data = await this.makeRequest('GET', '/movie/popular', { page }); const response = await this.makeRequest('GET', '/movie/popular', { page });
const data = response.data;
data.results = data.results.map(movie => ({ data.results = data.results.map(movie => ({
...movie, ...movie,
poster_path: this.getImageURL(movie.poster_path, 'w500'), poster_path: this.getImageURL(movie.poster_path, 'w500'),
backdrop_path: this.getImageURL(movie.backdrop_path, 'w1280') backdrop_path: this.getImageURL(movie.backdrop_path, 'original')
})); }));
return data; return response;
} }
async getTopRatedMovies(page = 1) { async getTopRatedMovies(page = 1) {
const data = await this.makeRequest('GET', '/movie/top_rated', { page }); const response = await this.makeRequest('GET', '/movie/top_rated', { page });
const data = response.data;
data.results = data.results.map(movie => ({ data.results = data.results.map(movie => ({
...movie, ...movie,
poster_path: this.getImageURL(movie.poster_path, 'w500'), poster_path: this.getImageURL(movie.poster_path, 'w500'),
backdrop_path: this.getImageURL(movie.backdrop_path, 'w1280') backdrop_path: this.getImageURL(movie.backdrop_path, 'original')
})); }));
return data; return response;
}
async getUpcomingMovies(page = 1) {
const data = await this.makeRequest('GET', '/movie/upcoming', { page });
data.results = data.results.map(movie => ({
...movie,
poster_path: this.getImageURL(movie.poster_path, 'w500'),
backdrop_path: this.getImageURL(movie.backdrop_path, 'w1280')
}));
return data;
}
async getMovieExternalIDs(id) {
return await this.makeRequest('GET', `/movie/${id}/external_ids`);
}
}
module.exports = TMDBClient;

View File

@@ -10,8 +10,8 @@ const app = express();
const port = process.env.PORT || 3000; const port = process.env.PORT || 3000;
// Определяем базовый URL для документации // Определяем базовый URL для документации
const BASE_URL = process.env.VERCEL_URL const BASE_URL = process.env.NODE_ENV === 'production'
? `https://${process.env.VERCEL_URL}` ? 'https://neomovies-api.vercel.app'
: `http://localhost:${port}`; : `http://localhost:${port}`;
// Swagger configuration // Swagger configuration
@@ -30,7 +30,7 @@ const swaggerOptions = {
servers: [ servers: [
{ {
url: BASE_URL, url: BASE_URL,
description: process.env.VERCEL_URL ? 'Production server' : 'Development server', description: process.env.NODE_ENV === 'production' ? 'Production server' : 'Development server',
}, },
], ],
tags: [ tags: [
@@ -207,22 +207,26 @@ const swaggerDocs = swaggerJsdoc(swaggerOptions);
// CORS configuration // CORS configuration
app.use(cors({ app.use(cors({
origin: '*', origin: true, // Разрешаем все origins в development
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'], allowedHeaders: ['X-Requested-With', 'Content-Type', 'Authorization', 'Accept']
credentials: true
})); }));
// Handle preflight requests
app.options('*', cors());
// Middleware // Middleware
app.use(express.json()); app.use(express.json());
app.use(express.static(path.join(__dirname, 'public'))); app.use(express.static(path.join(__dirname, 'public')));
// TMDB client middleware // TMDB client middleware
app.use((req, res, next) => { app.use((req, res, next) => {
if (!process.env.TMDB_ACCESS_TOKEN) { const token = process.env.TMDB_ACCESS_TOKEN || 'eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkOWRlZTY5ZjYzNzYzOGU2MjY5OGZhZGY0ZjhhYTNkYyIsInN1YiI6IjY1OTVkNmM5ODY5ZTc1NzJmOTY1MjZiZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.Wd_tBYGkAoGPVHq3A5DwV1iLs_eGvH3RRz86ghJTmU8';
if (!token) {
return res.status(500).json({ error: 'TMDB_ACCESS_TOKEN is not set' }); return res.status(500).json({ error: 'TMDB_ACCESS_TOKEN is not set' });
} }
req.tmdb = new TMDBClient(process.env.TMDB_ACCESS_TOKEN); req.tmdb = new TMDBClient(token);
next(); next();
}); });
@@ -268,5 +272,5 @@ app.use((err, req, res, next) => {
// Start server // Start server
app.listen(port, () => { app.listen(port, () => {
console.log(`Server is running on port ${port}`); console.log(`Server is running on port ${port}`);
console.log(`Documentation available at https://neomovies-api/api-docs`); console.log(`Documentation available at https://neomovies-api.vercel.app/api-docs`);
}); });

View File

@@ -62,30 +62,34 @@ const { formatDate } = require('../utils/date');
router.get('/search', async (req, res) => { router.get('/search', async (req, res) => {
try { try {
const { query, page = 1 } = req.query; const { query, page = 1 } = req.query;
if (!query) { if (!query) {
return res.status(400).json({ error: "query parameter is required" }); return res.status(400).json({ error: 'Query parameter is required' });
} }
const results = await req.tmdb.searchMovies(query, page); const response = await req.tmdb.searchMovies(query, page);
const response = { if (!response || !response.data) {
page: results.page, throw new Error('Failed to fetch data from TMDB');
total_pages: results.total_pages, }
total_results: results.total_results,
results: results.results.map(movie => ({
id: movie.id,
title: movie.title,
overview: movie.overview,
release_date: formatDate(movie.release_date),
vote_average: movie.vote_average,
poster_path: movie.poster_path,
backdrop_path: movie.backdrop_path
}))
};
res.json(response); const { results, ...rest } = response.data;
const formattedResults = results.map(movie => ({
...movie,
release_date: formatDate(movie.release_date)
}));
res.json({
...rest,
results: formattedResults
});
} catch (error) { } catch (error) {
res.status(500).json({ error: error.message }); console.error('Search movies error:', error);
res.status(500).json({
error: 'Failed to search movies',
details: process.env.NODE_ENV === 'development' ? error.message : undefined
});
} }
}); });

View File

@@ -9,14 +9,43 @@
"routes": [ "routes": [
{ {
"src": "/api-docs/(.*)", "src": "/api-docs/(.*)",
"dest": "src/index.js" "headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "X-Requested-With, Content-Type, Accept"
},
"dest": "/src/index.js"
},
{
"src": "/movies/(.*)",
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "X-Requested-With, Content-Type, Accept"
},
"dest": "/src/index.js"
},
{
"src": "/health",
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Allow-Headers": "X-Requested-With, Content-Type, Accept"
},
"dest": "/src/index.js"
}, },
{ {
"src": "/(.*)", "src": "/(.*)",
"dest": "src/index.js" "headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "X-Requested-With, Content-Type, Accept"
},
"dest": "/src/index.js"
} }
], ],
"env": { "env": {
"NODE_ENV": "production" "NODE_ENV": "production",
"TMDB_ACCESS_TOKEN": "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkOWRlZTY5ZjYzNzYzOGU2MjY5OGZhZGY0ZjhhYTNkYyIsInN1YiI6IjY1OTVkNmM5ODY5ZTc1NzJmOTY1MjZiZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.Wd_tBYGkAoGPVHq3A5DwV1iLs_eGvH3RRz86ghJTmU8"
} }
} }