package services import ( "context" "database/sql" "fmt" "github.com/google/uuid" "veza-backend-api/internal/database" "go.uber.org/zap" ) // SocialService handles social features (follows, likes, comments) type SocialService struct { db *database.Database logger *zap.Logger } // Comment represents a comment on a track type Comment struct { ID uuid.UUID `json:"id" db:"id"` UserID uuid.UUID `json:"user_id" db:"user_id"` TrackID uuid.UUID `json:"track_id" db:"track_id"` ParentID *uuid.UUID `json:"parent_id" db:"parent_id"` Content string `json:"content" db:"content"` CreatedAt string `json:"created_at" db:"created_at"` UpdatedAt string `json:"updated_at" db:"updated_at"` } // NewSocialService creates a new social service func NewSocialService(db *database.Database, logger *zap.Logger) *SocialService { return &SocialService{ db: db, logger: logger, } } // FollowUser creates a follow relationship func (ss *SocialService) FollowUser(followerID, followedID uuid.UUID) error { ctx := context.Background() _, err := ss.db.ExecContext(ctx, ` INSERT INTO follows (follower_id, followed_id) VALUES ($1, $2) ON CONFLICT (follower_id, followed_id) DO NOTHING `, followerID, followedID) if err != nil { return fmt.Errorf("failed to follow user: %w", err) } ss.logger.Info("User followed", zap.String("follower_id", followerID.String()), zap.String("followed_id", followedID.String()), ) return nil } // UnfollowUser removes a follow relationship func (ss *SocialService) UnfollowUser(followerID, followedID uuid.UUID) error { ctx := context.Background() _, err := ss.db.ExecContext(ctx, ` DELETE FROM follows WHERE follower_id = $1 AND followed_id = $2 `, followerID, followedID) if err != nil { return fmt.Errorf("failed to unfollow user: %w", err) } return nil } // LikeTrack creates a like on a track func (ss *SocialService) LikeTrack(userID, trackID uuid.UUID) error { ctx := context.Background() _, err := ss.db.ExecContext(ctx, ` INSERT INTO likes (user_id, track_id) VALUES ($1, $2) ON CONFLICT (user_id, track_id) DO NOTHING `, userID, trackID) if err != nil { return fmt.Errorf("failed to like track: %w", err) } return nil } // UnlikeTrack removes a like from a track func (ss *SocialService) UnlikeTrack(userID, trackID uuid.UUID) error { ctx := context.Background() _, err := ss.db.ExecContext(ctx, ` DELETE FROM likes WHERE user_id = $1 AND track_id = $2 `, userID, trackID) if err != nil { return fmt.Errorf("failed to unlike track: %w", err) } return nil } // CreateComment creates a comment on a track func (ss *SocialService) CreateComment(userID, trackID uuid.UUID, content string, parentID *uuid.UUID) (*Comment, error) { ctx := context.Background() var commentID uuid.UUID err := ss.db.QueryRowContext(ctx, ` INSERT INTO comments (id, user_id, track_id, parent_id, content) VALUES (gen_random_uuid(), $1, $2, $3, $4) RETURNING id `, userID, trackID, parentID, content).Scan(&commentID) if err != nil { return nil, fmt.Errorf("failed to create comment: %w", err) } // Fetch and return the created comment var comment Comment err = ss.db.QueryRowContext(ctx, ` SELECT id, user_id, track_id, parent_id, content, created_at, updated_at FROM comments WHERE id = $1 `, commentID).Scan( &comment.ID, &comment.UserID, &comment.TrackID, &comment.ParentID, &comment.Content, &comment.CreatedAt, &comment.UpdatedAt, ) if err != nil { return nil, fmt.Errorf("failed to fetch comment: %w", err) } return &comment, nil } // GetFollowersCount returns the number of followers for a user func (ss *SocialService) GetFollowersCount(userID uuid.UUID) (int, error) { ctx := context.Background() var count int err := ss.db.QueryRowContext(ctx, ` SELECT COUNT(*) FROM follows WHERE followed_id = $1 `, userID).Scan(&count) if err != nil { return 0, fmt.Errorf("failed to get followers count: %w", err) } return count, nil } // GetFollowingCount returns the number of users being followed func (ss *SocialService) GetFollowingCount(userID uuid.UUID) (int, error) { ctx := context.Background() var count int err := ss.db.QueryRowContext(ctx, ` SELECT COUNT(*) FROM follows WHERE follower_id = $1 `, userID).Scan(&count) if err != nil { return 0, fmt.Errorf("failed to get following count: %w", err) } return count, nil } // GetLikesCount returns the number of likes for a track func (ss *SocialService) GetLikesCount(trackID uuid.UUID) (int, error) { ctx := context.Background() var count int err := ss.db.QueryRowContext(ctx, ` SELECT COUNT(*) FROM likes WHERE track_id = $1 `, trackID).Scan(&count) if err != nil { return 0, fmt.Errorf("failed to get likes count: %w", err) } return count, nil } // IsFollowing checks if a user is following another user func (ss *SocialService) IsFollowing(followerID, followedID uuid.UUID) (bool, error) { ctx := context.Background() var exists bool err := ss.db.QueryRowContext(ctx, ` SELECT EXISTS( SELECT 1 FROM follows WHERE follower_id = $1 AND followed_id = $2 ) `, followerID, followedID).Scan(&exists) if err != nil { if err == sql.ErrNoRows { return false, nil } return false, fmt.Errorf("failed to check follow status: %w", err) } return exists, nil } // IsTrackLiked checks if a user has liked a track func (ss *SocialService) IsTrackLiked(userID, trackID uuid.UUID) (bool, error) { ctx := context.Background() var exists bool err := ss.db.QueryRowContext(ctx, ` SELECT EXISTS( SELECT 1 FROM likes WHERE user_id = $1 AND track_id = $2 ) `, userID, trackID).Scan(&exists) if err != nil { if err == sql.ErrNoRows { return false, nil } return false, fmt.Errorf("failed to check like status: %w", err) } return exists, nil }