cc3d0a8b07
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
491 lines
14 KiB
Go
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
|
|
} |