Update 35 files

- /src/api.ts
- /src/lib/utils.ts
- /src/lib/neoApi.ts
- /src/lib/mongodb.ts
- /src/lib/favoritesApi.ts
- /src/lib/models/Favorite.ts
- /src/hooks/useTMDBMovies.ts
- /src/hooks/useImageLoader.ts
- /src/hooks/useMovies.ts
- /src/types/movie.ts
- /src/components/SearchResults.tsx
- /src/components/SettingsContent.tsx
- /src/components/MovieCard.tsx
- /src/components/FavoriteButton.tsx
- /src/components/admin/MovieSearch.tsx
- /src/app/page.tsx
- /src/app/movie/[id]/page.tsx
- /src/app/movie/[id]/MovieContent.tsx
- /src/app/api/movies/upcoming/route.ts
- /src/app/api/movies/search/route.ts
- /src/app/api/movies/top-rated/route.ts
- /src/app/api/movies/[id]/route.ts
- /src/app/api/movies/popular/route.ts
- /src/app/api/favorites/route.ts
- /src/app/api/favorites/check/[mediaId]/route.ts
- /src/app/api/favorites/[mediaId]/route.ts
- /src/app/tv/[id]/TVShowContent.tsx
- /src/app/tv/[id]/TVShowPage.tsx
- /src/app/tv/[id]/page.tsx
- /src/app/favorites/page.tsx
- /src/configs/auth.ts
- /next.config.js
- /package.json
- /README.md
- /package-lock.json
This commit is contained in:
2025-01-05 01:43:34 +00:00
parent 3c3f58c7d3
commit 0aa6fb6038
35 changed files with 1656 additions and 548 deletions

30
src/lib/favoritesApi.ts Normal file
View File

@@ -0,0 +1,30 @@
import axios from 'axios';
// Создаем экземпляр axios
const api = axios.create({
headers: {
'Content-Type': 'application/json'
}
});
export const favoritesAPI = {
// Получить все избранные
getFavorites() {
return api.get('/api/favorites');
},
// Добавить в избранное
addFavorite(data: { mediaId: string; mediaType: 'movie' | 'tv'; title: string; posterPath?: string }) {
return api.post('/api/favorites', data);
},
// Удалить из избранного
removeFavorite(mediaId: string, mediaType: 'movie' | 'tv') {
return api.delete(`/api/favorites/${mediaId}?mediaType=${mediaType}`);
},
// Проверить есть ли в избранном
checkFavorite(mediaId: string, mediaType: 'movie' | 'tv') {
return api.get(`/api/favorites/check/${mediaId}?mediaType=${mediaType}`);
}
};

View File

@@ -0,0 +1,31 @@
import mongoose from 'mongoose';
const favoriteSchema = new mongoose.Schema({
userId: {
type: String,
required: true
},
mediaId: {
type: String,
required: true
},
mediaType: {
type: String,
required: true,
enum: ['movie', 'tv']
},
title: {
type: String,
required: true
},
posterPath: String,
createdAt: {
type: Date,
default: Date.now
}
});
// Составной индекс для уникальности комбинации userId, mediaId и mediaType
favoriteSchema.index({ userId: 1, mediaId: 1, mediaType: 1 }, { unique: true });
export default mongoose.models.Favorite || mongoose.model('Favorite', favoriteSchema);

View File

@@ -28,3 +28,35 @@ export async function connectToDatabase() {
const db = client.db();
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 }
);
}

154
src/lib/neoApi.ts Normal file
View File

@@ -0,0 +1,154 @@
import axios from 'axios';
const API_URL = process.env.NEXT_PUBLIC_API_URL;
export const neoApi = axios.create({
baseURL: API_URL,
headers: {
'Content-Type': 'application/json'
},
timeout: 30000 // Увеличиваем таймаут до 30 секунд
});
// Добавляем перехватчики запросов
neoApi.interceptors.request.use(
(config) => {
if (config.params?.page) {
const page = parseInt(config.params.page);
if (isNaN(page) || page < 1) {
config.params.page = 1;
}
}
return config;
},
(error) => {
console.error('❌ Request Error:', error);
return Promise.reject(error);
}
);
// Добавляем перехватчики ответов
neoApi.interceptors.response.use(
(response) => {
return response;
},
(error) => {
console.error('❌ Response Error:', {
status: error.response?.status,
url: error.config?.url,
method: error.config?.method,
message: error.message
});
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}`;
};
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?: Genre[];
}
export interface MovieResponse {
page: number;
results: Movie[];
total_pages: number;
total_results: number;
}
export const searchAPI = {
// Мультипоиск (фильмы и сериалы)
multiSearch(query: string, page = 1) {
return neoApi.get<MovieResponse>('/search/multi', {
params: {
query,
page
},
timeout: 30000 // Увеличиваем таймаут до 30 секунд
});
}
};
export const moviesAPI = {
// Получение популярных фильмов
getPopular(page = 1) {
return neoApi.get<MovieResponse>('/movies/popular', {
params: { page },
timeout: 30000
});
},
// Получение данных о фильме по его ID
getMovie(id: string | number) {
return neoApi.get(`/movies/${id}`, { timeout: 30000 });
},
// Поиск фильмов
searchMovies(query: string, page = 1) {
return neoApi.get<MovieResponse>('/movies/search', {
params: {
query,
page
},
timeout: 30000
});
},
// Получение IMDB ID
getImdbId(id: string | number) {
return neoApi.get(`/movies/${id}/external-ids`, { timeout: 30000 }).then(res => res.data.imdb_id);
}
};
export const tvShowsAPI = {
// Получение популярных сериалов
getPopular(page = 1) {
return neoApi.get('/tv/popular', {
params: { page },
timeout: 30000
});
},
// Получение данных о сериале по его ID
getTVShow(id: string | number) {
return neoApi.get(`/tv/${id}`, { timeout: 30000 });
},
// Поиск сериалов
searchTVShows(query: string, page = 1) {
return neoApi.get('/tv/search', {
params: {
query,
page
},
timeout: 30000
});
},
// Получение IMDB ID
getImdbId(id: string | number) {
return neoApi.get(`/tv/${id}/external-ids`, { timeout: 30000 }).then(res => res.data.imdb_id);
}
};

View File

@@ -7,10 +7,28 @@ export const validateEmail = (email: string) => {
return re.test(email);
};
export const formatDate = (date: Date) => {
return new Intl.DateTimeFormat('ru-RU', {
year: 'numeric',
month: 'long',
day: 'numeric',
}).format(date);
export const formatDate = (dateString: string | Date | undefined | null) => {
if (!dateString) return 'Нет даты';
// Если это строка и она уже содержит "г." (формат с API), возвращаем как есть
if (typeof dateString === 'string' && dateString.includes(' г.')) {
return dateString;
}
try {
const date = typeof dateString === 'string' ? new Date(dateString) : dateString;
if (isNaN(date.getTime())) {
return 'Нет даты';
}
return new Intl.DateTimeFormat('ru-RU', {
year: 'numeric',
month: 'long',
day: 'numeric',
}).format(date) + ' г.';
} catch (error) {
console.error('Error formatting date:', error);
return 'Нет даты';
}
};