mirror of
https://gitlab.com/foxixus/neomovies.git
synced 2025-10-29 10:28:49 +05:00
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:
30
src/lib/favoritesApi.ts
Normal file
30
src/lib/favoritesApi.ts
Normal 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}`);
|
||||
}
|
||||
};
|
||||
31
src/lib/models/Favorite.ts
Normal file
31
src/lib/models/Favorite.ts
Normal 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);
|
||||
@@ -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
154
src/lib/neoApi.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
@@ -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 'Нет даты';
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user