diff --git a/src/config/tmdb.js b/src/config/tmdb.js index ada9f24..e96245f 100644 --- a/src/config/tmdb.js +++ b/src/config/tmdb.js @@ -7,13 +7,27 @@ class TMDBClient { headers: { 'Authorization': `Bearer ${accessToken}`, '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 = {}) { try { - const response = await this.client.request({ + const response = await this.client({ method, url: endpoint, params: { @@ -22,10 +36,12 @@ class TMDBClient { region: 'RU' } }); - return response.data; + return response; } catch (error) { - console.error(`TMDB API Error: ${error.message}`); - throw error; + if (error.response) { + 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) { - const data = await this.makeRequest('GET', '/search/movie', { + const response = await this.makeRequest('GET', '/search/movie', { query, page, include_adult: false }); + const data = response.data; + // Фильтруем результаты data.results = data.results.filter(movie => movie.poster_path && movie.overview && movie.vote_average > 0 - ); - - // Добавляем полные URL для изображений - data.results = data.results.map(movie => ({ + ).map(movie => ({ ...movie, 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) { - const movie = await this.makeRequest('GET', `/movie/${id}`); + const response = await this.makeRequest('GET', `/movie/${id}`); + const movie = response.data; return { ...movie, 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) { - 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 => ({ ...movie, 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) { - 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 => ({ ...movie, 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; - } - - 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; + return response; diff --git a/src/index.js b/src/index.js index db098cd..3264a32 100644 --- a/src/index.js +++ b/src/index.js @@ -10,8 +10,8 @@ const app = express(); const port = process.env.PORT || 3000; // Определяем базовый URL для документации -const BASE_URL = process.env.VERCEL_URL - ? `https://${process.env.VERCEL_URL}` +const BASE_URL = process.env.NODE_ENV === 'production' + ? 'https://neomovies-api.vercel.app' : `http://localhost:${port}`; // Swagger configuration @@ -30,7 +30,7 @@ const swaggerOptions = { servers: [ { url: BASE_URL, - description: process.env.VERCEL_URL ? 'Production server' : 'Development server', + description: process.env.NODE_ENV === 'production' ? 'Production server' : 'Development server', }, ], tags: [ @@ -207,22 +207,26 @@ const swaggerDocs = swaggerJsdoc(swaggerOptions); // CORS configuration app.use(cors({ - origin: '*', + origin: true, // Разрешаем все origins в development + credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - allowedHeaders: ['Content-Type', 'Authorization'], - credentials: true + allowedHeaders: ['X-Requested-With', 'Content-Type', 'Authorization', 'Accept'] })); +// Handle preflight requests +app.options('*', cors()); + // Middleware app.use(express.json()); app.use(express.static(path.join(__dirname, 'public'))); // TMDB client middleware 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' }); } - req.tmdb = new TMDBClient(process.env.TMDB_ACCESS_TOKEN); + req.tmdb = new TMDBClient(token); next(); }); @@ -268,5 +272,5 @@ app.use((err, req, res, next) => { // Start server app.listen(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`); +}); \ No newline at end of file diff --git a/src/routes/movies.js b/src/routes/movies.js index 84dc57f..4cc1cd8 100644 --- a/src/routes/movies.js +++ b/src/routes/movies.js @@ -62,30 +62,34 @@ const { formatDate } = require('../utils/date'); 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" }); + 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 = { - page: results.page, - 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 - })) - }; + if (!response || !response.data) { + throw new Error('Failed to fetch data from TMDB'); + } - 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) { - 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 + }); } }); diff --git a/vercel.json b/vercel.json index e6e16d3..5a30818 100644 --- a/vercel.json +++ b/vercel.json @@ -9,14 +9,43 @@ "routes": [ { "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": "/(.*)", - "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": { - "NODE_ENV": "production" + "NODE_ENV": "production", + "TMDB_ACCESS_TOKEN": "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkOWRlZTY5ZjYzNzYzOGU2MjY5OGZhZGY0ZjhhYTNkYyIsInN1YiI6IjY1OTVkNmM5ODY5ZTc1NzJmOTY1MjZiZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.Wd_tBYGkAoGPVHq3A5DwV1iLs_eGvH3RRz86ghJTmU8" } }