Измения в АПИ

This commit is contained in:
2025-08-07 18:33:28 +00:00
parent b2db578f5f
commit a1f1deea13
45 changed files with 1955 additions and 1119 deletions

View File

@@ -1,262 +0,0 @@
import axios from 'axios';
const API_URL = process.env.NEXT_PUBLIC_API_URL;
if (!API_URL) {
throw new Error('NEXT_PUBLIC_API_URL is not defined in environment variables');
}
export const api = axios.create({
baseURL: API_URL,
headers: {
'Content-Type': 'application/json'
}
});
// Attach JWT token if present in localStorage
if (typeof window !== 'undefined') {
const token = localStorage.getItem('token');
if (token) {
api.defaults.headers.common['Authorization'] = `Bearer ${token}`;
}
}
// Update stored token on login response with { token }
api.interceptors.response.use((response) => {
if (response.config.url?.includes('/auth/login') && response.data?.token) {
if (typeof window !== 'undefined') {
localStorage.setItem('token', response.data.token);
api.defaults.headers.common['Authorization'] = `Bearer ${response.data.token}`;
}
}
return response;
});
export interface Category {
id: number;
name: string;
slug: string;
}
export interface Genre {
id: number;
name: string;
}
export interface Movie {
id: number;
title: string;
overview: string;
poster_path: string | null;
backdrop_path: string | null;
release_date: string;
vote_average: number;
vote_count: number;
genre_ids: number[];
runtime?: number;
genres?: Array<{ id: number; name: string }>;
}
export interface MovieDetails extends Movie {
genres: Genre[];
runtime: number;
imdb_id?: string | null;
tagline: string;
budget: number;
revenue: number;
videos: {
results: Video[];
};
credits: {
cast: Cast[];
crew: Crew[];
};
}
export interface TVShow {
id: number;
name: string;
overview: string;
poster_path: string | null;
backdrop_path: string | null;
first_air_date: string;
vote_average: number;
vote_count: number;
genre_ids: number[];
}
export interface TVShowDetails extends TVShow {
genres: Genre[];
number_of_episodes: number;
number_of_seasons: number;
tagline: string;
credits: {
cast: Cast[];
crew: Crew[];
};
seasons: Array<{
id: number;
name: string;
episode_count: number;
poster_path: string | null;
}>;
external_ids?: {
imdb_id: string | null;
tvdb_id: number | null;
tvrage_id: number | null;
};
}
export interface Video {
id: string;
key: string;
name: string;
site: string;
type: string;
}
export interface Cast {
id: number;
name: string;
character: string;
profile_path: string | null;
}
export interface Crew {
id: number;
name: string;
job: string;
profile_path: string | null;
}
export interface MovieResponse {
page: number;
results: Movie[];
total_pages: number;
total_results: number;
}
export interface TVShowResponse {
page: number;
results: TVShow[];
total_pages: number;
total_results: number;
}
export const categoriesAPI = {
// Получение всех категорий
getCategories() {
return api.get<{ categories: Category[] }>('/categories');
},
// Получение категории по ID
getCategory(id: number) {
return api.get<Category>(`/categories/${id}`);
},
// Получение фильмов по категории
getMoviesByCategory(categoryId: number, page = 1) {
return api.get<MovieResponse>(`/categories/${categoryId}/movies`, {
params: { page }
});
},
// Получение сериалов по категории
getTVShowsByCategory(categoryId: number, page = 1) {
return api.get<TVShowResponse>(`/categories/${categoryId}/tv`, {
params: { page }
});
}
};
export const moviesAPI = {
// Получение популярных фильмов
getPopular(page = 1) {
return api.get<MovieResponse>('/movies/popular', {
params: { page }
});
},
// Получение данных о фильме по его TMDB ID
getMovie(id: string | number) {
return api.get<MovieDetails>(`/movies/${id}`);
},
// Получение IMDb ID по TMDB ID для плеера
getImdbId(tmdbId: string | number) {
return api.get<{ imdb_id: string }>(`/movies/${tmdbId}/external-ids`);
},
// Получение видео по TMDB ID для плеера
getVideo(tmdbId: string | number) {
return api.get<{ results: Video[] }>(`/movies/${tmdbId}/videos`);
},
// Поиск фильмов
searchMovies(query: string, page = 1) {
return api.get<MovieResponse>('/movies/search', {
params: { query, page }
});
},
// Получение предстоящих фильмов
getUpcoming(page = 1) {
return api.get<MovieResponse>('/movies/upcoming', {
params: { page }
});
},
// Получение лучших фильмов
getTopRated(page = 1) {
return api.get<MovieResponse>('/movies/top-rated', {
params: { page }
});
},
// Получение фильмов по жанру
getMoviesByGenre(genreId: number, page = 1) {
return api.get<MovieResponse>('/movies/discover', {
params: { with_genres: genreId, page }
});
},
// Получение жанров
getGenres() {
return api.get<{ genres: Genre[] }>('/movies/genres');
}
};
export const tvAPI = {
// Получение популярных сериалов
getPopular(page = 1) {
return api.get<TVShowResponse>('/tv/popular', {
params: { page }
});
},
// Получение данных о сериале по его TMDB ID
getShow(id: string | number) {
return api.get<TVShowDetails>(`/tv/${id}`);
},
// Получение IMDb ID по TMDB ID для плеера
getImdbId(tmdbId: string | number) {
return api.get<{ imdb_id: string }>(`/tv/${tmdbId}/external-ids`);
},
// Поиск сериалов
searchShows(query: string, page = 1) {
return api.get<TVShowResponse>('/tv/search', {
params: { query, page }
});
}
};
// Мультипоиск (фильмы и сериалы)
export const searchAPI = {
multiSearch(query: string, page = 1) {
return api.get('/search/multi', {
params: { query, page }
});
}
};

View File

@@ -1,19 +1,10 @@
import { api } from './api';
import { neoApi } from './neoApi';
export const authAPI = {
register(data: { email: string; password: string; name?: string }) {
return api.post('/auth/register', data);
},
resendCode(email: string) {
return api.post('/auth/resend-code', { email });
},
verify(email: string, code: string) {
return api.post('/auth/verify', { email, code });
},
login(email: string, password: string) {
return api.post('/auth/login', { email, password });
},
deleteAccount() {
return api.delete('/auth/profile');
}
register: (data: any) => neoApi.post('/api/v1/auth/register', data),
resendCode: (email: string) => neoApi.post('/api/v1/auth/resend-code', { email }),
verify: (email: string, code: string) => neoApi.post('/api/v1/auth/verify', { email, code }),
checkVerification: (email: string) => neoApi.post('/api/v1/auth/check-verification', { email }),
login: (email: string, password: string) => neoApi.post('/api/v1/auth/login', { email, password }),
deleteAccount: () => neoApi.delete('/api/v1/auth/profile'),
};

View File

@@ -1,25 +1,24 @@
import { api } from './api';
import { neoApi } from './neoApi';
export const favoritesAPI = {
// Получить все избранные
// Получение всех избранных
getFavorites() {
return api.get('/favorites');
return neoApi.get('/api/v1/favorites');
},
// Добавить в избранное
addFavorite(data: { mediaId: string; mediaType: 'movie' | 'tv', title: string, posterPath: string }) {
const { mediaId, mediaType, title, posterPath } = data;
return api.post(`/favorites/${mediaId}?mediaType=${mediaType}`, { title, posterPath });
// Добавление в избранное
addFavorite(data: { mediaId: string; mediaType: string; title: string; posterPath?: string }) {
const { mediaId, mediaType, ...rest } = data;
return neoApi.post(`/api/v1/favorites/${mediaId}?mediaType=${mediaType}`, rest);
},
// Удалить из избранного
// Удаление из избранного
removeFavorite(mediaId: string) {
return api.delete(`/favorites/${mediaId}`);
return neoApi.delete(`/api/v1/favorites/${mediaId}`);
},
// Проверить есть ли в избранном
// Проверка, добавлен ли в избранное
checkFavorite(mediaId: string) {
return api.get(`/favorites/check/${mediaId}`);
return neoApi.get(`/api/v1/favorites/check/${mediaId}`);
}
};

View File

@@ -29,32 +29,26 @@ export async function connectToDatabase() {
return { db, client };
}
// Инициализация MongoDB
export async function initMongoDB() {
try {
const { db } = await connectToDatabase();
// Создаем уникальный индекс для избранного
await db.collection('favorites').createIndex(
{ userId: 1, mediaId: 1, mediaType: 1 },
{ unique: true }
);
console.log('MongoDB initialized successfully');
} catch (error) {
console.error('Error initializing MongoDB:', error);
throw error;
}
}
// Функция для сброса и создания индексов
export async function resetIndexes() {
const { db } = await connectToDatabase();
// Удаляем все индексы из коллекции favorites
await db.collection('favorites').dropIndexes();
// Создаем новый правильный индекс
await db.collection('favorites').createIndex(
{ userId: 1, mediaId: 1, mediaType: 1 },
{ unique: true }

View File

@@ -1,18 +1,25 @@
import axios from 'axios';
const API_URL = process.env.NEXT_PUBLIC_API_URL;
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'https://neomovies-test-api.vercel.app';
// Создание экземпляра Axios с базовыми настройками
export const neoApi = axios.create({
baseURL: API_URL,
headers: {
'Content-Type': 'application/json'
},
timeout: 30000 // Увеличиваем таймаут до 30 секунд
timeout: 30000
});
// Добавляем перехватчики запросов
// Перехватчик запросов
neoApi.interceptors.request.use(
(config) => {
// Получение токена из localStorage или другого хранилища
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// Логика для пагинации
if (config.params?.page) {
const page = parseInt(config.params.page);
if (isNaN(page) || page < 1) {
@@ -27,29 +34,34 @@ neoApi.interceptors.request.use(
}
);
// Добавляем перехватчики ответов
// Перехватчик ответов
neoApi.interceptors.response.use(
(response) => {
if (response.data && response.data.success && response.data.data !== undefined) {
response.data = response.data.data;
}
return response;
},
(error) => {
console.error('❌ Response Error:', {
status: error.response?.status,
statusText: error.response?.statusText,
url: error.config?.url,
method: error.config?.method,
message: error.message
message: error.message,
data: error.response?.data
});
return Promise.reject(error);
}
);
// Функция для получения URL изображения
export const getImageUrl = (path: string | null, size: string = 'w500'): string => {
if (!path) return '/images/placeholder.jpg';
// Извлекаем только ID изображения из полного пути
const imageId = path.split('/').pop();
if (!imageId) return '/images/placeholder.jpg';
return `${API_URL}/images/${size}/${imageId}`;
if (path.startsWith('http')) {
return path;
}
const cleanPath = path.startsWith('/') ? path.slice(1) : path;
return `${API_URL}/api/v1/images/${size}/${cleanPath}`;
};
export interface Genre {
@@ -80,10 +92,44 @@ export interface MovieResponse {
total_results: number;
}
export interface TorrentResult {
title: string;
tracker: string;
size: string;
seeders: number;
peers: number;
leechers: number;
quality: string;
voice?: string[];
types?: string[];
seasons?: number[];
category: string;
magnet: string;
torrent_link?: string;
details?: string;
publish_date: string;
added_date?: string;
source: string;
}
export interface TorrentSearchResponse {
query: string;
results: TorrentResult[];
total: number;
}
export interface AvailableSeasonsResponse {
title: string;
originalTitle: string;
year: string;
seasons: number[];
total: number;
}
export const searchAPI = {
// Поиск фильмов
searchMovies(query: string, page = 1) {
return neoApi.get<MovieResponse>('/movies/search', {
return neoApi.get<MovieResponse>('/api/v1/movies/search', {
params: {
query,
page
@@ -94,7 +140,7 @@ export const searchAPI = {
// Поиск сериалов
searchTV(query: string, page = 1) {
return neoApi.get<MovieResponse>('/tv/search', {
return neoApi.get<MovieResponse>('/api/v1/tv/search', {
params: {
query,
page
@@ -103,46 +149,19 @@ export const searchAPI = {
});
},
// Мультипоиск (фильмы и сериалы)
// Мультипоиск (фильмы и сериалы) - новый эндпоинт
async multiSearch(query: string, page = 1) {
// Запускаем параллельные запросы к фильмам и сериалам
try {
const [moviesResponse, tvResponse] = await Promise.all([
this.searchMovies(query, page),
this.searchTV(query, page)
]);
// Используем новый эндпоинт Go API
const response = await neoApi.get<MovieResponse>('/search/multi', {
params: {
query,
page
},
timeout: 30000
});
// Объединяем результаты
const moviesData = moviesResponse.data;
const tvData = tvResponse.data;
// Метаданные для пагинации
const totalResults = (moviesData.total_results || 0) + (tvData.total_results || 0);
const totalPages = Math.max(moviesData.total_pages || 0, tvData.total_pages || 0);
// Добавляем информацию о типе контента
const moviesWithType = (moviesData.results || []).map(movie => ({
...movie,
media_type: 'movie'
}));
const tvWithType = (tvData.results || []).map(show => ({
...show,
media_type: 'tv'
}));
// Объединяем и сортируем по популярности
const combinedResults = [...moviesWithType, ...tvWithType]
.sort((a, b) => (b.popularity || 0) - (a.popularity || 0));
return {
data: {
page: parseInt(String(page)),
results: combinedResults,
total_pages: totalPages,
total_results: totalResults
}
};
return response;
} catch (error) {
console.error('Error in multiSearch:', error);
throw error;
@@ -153,7 +172,7 @@ export const searchAPI = {
export const moviesAPI = {
// Получение популярных фильмов
getPopular(page = 1) {
return neoApi.get<MovieResponse>('/movies/popular', {
return neoApi.get<MovieResponse>('/api/v1/movies/popular', {
params: { page },
timeout: 30000
});
@@ -161,7 +180,7 @@ export const moviesAPI = {
// Получение фильмов с высоким рейтингом
getTopRated(page = 1) {
return neoApi.get<MovieResponse>('/movies/top_rated', {
return neoApi.get<MovieResponse>('/api/v1/movies/top-rated', {
params: { page },
timeout: 30000
});
@@ -169,7 +188,15 @@ export const moviesAPI = {
// Получение новинок
getNowPlaying(page = 1) {
return neoApi.get<MovieResponse>('/movies/now_playing', {
return neoApi.get<MovieResponse>('/api/v1/movies/now-playing', {
params: { page },
timeout: 30000
});
},
// Получение предстоящих фильмов
getUpcoming(page = 1) {
return neoApi.get<MovieResponse>('/api/v1/movies/upcoming', {
params: { page },
timeout: 30000
});
@@ -177,12 +204,12 @@ export const moviesAPI = {
// Получение данных о фильме по его ID
getMovie(id: string | number) {
return neoApi.get(`/movies/${id}`, { timeout: 30000 });
return neoApi.get(`/api/v1/movies/${id}`, { timeout: 30000 });
},
// Поиск фильмов
searchMovies(query: string, page = 1) {
return neoApi.get<MovieResponse>('/movies/search', {
return neoApi.get<MovieResponse>('/api/v1/movies/search', {
params: {
query,
page
@@ -191,16 +218,40 @@ export const moviesAPI = {
});
},
// Получение IMDB ID
getImdbId(id: string | number) {
return neoApi.get(`/movies/${id}/external_ids`, { timeout: 30000 }).then(res => res.data.imdb_id);
// Получение IMDB и других external ids
getExternalIds(id: string | number) {
return neoApi.get(`/api/v1/movies/${id}/external-ids`, { timeout: 30000 }).then(res => res.data);
}
};
export const tvShowsAPI = {
// Получение популярных сериалов
getPopular(page = 1) {
return neoApi.get('/tv/popular', {
return neoApi.get('/api/v1/tv/popular', {
params: { page },
timeout: 30000
});
},
// Получение сериалов с высоким рейтингом
getTopRated(page = 1) {
return neoApi.get('/api/v1/tv/top-rated', {
params: { page },
timeout: 30000
});
},
// Получение сериалов в эфире
getOnTheAir(page = 1) {
return neoApi.get('/api/v1/tv/on-the-air', {
params: { page },
timeout: 30000
});
},
// Получение сериалов, которые выходят сегодня
getAiringToday(page = 1) {
return neoApi.get('/api/v1/tv/airing-today', {
params: { page },
timeout: 30000
});
@@ -208,12 +259,12 @@ export const tvShowsAPI = {
// Получение данных о сериале по его ID
getTVShow(id: string | number) {
return neoApi.get(`/tv/${id}`, { timeout: 30000 });
return neoApi.get(`/api/v1/tv/${id}`, { timeout: 30000 });
},
// Поиск сериалов
searchTVShows(query: string, page = 1) {
return neoApi.get('/tv/search', {
return neoApi.get('/api/v1/tv/search', {
params: {
query,
page
@@ -222,8 +273,101 @@ export const tvShowsAPI = {
});
},
// Получение IMDB ID
getImdbId(id: string | number) {
return neoApi.get(`/tv/${id}/external-ids`, { timeout: 30000 }).then(res => res.data.imdb_id);
// Получение IMDB и других external ids
getExternalIds(id: string | number) {
return neoApi.get(`/api/v1/tv/${id}/external-ids`, { timeout: 30000 }).then(res => res.data);
}
};
export const torrentsAPI = {
// Поиск торрентов по IMDB ID
searchTorrents(imdbId: string, type: 'movie' | 'tv', options?: {
season?: number;
quality?: string;
minQuality?: string;
maxQuality?: string;
excludeQualities?: string;
hdr?: boolean;
hevc?: boolean;
sortBy?: string;
sortOrder?: string;
groupByQuality?: boolean;
groupBySeason?: boolean;
}) {
const params: any = { type };
if (options) {
Object.entries(options).forEach(([key, value]) => {
if (value !== undefined) {
if (key === 'excludeQualities' && Array.isArray(value)) {
params[key] = value.join(',');
} else {
params[key] = value;
}
}
});
}
return neoApi.get<TorrentSearchResponse>(`/api/v1/torrents/search/${imdbId}`, {
params,
timeout: 30000
});
},
// Получение доступных сезонов для сериала
getAvailableSeasons(title: string, originalTitle?: string, year?: string) {
const params: any = { title };
if (originalTitle) params.originalTitle = originalTitle;
if (year) params.year = year;
return neoApi.get<AvailableSeasonsResponse>('/api/v1/torrents/seasons', {
params,
timeout: 30000
});
},
// Универсальный поиск торрентов по запросу
searchByQuery(query: string, type: 'movie' | 'tv' | 'anime' = 'movie', year?: string) {
const params: any = { query, type };
if (year) params.year = year;
return neoApi.get<TorrentSearchResponse>('/api/v1/torrents/search', {
params,
timeout: 30000
});
}
};
export const categoriesAPI = {
// Получение всех категорий
getCategories() {
return neoApi.get<{ categories: Category[] }>('/api/v1/categories');
},
// Получение категории по ID
getCategory(id: number) {
return neoApi.get<Category>(`/api/v1/categories/${id}`);
},
// Получение фильмов по категории
getMoviesByCategory(categoryId: number, page = 1) {
return neoApi.get<MovieResponse>(`/api/v1/categories/${categoryId}/movies`, {
params: { page }
});
},
// Получение сериалов по категории
getTVShowsByCategory(categoryId: number, page = 1) {
return neoApi.get<MovieResponse>(`/api/v1/categories/${categoryId}/tv`, {
params: { page }
});
}
};
// Новый API-клиент для работы с аутентификацией и профилем
export const authAPI = {
// Новый метод для удаления аккаунта
deleteAccount() {
return neoApi.delete('/api/v1/profile');
}
};

View File

@@ -1,28 +1,30 @@
import { api } from './api';
import { neoApi } from './neoApi';
export interface Reaction {
_id: string;
userId: string;
type: 'like' | 'dislike';
mediaId: string;
mediaType: 'movie' | 'tv';
type: 'fire' | 'nice' | 'think' | 'bore' | 'shit';
createdAt: string;
}
export const reactionsAPI = {
// [PUBLIC] Получить счетчики для всех типов реакций
getReactionCounts(mediaType: string, mediaId: string): Promise<{ data: Record<string, number> }> {
return api.get(`/reactions/${mediaType}/${mediaId}/counts`);
// Получение счетчиков реакций
getReactionCounts(mediaType: string, mediaId: string) {
return neoApi.get(`/api/v1/reactions/${mediaType}/${mediaId}/counts`);
},
// [AUTH] Получить реакцию пользователя для медиа
getMyReaction(mediaType: string, mediaId: string): Promise<{ data: Reaction | null }> {
return api.get(`/reactions/${mediaType}/${mediaId}/my-reaction`);
// Получение моей реакции
getMyReaction(mediaType: string, mediaId: string) {
return neoApi.get(`/api/v1/reactions/${mediaType}/${mediaId}/my-reaction`);
},
// [AUTH] Установить/обновить/удалить реакцию
setReaction(mediaType: string, mediaId: string, type: Reaction['type']): Promise<{ data: Reaction }> {
// Установка реакции
setReaction(mediaType: string, mediaId: string, type: 'like' | 'dislike') {
const fullMediaId = `${mediaType}_${mediaId}`;
return api.post('/reactions', { mediaId: fullMediaId, type });
return neoApi.post('/api/v1/reactions', { mediaId: fullMediaId, type });
},
// Удаление реакции
removeReaction(mediaType: string, mediaId: string) {
return neoApi.delete(`/api/v1/reactions/${mediaType}/${mediaId}`);
}
};