veza/veza-backend-api/internal/core/social/service.go

206 lines
5.6 KiB
Go
Raw Normal View History

2025-12-03 19:29:37 +00:00
package social
import (
"context"
"fmt"
"github.com/google/uuid"
"go.uber.org/zap"
"gorm.io/gorm"
)
// SocialService gère les interactions sociales
type SocialService interface {
CreatePost(ctx context.Context, userID uuid.UUID, content string, attachments map[string]uuid.UUID) (*Post, error)
GetGlobalFeed(ctx context.Context, limit, offset int) ([]FeedItem, error)
GetUserFeed(ctx context.Context, userID uuid.UUID, limit, offset int) ([]FeedItem, error)
// Interactions
ToggleLike(ctx context.Context, userID uuid.UUID, targetID uuid.UUID, targetType string) (bool, error)
AddComment(ctx context.Context, userID uuid.UUID, targetID uuid.UUID, targetType string, content string) (*Comment, error)
// Internal
CreateActivityPost(ctx context.Context, userID uuid.UUID, content string, meta map[string]interface{}) error
}
// Service implémente SocialService
type Service struct {
db *gorm.DB
logger *zap.Logger
}
// NewService crée une nouvelle instance du service social
func NewService(db *gorm.DB, logger *zap.Logger) *Service {
return &Service{
db: db,
logger: logger,
}
}
// CreatePost crée une nouvelle publication
func (s *Service) CreatePost(ctx context.Context, userID uuid.UUID, content string, attachments map[string]uuid.UUID) (*Post, error) {
post := &Post{
UserID: userID,
Content: content,
Type: PostTypeStatus,
}
// Handle attachments
if trackID, ok := attachments["track_id"]; ok {
post.TrackID = &trackID
post.Type = PostTypeShare
}
if playlistID, ok := attachments["playlist_id"]; ok {
post.PlaylistID = &playlistID
post.Type = PostTypeShare
}
if err := s.db.Create(post).Error; err != nil {
s.logger.Error("Failed to create post", zap.Error(err), zap.String("user_id", userID.String()))
return nil, err
}
return post, nil
}
// GetGlobalFeed récupère un flux d'activité global
func (s *Service) GetGlobalFeed(ctx context.Context, limit, offset int) ([]FeedItem, error) {
var posts []Post
if err := s.db.Order("created_at desc").Limit(limit).Offset(offset).Find(&posts).Error; err != nil {
return nil, err
}
var feed []FeedItem
for _, p := range posts {
targetType := "none"
targetID := uuid.Nil
if p.TrackID != nil {
targetType = "track"
targetID = *p.TrackID
} else if p.PlaylistID != nil {
targetType = "playlist"
targetID = *p.PlaylistID
}
item := FeedItem{
ID: fmt.Sprintf("post:%s", p.ID.String()),
Type: ActivityPost,
ActorID: p.UserID,
TargetID: targetID,
TargetType: targetType,
Content: p.Content,
CreatedAt: p.CreatedAt,
}
// Spécial pour les activités automatiques
if p.Type == PostTypeActivity {
item.Type = ActivityPurchase // Ou autre logique plus fine
}
feed = append(feed, item)
}
return feed, nil
}
// GetUserFeed récupère le flux d'un utilisateur
func (s *Service) GetUserFeed(ctx context.Context, userID uuid.UUID, limit, offset int) ([]FeedItem, error) {
var posts []Post
if err := s.db.Where("user_id = ?", userID).Order("created_at desc").Limit(limit).Offset(offset).Find(&posts).Error; err != nil {
return nil, err
}
var feed []FeedItem
for _, p := range posts {
item := FeedItem{
ID: fmt.Sprintf("post:%s", p.ID.String()),
Type: ActivityPost,
ActorID: p.UserID,
Content: p.Content,
CreatedAt: p.CreatedAt,
TargetType: "user_wall",
}
feed = append(feed, item)
}
return feed, nil
}
// ToggleLike ajoute ou supprime un like
func (s *Service) ToggleLike(ctx context.Context, userID uuid.UUID, targetID uuid.UUID, targetType string) (bool, error) {
var like Like
err := s.db.Where("user_id = ? AND target_id = ? AND target_type = ?", userID, targetID, targetType).First(&like).Error
if err == nil {
// Like existe, on le supprime (Unlike)
if err := s.db.Delete(&like).Error; err != nil {
return false, err
}
// Décrémenter le compteur si c'est un post
if targetType == "post" {
s.db.Model(&Post{}).Where("id = ?", targetID).Update("like_count", gorm.Expr("like_count - 1"))
}
return false, nil // Liked = false
} else if err == gorm.ErrRecordNotFound {
// Like n'existe pas, on le crée
like = Like{
UserID: userID,
TargetID: targetID,
TargetType: targetType,
}
if err := s.db.Create(&like).Error; err != nil {
return false, err
}
// Incrémenter le compteur si c'est un post
if targetType == "post" {
s.db.Model(&Post{}).Where("id = ?", targetID).Update("like_count", gorm.Expr("like_count + 1"))
}
return true, nil // Liked = true
} else {
return false, err
}
}
// AddComment ajoute un commentaire
func (s *Service) AddComment(ctx context.Context, userID uuid.UUID, targetID uuid.UUID, targetType string, content string) (*Comment, error) {
comment := &Comment{
UserID: userID,
TargetID: targetID,
TargetType: targetType,
Content: content,
}
if err := s.db.Create(comment).Error; err != nil {
return nil, err
}
// Incrémenter le compteur si c'est un post
if targetType == "post" {
s.db.Model(&Post{}).Where("id = ?", targetID).Update("comment_count", gorm.Expr("comment_count + 1"))
}
return comment, nil
}
// CreateActivityPost crée un post automatique pour une activité (ex: Achat)
func (s *Service) CreateActivityPost(ctx context.Context, userID uuid.UUID, content string, meta map[string]interface{}) error {
post := &Post{
UserID: userID,
Content: content,
Type: PostTypeActivity,
}
if trackIDStr, ok := meta["track_id"].(string); ok {
if trackID, err := uuid.Parse(trackIDStr); err == nil {
post.TrackID = &trackID
}
}
return s.db.Create(post).Error
}