2025-08-07 13:47:42 +00:00
|
|
|
package services
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"net/http"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
|
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
|
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
|
|
|
|
|
2025-08-08 16:47:02 +00:00
|
|
|
"neomovies-api/pkg/config"
|
2025-08-07 13:47:42 +00:00
|
|
|
"neomovies-api/pkg/models"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type ReactionsService struct {
|
|
|
|
|
db *mongo.Database
|
|
|
|
|
client *http.Client
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewReactionsService(db *mongo.Database) *ReactionsService {
|
|
|
|
|
return &ReactionsService{
|
|
|
|
|
db: db,
|
|
|
|
|
client: &http.Client{},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 16:47:02 +00:00
|
|
|
var validReactions = []string{"fire", "nice", "think", "bore", "shit"}
|
2025-08-07 13:47:42 +00:00
|
|
|
|
|
|
|
|
// Получить счетчики реакций для медиа из внешнего API (cub.rip)
|
|
|
|
|
func (s *ReactionsService) GetReactionCounts(mediaType, mediaID string) (*models.ReactionCounts, error) {
|
|
|
|
|
cubID := fmt.Sprintf("%s_%s", mediaType, mediaID)
|
2025-09-28 11:37:56 +00:00
|
|
|
|
2025-08-08 16:47:02 +00:00
|
|
|
resp, err := s.client.Get(fmt.Sprintf("%s/reactions/get/%s", config.CubAPIBaseURL, cubID))
|
2025-08-07 13:47:42 +00:00
|
|
|
if err != nil {
|
2025-08-08 16:47:02 +00:00
|
|
|
return &models.ReactionCounts{}, nil
|
2025-08-07 13:47:42 +00:00
|
|
|
}
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
|
return &models.ReactionCounts{}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return &models.ReactionCounts{}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var response struct {
|
|
|
|
|
Result []struct {
|
|
|
|
|
Type string `json:"type"`
|
|
|
|
|
Counter int `json:"counter"`
|
|
|
|
|
} `json:"result"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := json.Unmarshal(body, &response); err != nil {
|
|
|
|
|
return &models.ReactionCounts{}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
counts := &models.ReactionCounts{}
|
|
|
|
|
for _, reaction := range response.Result {
|
|
|
|
|
switch reaction.Type {
|
|
|
|
|
case "fire":
|
|
|
|
|
counts.Fire = reaction.Counter
|
|
|
|
|
case "nice":
|
|
|
|
|
counts.Nice = reaction.Counter
|
|
|
|
|
case "think":
|
|
|
|
|
counts.Think = reaction.Counter
|
|
|
|
|
case "bore":
|
|
|
|
|
counts.Bore = reaction.Counter
|
|
|
|
|
case "shit":
|
|
|
|
|
counts.Shit = reaction.Counter
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return counts, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 16:47:02 +00:00
|
|
|
func (s *ReactionsService) GetMyReaction(userID, mediaType, mediaID string) (string, error) {
|
2025-08-07 13:47:42 +00:00
|
|
|
collection := s.db.Collection("reactions")
|
2025-08-08 16:47:02 +00:00
|
|
|
ctx := context.Background()
|
2025-08-07 13:47:42 +00:00
|
|
|
|
2025-09-28 11:37:56 +00:00
|
|
|
var result struct {
|
|
|
|
|
Type string `bson:"type"`
|
|
|
|
|
}
|
2025-08-08 16:47:02 +00:00
|
|
|
err := collection.FindOne(ctx, bson.M{
|
|
|
|
|
"userId": userID,
|
|
|
|
|
"mediaType": mediaType,
|
|
|
|
|
"mediaId": mediaID,
|
|
|
|
|
}).Decode(&result)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if err == mongo.ErrNoDocuments {
|
|
|
|
|
return "", nil
|
|
|
|
|
}
|
|
|
|
|
return "", err
|
2025-08-07 13:47:42 +00:00
|
|
|
}
|
2025-08-08 16:47:02 +00:00
|
|
|
return result.Type, nil
|
2025-08-07 13:47:42 +00:00
|
|
|
}
|
|
|
|
|
|
2025-08-08 16:47:02 +00:00
|
|
|
func (s *ReactionsService) SetReaction(userID, mediaType, mediaID, reactionType string) error {
|
2025-08-07 13:47:42 +00:00
|
|
|
if !s.isValidReactionType(reactionType) {
|
2025-08-08 16:47:02 +00:00
|
|
|
return fmt.Errorf("invalid reaction type")
|
2025-08-07 13:47:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
collection := s.db.Collection("reactions")
|
2025-08-08 16:47:02 +00:00
|
|
|
ctx := context.Background()
|
2025-08-07 13:47:42 +00:00
|
|
|
|
2025-08-08 16:47:02 +00:00
|
|
|
_, err := collection.UpdateOne(
|
|
|
|
|
ctx,
|
|
|
|
|
bson.M{"userId": userID, "mediaType": mediaType, "mediaId": mediaID},
|
|
|
|
|
bson.M{"$set": bson.M{"type": reactionType, "updatedAt": time.Now()}},
|
|
|
|
|
options.Update().SetUpsert(true),
|
|
|
|
|
)
|
|
|
|
|
if err == nil {
|
|
|
|
|
go s.sendReactionToCub(fmt.Sprintf("%s_%s", mediaType, mediaID), reactionType)
|
2025-08-07 13:47:42 +00:00
|
|
|
}
|
2025-08-08 16:47:02 +00:00
|
|
|
return err
|
2025-08-07 13:47:42 +00:00
|
|
|
}
|
|
|
|
|
|
2025-08-08 16:47:02 +00:00
|
|
|
func (s *ReactionsService) RemoveReaction(userID, mediaType, mediaID string) error {
|
2025-08-07 13:47:42 +00:00
|
|
|
collection := s.db.Collection("reactions")
|
2025-08-08 16:47:02 +00:00
|
|
|
ctx := context.Background()
|
2025-08-07 13:47:42 +00:00
|
|
|
|
2025-08-08 16:47:02 +00:00
|
|
|
_, err := collection.DeleteOne(ctx, bson.M{
|
|
|
|
|
"userId": userID,
|
|
|
|
|
"mediaType": mediaType,
|
|
|
|
|
"mediaId": mediaID,
|
2025-08-07 13:47:42 +00:00
|
|
|
})
|
|
|
|
|
|
2025-08-08 16:47:02 +00:00
|
|
|
fullMediaID := fmt.Sprintf("%s_%s", mediaType, mediaID)
|
|
|
|
|
go s.sendReactionToCub(fullMediaID, "remove")
|
|
|
|
|
|
2025-08-07 13:47:42 +00:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Получить все реакции пользователя
|
|
|
|
|
func (s *ReactionsService) GetUserReactions(userID string, limit int) ([]models.Reaction, error) {
|
|
|
|
|
collection := s.db.Collection("reactions")
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
cursor, err := collection.Find(ctx, bson.M{"userId": userID})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
defer cursor.Close(ctx)
|
|
|
|
|
|
|
|
|
|
var reactions []models.Reaction
|
|
|
|
|
if err := cursor.All(ctx, &reactions); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return reactions, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *ReactionsService) isValidReactionType(reactionType string) bool {
|
2025-08-08 16:47:02 +00:00
|
|
|
for _, valid := range validReactions {
|
2025-08-07 13:47:42 +00:00
|
|
|
if valid == reactionType {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Отправка реакции в cub.rip API (асинхронно)
|
|
|
|
|
func (s *ReactionsService) sendReactionToCub(mediaID, reactionType string) {
|
2025-08-08 16:47:02 +00:00
|
|
|
url := fmt.Sprintf("%s/reactions/set", config.CubAPIBaseURL)
|
2025-09-28 11:37:56 +00:00
|
|
|
|
2025-08-07 13:47:42 +00:00
|
|
|
data := map[string]string{
|
|
|
|
|
"mediaId": mediaID,
|
|
|
|
|
"type": reactionType,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err := json.Marshal(data)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resp, err := s.client.Get(fmt.Sprintf("%s?mediaId=%s&type=%s", url, mediaID, reactionType))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
if resp.StatusCode == http.StatusOK {
|
|
|
|
|
fmt.Printf("Reaction sent to cub.rip: %s - %s\n", mediaID, reactionType)
|
|
|
|
|
}
|
2025-09-28 11:37:56 +00:00
|
|
|
}
|