package services import ( "context" "errors" "time" "github.com/google/uuid" "veza-backend-api/internal/models" "go.uber.org/zap" "gorm.io/gorm" ) type CommentService struct { db *gorm.DB logger *zap.Logger } func NewCommentService(db *gorm.DB, logger *zap.Logger) *CommentService { return &CommentService{ db: db, logger: logger, } } // CreateComment creates a new comment on a track func (s *CommentService) CreateComment(ctx context.Context, trackID uuid.UUID, userID uuid.UUID, content string, timestamp float64, parentID *uuid.UUID) (*models.TrackComment, error) { // Updated trackID and parentID to uuid.UUID // Verify if track exists var track models.Track if err := s.db.WithContext(ctx).First(&track, "id = ?", trackID).Error; err != nil { // Updated query if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrTrackNotFound } return nil, err } // Verify if parent comment exists (if reply) if parentID != nil { var parent models.TrackComment if err := s.db.WithContext(ctx).First(&parent, "id = ?", *parentID).Error; err != nil { // Updated query if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrParentCommentNotFound } return nil, err } // Ensure parent belongs to the same track if parent.TrackID != trackID { return nil, ErrParentTrackMismatch } } comment := &models.TrackComment{ TrackID: trackID, UserID: userID, Content: content, Timestamp: timestamp, ParentID: parentID, CreatedAt: time.Now(), UpdatedAt: time.Now(), } if err := s.db.WithContext(ctx).Create(comment).Error; err != nil { s.logger.Error("Failed to create comment", zap.Error(err)) return nil, err } // Preload user info for response if err := s.db.WithContext(ctx).Preload("User").First(comment, comment.ID).Error; err != nil { return comment, nil // Return comment without user info if preload fails } s.logger.Info("Comment created", zap.Any("comment_id", comment.ID), // Changed to zap.Any for uuid.UUID zap.Any("track_id", trackID), // Changed to zap.Any for uuid.UUID zap.String("user_id", userID.String())) return comment, nil } // GetComments retrieves comments for a track func (s *CommentService) GetComments(ctx context.Context, trackID uuid.UUID, page, limit int) ([]models.TrackComment, int64, error) { // Updated trackID to uuid.UUID var comments []models.TrackComment var total int64 offset := (page - 1) * limit // Count total top-level comments (or all comments? usually top-level for pagination, replies fetched separately or nested) // Here we fetch all top-level comments query := s.db.WithContext(ctx).Model(&models.TrackComment{}).Where("track_id = ? AND parent_id IS NULL", trackID) if err := query.Count(&total).Error; err != nil { return nil, 0, err } // Fetch comments with user info and replies // Note: Deep nesting of replies might require recursive query or multiple queries. // For simplicity, we just preload direct replies or let frontend handle threading if flat list. // Assuming flat list of top level + preloaded replies? // Let's just fetch top level and preload their replies one level deep for now err := query. Preload("User"). Preload("Replies"). Preload("Replies.User"). Order("created_at DESC"). Limit(limit). Offset(offset). Find(&comments).Error if err != nil { s.logger.Error("Failed to get comments", zap.Error(err)) return nil, 0, err } return comments, total, nil } // UpdateComment updates a comment func (s *CommentService) UpdateComment(ctx context.Context, commentID uuid.UUID, userID uuid.UUID, content string) (*models.TrackComment, error) { // Updated commentID to uuid.UUID var comment models.TrackComment if err := s.db.WithContext(ctx).First(&comment, "id = ?", commentID).Error; err != nil { // Updated query if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrCommentNotFound } return nil, err } // Check permission if comment.UserID != userID { return nil, ErrForbidden } comment.Content = content comment.IsEdited = true comment.UpdatedAt = time.Now() if err := s.db.WithContext(ctx).Save(&comment).Error; err != nil { s.logger.Error("Failed to update comment", zap.Error(err)) return nil, err } s.logger.Info("Comment updated", zap.Any("comment_id", comment.ID), // Changed to zap.Any for uuid.UUID zap.String("user_id", userID.String())) return &comment, nil } // GetReplies retrieves replies for a given parent comment ID func (s *CommentService) GetReplies(ctx context.Context, parentID uuid.UUID, page, limit int) ([]models.TrackComment, int64, error) { // Updated parentID to uuid.UUID var replies []models.TrackComment var total int64 offset := (page - 1) * limit // Verify if parent comment exists var parent models.TrackComment if err := s.db.WithContext(ctx).First(&parent, "id = ?", parentID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, 0, ErrParentCommentNotFound } return nil, 0, err } // Count total replies query := s.db.WithContext(ctx).Model(&models.TrackComment{}).Where("parent_id = ?", parentID) if err := query.Count(&total).Error; err != nil { return nil, 0, err } // Fetch replies with user info err := query. Preload("User"). Order("created_at ASC"). // Order by oldest first Limit(limit). Offset(offset). Find(&replies).Error if err != nil { s.logger.Error("Failed to get replies", zap.Error(err)) return nil, 0, err } return replies, total, nil } // DeleteComment deletes a comment // MIGRATION UUID: userID migré vers uuid.UUID, commentID reste int64 func (s *CommentService) DeleteComment(ctx context.Context, commentID uuid.UUID, userID uuid.UUID, isAdmin bool) error { // Updated commentID to uuid.UUID var comment models.TrackComment if err := s.db.WithContext(ctx).First(&comment, "id = ?", commentID).Error; err != nil { // Updated query if errors.Is(err, gorm.ErrRecordNotFound) { return ErrCommentNotFound } return err } // Check permission if comment.UserID != userID && !isAdmin { return ErrForbidden } // Soft delete or hard delete? Model has DeletedAt so soft delete if err := s.db.WithContext(ctx).Delete(&comment).Error; err != nil { s.logger.Error("Failed to delete comment", zap.Error(err)) return err } return nil }