Files
neomovies-api/pkg/services/favorites.go
2025-11-21 20:46:54 +02:00

258 lines
7.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package services
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"neomovies-api/pkg/models"
)
type FavoritesService struct {
db *mongo.Database
tmdb *TMDBService
kinopoisk *KinopoiskService
}
func NewFavoritesService(db *mongo.Database, tmdb *TMDBService) *FavoritesService {
return &FavoritesService{
db: db,
tmdb: tmdb,
}
}
func NewFavoritesServiceWithKP(db *mongo.Database, tmdb *TMDBService, kp *KinopoiskService) *FavoritesService {
return &FavoritesService{
db: db,
tmdb: tmdb,
kinopoisk: kp,
}
}
func (s *FavoritesService) AddToFavorites(userID, mediaID, mediaType string) error {
collection := s.db.Collection("favorites")
// Проверяем, не добавлен ли уже в избранное
filter := bson.M{
"userId": userID,
"mediaId": mediaID,
"mediaType": mediaType,
}
var existingFavorite models.Favorite
err := collection.FindOne(context.Background(), filter).Decode(&existingFavorite)
if err == nil {
// Уже в избранном
return nil
}
// Получаем информацию из TMDB
mediaIDInt, err := strconv.Atoi(mediaID)
if err != nil {
return fmt.Errorf("invalid media ID: %s", mediaID)
}
var movie *models.Movie
var tv *models.TVShow
if mediaType == "movie" {
movie, err = s.tmdb.GetMovie(mediaIDInt, "ru-RU")
if err != nil {
return err
}
} else if mediaType == "tv" {
tv, err = s.tmdb.GetTVShow(mediaIDInt, "ru-RU")
if err != nil {
return err
}
} else {
return fmt.Errorf("invalid media type: %s", mediaType)
}
favorite := models.Favorite{
UserID: userID,
MediaID: mediaID,
MediaType: mediaType,
CreatedAt: time.Now(),
}
if movie != nil {
favorite.Title = movie.Title
favorite.NameRu = movie.Title
favorite.NameEn = movie.OriginalTitle
favorite.PosterPath = movie.PosterPath
if len(movie.ReleaseDate) >= 4 {
year, _ := strconv.Atoi(movie.ReleaseDate[:4])
favorite.Year = year
}
favorite.Rating = movie.VoteAverage
} else if tv != nil {
favorite.Title = tv.Name
favorite.NameRu = tv.Name
favorite.NameEn = tv.OriginalName
favorite.PosterPath = tv.PosterPath
if len(tv.FirstAirDate) >= 4 {
year, _ := strconv.Atoi(tv.FirstAirDate[:4])
favorite.Year = year
}
favorite.Rating = tv.VoteAverage
}
_, err = collection.InsertOne(context.Background(), favorite)
return err
}
// AddToFavoritesWithInfo adds media to favorites with provided media information
func (s *FavoritesService) AddToFavoritesWithInfo(userID, mediaID, mediaType string, mediaInfo *models.MediaInfo) error {
collection := s.db.Collection("favorites")
// Проверяем, не добавлен ли уже в избранное
filter := bson.M{
"userId": userID,
"mediaId": mediaID,
"mediaType": mediaType,
}
var existingFavorite models.Favorite
err := collection.FindOne(context.Background(), filter).Decode(&existingFavorite)
if err == nil {
// Уже в избранном
return nil
}
// Определяем источник данных и обрабатываем постер соответственно
var posterPath, posterUrlPreview string
if mediaInfo.PosterPath != "" {
// Проверяем, это TMDB путь (начинается с /) или полный URL от Кинопоиска
if len(mediaInfo.PosterPath) > 0 && mediaInfo.PosterPath[0] == '/' {
// TMDB путь - формируем полный URL
posterPath = fmt.Sprintf("https://image.tmdb.org/t/p/w500%s", mediaInfo.PosterPath)
} else if len(mediaInfo.PosterPath) > 10 && (mediaInfo.PosterPath[:8] == "https://" || mediaInfo.PosterPath[:7] == "http://") {
// Полный URL от Кинопоиска - извлекаем ID и формируем через API
// Пример: https://kinopoiskapiunofficial.tech/images/posters/kp_small/6605654.jpg
// Нужно получить: /api/v1/images/kp_small/6605654
posterUrlPreview = extractKinopoiskImagePath(mediaInfo.PosterPath)
posterPath = posterUrlPreview
} else {
// Другой формат - используем как есть
posterPath = mediaInfo.PosterPath
}
}
// Извлекаем год из даты
year := 0
if mediaType == "movie" && len(mediaInfo.ReleaseDate) >= 4 {
year, _ = strconv.Atoi(mediaInfo.ReleaseDate[:4])
} else if mediaType == "tv" && len(mediaInfo.FirstAirDate) >= 4 {
year, _ = strconv.Atoi(mediaInfo.FirstAirDate[:4])
}
favorite := models.Favorite{
UserID: userID,
MediaID: mediaID,
MediaType: mediaType,
Title: mediaInfo.Title,
NameRu: mediaInfo.Title,
NameEn: mediaInfo.OriginalTitle,
PosterPath: posterPath,
PosterUrlPreview: posterUrlPreview,
Year: year,
Rating: mediaInfo.VoteAverage,
CreatedAt: time.Now(),
}
_, err = collection.InsertOne(context.Background(), favorite)
return err
}
func (s *FavoritesService) RemoveFromFavorites(userID, mediaID, mediaType string) error {
collection := s.db.Collection("favorites")
filter := bson.M{
"userId": userID,
"mediaId": mediaID,
"mediaType": mediaType,
}
_, err := collection.DeleteOne(context.Background(), filter)
return err
}
func (s *FavoritesService) GetFavorites(userID string) ([]models.Favorite, error) {
collection := s.db.Collection("favorites")
filter := bson.M{
"userId": userID,
}
cursor, err := collection.Find(context.Background(), filter)
if err != nil {
return nil, err
}
defer cursor.Close(context.Background())
var favorites []models.Favorite
err = cursor.All(context.Background(), &favorites)
if err != nil {
return nil, err
}
// Возвращаем пустой массив вместо nil если нет избранных
if favorites == nil {
favorites = []models.Favorite{}
}
return favorites, nil
}
func (s *FavoritesService) IsFavorite(userID, mediaID, mediaType string) (bool, error) {
collection := s.db.Collection("favorites")
filter := bson.M{
"userId": userID,
"mediaId": mediaID,
"mediaType": mediaType,
}
var favorite models.Favorite
err := collection.FindOne(context.Background(), filter).Decode(&favorite)
if err != nil {
if err == mongo.ErrNoDocuments {
return false, nil
}
return false, err
}
return true, nil
}
// extractKinopoiskImagePath извлекает путь изображения Кинопоиска и преобразует его в API путь
// Пример входа: https://kinopoiskapiunofficial.tech/images/posters/kp_small/6605654.jpg
// Пример выхода: /api/v1/images/kp_small/6605654
func extractKinopoiskImagePath(imageURL string) string {
// Ищем паттерн /images/posters/
pattern := "/images/posters/"
idx := strings.Index(imageURL, pattern)
if idx == -1 {
return imageURL
}
// Получаем часть после /images/posters/
// Например: kp_small/6605654.jpg
remainder := imageURL[idx+len(pattern):]
// Удаляем расширение файла
if lastDot := strings.LastIndex(remainder, "."); lastDot != -1 {
remainder = remainder[:lastDot]
}
// Формируем API путь
return fmt.Sprintf("/api/v1/images/%s", remainder)
}