Files
tp/main_dc/yalarba/api_yal/internal/domain/comment/service.go
T
valitovgaziz cc3d0a8b07 On branch main
modified:   yalarba/api_yal/internal/domain/account/service.go
	modified:   yalarba/api_yal/internal/domain/comment/dto.go
	new file:   yalarba/api_yal/internal/domain/comment/handler.go
	new file:   yalarba/api_yal/internal/domain/comment/router.go
	new file:   yalarba/api_yal/internal/domain/comment/service.go
	modified:   yalarba/api_yal/internal/repository/feedback_repository.go
	new file:   yalarba/api_yal/internal/util/JSON_resp.go
Realize comment domain hole
2026-05-19 18:11:20 +05:00

491 lines
14 KiB
Go

package comment
import (
"api_yal/internal/logger"
"api_yal/internal/models"
"api_yal/internal/repository"
"errors"
"fmt"
"gorm.io/gorm"
"go.uber.org/zap"
)
type CommentService interface {
CreateComment(userID uint, req *CreateCommentRequest) (*CommentResponse, error)
GetCommentByID(id uint) (*CommentResponse, error)
UpdateComment(id uint, userID uint, req *UpdateCommentRequest) (*CommentResponse, error)
DeleteComment(id uint, userID uint, isAdmin bool) error
ListComments(req *ListCommentsRequest) ([]CommentResponse, int64, error)
GetCommentsByFeedback(feedbackID uint, req *ListCommentsRequest) ([]CommentResponse, int64, error)
GetCommentsByAuthor(authorID uint, req *ListCommentsRequest) ([]CommentResponse, int64, error)
GetReplies(parentID uint, req *ListCommentsRequest) ([]CommentResponse, int64, error)
VerifyComment(id uint, verified bool) error
GetCommentStats() (map[string]interface{}, error)
}
type CommentServiceImpl struct {
commentRepo repository.CommentRepository
db *gorm.DB
logger *zap.Logger
}
func NewCommentServiceImpl(commentRepo repository.CommentRepository, db *gorm.DB) *CommentServiceImpl {
return &CommentServiceImpl{
commentRepo: commentRepo,
db: db,
logger: logger.Get(),
}
}
func (s *CommentServiceImpl) CreateComment(userID uint, req *CreateCommentRequest) (*CommentResponse, error) {
s.logger.Info("Creating new comment",
zap.Uint("user_id", userID),
zap.Uint("feedback_id", req.FeedbackID),
zap.Uint("parent_id", *req.ParentID),
)
comment := &models.Comment{
AuthorID: userID,
FeedbackID: req.FeedbackID,
Text: req.Text,
ParentID: req.ParentID,
IsVerified: false, // можно настроить автоматическую верификацию
}
err := s.commentRepo.Create(comment)
if err != nil {
s.logger.Error("Failed to create comment",
zap.Error(err),
zap.Uint("user_id", userID),
)
return nil, fmt.Errorf("failed to create comment: %w", err)
}
// Загружаем полную информацию о комментарии
createdComment, err := s.commentRepo.GetByID(comment.ID)
if err != nil {
s.logger.Error("Failed to get created comment",
zap.Error(err),
zap.Uint("comment_id", comment.ID),
)
return nil, fmt.Errorf("failed to get created comment: %w", err)
}
return s.mapToResponse(createdComment), nil
}
func (s *CommentServiceImpl) GetCommentByID(id uint) (*CommentResponse, error) {
s.logger.Info("Getting comment by ID",
zap.Uint("comment_id", id),
)
comment, err := s.commentRepo.GetByID(id)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("comment not found")
}
s.logger.Error("Failed to get comment",
zap.Error(err),
zap.Uint("comment_id", id),
)
return nil, fmt.Errorf("failed to get comment: %w", err)
}
return s.mapToResponse(comment), nil
}
func (s *CommentServiceImpl) UpdateComment(id uint, userID uint, req *UpdateCommentRequest) (*CommentResponse, error) {
s.logger.Info("Updating comment",
zap.Uint("comment_id", id),
zap.Uint("user_id", userID),
)
comment, err := s.commentRepo.GetByID(id)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("comment not found")
}
s.logger.Error("Failed to get comment for update",
zap.Error(err),
zap.Uint("comment_id", id),
)
return nil, fmt.Errorf("failed to get comment: %w", err)
}
// Проверяем права: только автор может редактировать
if comment.AuthorID != userID {
s.logger.Warn("Unauthorized update attempt",
zap.Uint("comment_id", id),
zap.Uint("author_id", comment.AuthorID),
zap.Uint("user_id", userID),
)
return nil, errors.New("you can only edit your own comments")
}
comment.Text = req.Text
err = s.commentRepo.Update(comment)
if err != nil {
s.logger.Error("Failed to update comment",
zap.Error(err),
zap.Uint("comment_id", id),
)
return nil, fmt.Errorf("failed to update comment: %w", err)
}
// Отмечаем как отредактированный
err = s.commentRepo.MarkAsEdited(id)
if err != nil {
s.logger.Warn("Failed to mark comment as edited",
zap.Error(err),
zap.Uint("comment_id", id),
)
}
updatedComment, err := s.commentRepo.GetByID(id)
if err != nil {
s.logger.Error("Failed to get updated comment",
zap.Error(err),
zap.Uint("comment_id", id),
)
return nil, fmt.Errorf("failed to get updated comment: %w", err)
}
return s.mapToResponse(updatedComment), nil
}
func (s *CommentServiceImpl) DeleteComment(id uint, userID uint, isAdmin bool) error {
s.logger.Info("Deleting comment",
zap.Uint("comment_id", id),
zap.Uint("user_id", userID),
zap.Bool("is_admin", isAdmin),
)
comment, err := s.commentRepo.GetByID(id)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("comment not found")
}
s.logger.Error("Failed to get comment for deletion",
zap.Error(err),
zap.Uint("comment_id", id),
)
return fmt.Errorf("failed to get comment: %w", err)
}
// Проверяем права: автор или администратор
if !isAdmin && comment.AuthorID != userID {
s.logger.Warn("Unauthorized delete attempt",
zap.Uint("comment_id", id),
zap.Uint("author_id", comment.AuthorID),
zap.Uint("user_id", userID),
zap.Bool("is_admin", isAdmin),
)
return errors.New("you can only delete your own comments")
}
err = s.commentRepo.Delete(id)
if err != nil {
s.logger.Error("Failed to delete comment",
zap.Error(err),
zap.Uint("comment_id", id),
)
return fmt.Errorf("failed to delete comment: %w", err)
}
return nil
}
func (s *CommentServiceImpl) ListComments(req *ListCommentsRequest) ([]CommentResponse, int64, error) {
s.logger.Info("Listing comments",
zap.Int("page", req.Page),
zap.Int("page_size", req.PageSize),
)
offset := (req.Page - 1) * req.PageSize
limit := req.PageSize
// Базовый запрос для подсчета
query := s.db.Model(&models.Comment{})
// Применяем фильтры
if req.FeedbackID != nil {
query = query.Where("feedback_id = ?", *req.FeedbackID)
}
if req.AuthorID != nil {
query = query.Where("author_id = ?", *req.AuthorID)
}
if req.ParentID != nil {
if *req.ParentID == 0 {
query = query.Where("parent_id IS NULL")
} else {
query = query.Where("parent_id = ?", *req.ParentID)
}
}
if req.Verified != nil {
query = query.Where("is_verified = ?", *req.Verified)
}
// Подсчитываем общее количество
var total int64
err := query.Count(&total).Error
if err != nil {
s.logger.Error("Failed to count comments",
zap.Error(err),
)
return nil, 0, fmt.Errorf("failed to count comments: %w", err)
}
// Получаем комментарии
comments, err := s.commentRepo.List(offset, limit)
if err != nil {
s.logger.Error("Failed to list comments",
zap.Error(err),
)
return nil, 0, fmt.Errorf("failed to list comments: %w", err)
}
// Фильтруем и маппим результаты
var responses []CommentResponse
for i := range comments {
// Применяем фильтры в памяти (так как репозиторий возвращает все)
if s.shouldIncludeComment(&comments[i], req) {
responses = append(responses, *s.mapToResponse(&comments[i]))
}
}
return responses, total, nil
}
func (s *CommentServiceImpl) GetCommentsByFeedback(feedbackID uint, req *ListCommentsRequest) ([]CommentResponse, int64, error) {
s.logger.Info("Getting comments by feedback",
zap.Uint("feedback_id", feedbackID),
zap.Int("page", req.Page),
zap.Int("page_size", req.PageSize),
)
offset := (req.Page - 1) * req.PageSize
limit := req.PageSize
comments, err := s.commentRepo.ListByFeedback(feedbackID, offset, limit)
if err != nil {
s.logger.Error("Failed to get comments by feedback",
zap.Error(err),
zap.Uint("feedback_id", feedbackID),
)
return nil, 0, fmt.Errorf("failed to get comments by feedback: %w", err)
}
// Подсчитываем общее количество
var total int64
err = s.db.Model(&models.Comment{}).Where("feedback_id = ?", feedbackID).Count(&total).Error
if err != nil {
s.logger.Error("Failed to count comments by feedback",
zap.Error(err),
zap.Uint("feedback_id", feedbackID),
)
return nil, 0, fmt.Errorf("failed to count comments: %w", err)
}
responses := make([]CommentResponse, len(comments))
for i := range comments {
responses[i] = *s.mapToResponse(&comments[i])
}
return responses, total, nil
}
func (s *CommentServiceImpl) GetCommentsByAuthor(authorID uint, req *ListCommentsRequest) ([]CommentResponse, int64, error) {
s.logger.Info("Getting comments by author",
zap.Uint("author_id", authorID),
zap.Int("page", req.Page),
zap.Int("page_size", req.PageSize),
)
offset := (req.Page - 1) * req.PageSize
limit := req.PageSize
comments, err := s.commentRepo.ListByAuthor(authorID, offset, limit)
if err != nil {
s.logger.Error("Failed to get comments by author",
zap.Error(err),
zap.Uint("author_id", authorID),
)
return nil, 0, fmt.Errorf("failed to get comments by author: %w", err)
}
// Подсчитываем общее количество
var total int64
err = s.db.Model(&models.Comment{}).Where("author_id = ?", authorID).Count(&total).Error
if err != nil {
s.logger.Error("Failed to count comments by author",
zap.Error(err),
zap.Uint("author_id", authorID),
)
return nil, 0, fmt.Errorf("failed to count comments: %w", err)
}
responses := make([]CommentResponse, len(comments))
for i := range comments {
responses[i] = *s.mapToResponse(&comments[i])
}
return responses, total, nil
}
func (s *CommentServiceImpl) GetReplies(parentID uint, req *ListCommentsRequest) ([]CommentResponse, int64, error) {
s.logger.Info("Getting replies for comment",
zap.Uint("parent_id", parentID),
zap.Int("page", req.Page),
zap.Int("page_size", req.PageSize),
)
offset := (req.Page - 1) * req.PageSize
limit := req.PageSize
replies, err := s.commentRepo.ListReplies(parentID, offset, limit)
if err != nil {
s.logger.Error("Failed to get replies",
zap.Error(err),
zap.Uint("parent_id", parentID),
)
return nil, 0, fmt.Errorf("failed to get replies: %w", err)
}
// Подсчитываем общее количество
total, err := s.commentRepo.GetRepliesCount(parentID)
if err != nil {
s.logger.Error("Failed to count replies",
zap.Error(err),
zap.Uint("parent_id", parentID),
)
return nil, 0, fmt.Errorf("failed to count replies: %w", err)
}
responses := make([]CommentResponse, len(replies))
for i := range replies {
responses[i] = *s.mapToResponse(&replies[i])
}
return responses, total, nil
}
func (s *CommentServiceImpl) VerifyComment(id uint, verified bool) error {
s.logger.Info("Verifying comment",
zap.Uint("comment_id", id),
zap.Bool("verified", verified),
)
err := s.commentRepo.ToggleVerification(id, verified)
if err != nil {
s.logger.Error("Failed to verify comment",
zap.Error(err),
zap.Uint("comment_id", id),
)
return fmt.Errorf("failed to verify comment: %w", err)
}
return nil
}
func (s *CommentServiceImpl) GetCommentStats() (map[string]interface{}, error) {
s.logger.Info("Getting comment statistics")
var total int64
err := s.db.Model(&models.Comment{}).Count(&total).Error
if err != nil {
s.logger.Error("Failed to count total comments",
zap.Error(err),
)
return nil, fmt.Errorf("failed to count total comments: %w", err)
}
var verified int64
err = s.db.Model(&models.Comment{}).Where("is_verified = ?", true).Count(&verified).Error
if err != nil {
s.logger.Error("Failed to count verified comments",
zap.Error(err),
)
return nil, fmt.Errorf("failed to count verified comments: %w", err)
}
var withReplies int64
err = s.db.Model(&models.Comment{}).Where("id IN (SELECT DISTINCT parent_id FROM comments WHERE parent_id IS NOT NULL)").Count(&withReplies).Error
if err != nil {
s.logger.Error("Failed to count comments with replies",
zap.Error(err),
)
return nil, fmt.Errorf("failed to count comments with replies: %w", err)
}
stats := map[string]interface{}{
"total_comments": total,
"verified_comments": verified,
"unverified_comments": total - verified,
"comments_with_replies": withReplies,
}
return stats, nil
}
// Вспомогательные методы
func (s *CommentServiceImpl) mapToResponse(comment *models.Comment) *CommentResponse {
response := &CommentResponse{
ID: comment.ID,
CreatedAt: comment.CreatedAt,
UpdatedAt: comment.UpdatedAt,
AuthorID: comment.AuthorID,
AuthorName: comment.Author.FullName,
FeedbackID: comment.FeedbackID,
Text: comment.Text,
IsEdited: comment.IsEdited,
IsVerified: comment.IsVerified,
}
if comment.ParentID != nil {
response.ParentID = comment.ParentID
}
// Добавляем количество ответов, если нужно
if len(comment.Replies) > 0 {
response.RepliesCount = int64(len(comment.Replies))
response.Replies = make([]CommentShortResponse, len(comment.Replies))
for i, reply := range comment.Replies {
response.Replies[i] = CommentShortResponse{
ID: reply.ID,
CreatedAt: reply.CreatedAt,
AuthorID: reply.AuthorID,
AuthorName: reply.Author.FullName,
Text: reply.Text,
IsEdited: reply.IsEdited,
}
}
} else {
// Подсчитываем количество ответов, если они не были загружены
count, _ := s.commentRepo.GetRepliesCount(comment.ID)
response.RepliesCount = count
}
return response
}
func (s *CommentServiceImpl) shouldIncludeComment(comment *models.Comment, req *ListCommentsRequest) bool {
if req.FeedbackID != nil && comment.FeedbackID != *req.FeedbackID {
return false
}
if req.AuthorID != nil && comment.AuthorID != *req.AuthorID {
return false
}
if req.ParentID != nil {
if *req.ParentID == 0 && comment.ParentID != nil {
return false
}
if *req.ParentID != 0 && (comment.ParentID == nil || *comment.ParentID != *req.ParentID) {
return false
}
}
if req.Verified != nil && comment.IsVerified != *req.Verified {
return false
}
return true
}