From 894415e3ac35dc9ebe078d584c019911c6d65cba Mon Sep 17 00:00:00 2001 From: valitovgaziz Date: Tue, 19 May 2026 14:07:06 +0500 Subject: [PATCH] On branch main modified: main_dc/yalarba/api_yal/internal/domain/feetback/dto.go modified: main_dc/yalarba/api_yal/internal/domain/feetback/service.go last --- .../api_yal/internal/domain/feetback/dto.go | 85 ++- .../internal/domain/feetback/service.go | 534 ++++++++++++------ 2 files changed, 454 insertions(+), 165 deletions(-) diff --git a/main_dc/yalarba/api_yal/internal/domain/feetback/dto.go b/main_dc/yalarba/api_yal/internal/domain/feetback/dto.go index 3b92de0..3d6b07f 100644 --- a/main_dc/yalarba/api_yal/internal/domain/feetback/dto.go +++ b/main_dc/yalarba/api_yal/internal/domain/feetback/dto.go @@ -1,5 +1,88 @@ package feetback import ( + "api_yal/internal/domain/account" + "api_yal/internal/domain/comment" + "api_yal/internal/domain/object" + "api_yal/internal/models" + "time" +) -) \ No newline at end of file +// CreateFeedbackRequest - DTO для создания отзыва +// Обязательные поля: ObjectID, Platform, Score, Text +// Score должен быть от 1 до 5 +// Platform должен быть одним из допустимых значений (entrepreneur, tourist) +type CreateFeedbackRequest struct { + ObjectID uint `json:"object_id" binding:"required"` + Platform models.PlatformType `json:"platform" binding:"required,oneof=entrepreneur tourist"` + Score int `json:"score" binding:"required,min=1,max=5"` + Text string `json:"text" binding:"required"` +} + +// UpdateFeedbackRequest - DTO для обновления отзыва +// Все поля опциональны, позволяют обновлять только указанные данные +type UpdateFeedbackRequest struct { + Score *int `json:"score" binding:"omitempty,min=1,max=5"` + Text *string `json:"text"` +} + +// FeedbackResponse - DTO для полного ответа с отзывом +// Включает всю информацию о отзыве, включая связанные данные +// Owner и Object могут быть nil, если предзагрузка не была выполнена +type FeedbackResponse struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt *time.Time `json:"deleted_at,omitempty"` + OwnerID uint `json:"owner_id"` + Owner *account.AccountResponse `json:"owner,omitempty"` + ObjectID uint `json:"object_id"` + Object *object.ObjectShortResponse `json:"object,omitempty"` + Platform models.PlatformType `json:"platform"` + Score int `json:"score"` + Text string `json:"text"` + CommentCount int `json:"comment_count"` + Comments []comment.CommentShortResponse `json:"comments,omitempty"` +} + +// FeedbackShortResponse - DTO для краткого ответа (вложенный в объект) +// Используется при возврате отзывов в составе других объектов +// OwnerName берется из Account.OwnerName или формируется из имени пользователя +type FeedbackShortResponse struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"created_at"` + OwnerID uint `json:"owner_id"` + OwnerName string `json:"owner_name,omitempty"` + Platform models.PlatformType `json:"platform"` + Score int `json:"score"` + Text string `json:"text"` +} + +// FeedbackListResponse - DTO для списка отзывов с пагинацией +// Структура для возврата списка отзывов с метаданными пагинации +type FeedbackListResponse struct { + Items []FeedbackShortResponse `json:"items"` + Total int64 `json:"total"` + Page int `json:"page"` + PageSize int `json:"page_size"` + TotalPages int `json:"total_pages"` +} + +// SearchFeedbacksRequest - DTO для поиска отзывов +// Используется для параметров поиска +// Запрос должен быть не пустым +type SearchFeedbacksRequest struct { + Query string `json:"q" binding:"required"` + Page int `json:"page"` + PageSize int `json:"page_size"` +} + +// FeedbackStatsResponse - DTO для статистики по отзывам +// Используется для агрегированной информации +// AverageScore может быть 0, если нет отзывов +type FeedbackStatsResponse struct { + TotalCount int `json:"total_count"` + AverageScore float64 `json:"average_score"` + PlatformStats map[models.PlatformType]int `json:"platform_stats"` + ScoreDistribution map[int]int `json:"score_distribution"` // распределение по баллам (1-5) +} diff --git a/main_dc/yalarba/api_yal/internal/domain/feetback/service.go b/main_dc/yalarba/api_yal/internal/domain/feetback/service.go index a4b92b5..9adfe49 100644 --- a/main_dc/yalarba/api_yal/internal/domain/feetback/service.go +++ b/main_dc/yalarba/api_yal/internal/domain/feetback/service.go @@ -13,21 +13,33 @@ import ( ) type FeedbackService interface { - Create(ctx context.Context, feedback *models.Feedback) error - GetByID(ctx context.Context, id uint) (*models.Feedback, error) - Update(ctx context.Context, feedback *models.Feedback) error + // Create создает новый отзыв + Create(ctx context.Context, req *CreateFeedbackRequest) (*FeedbackResponse, error) + // GetByID возвращает отзыв по ID + GetByID(ctx context.Context, id uint) (*FeedbackResponse, error) + // Update обновляет существующий отзыв + Update(ctx context.Context, id uint, req *UpdateFeedbackRequest) (*FeedbackResponse, error) + // Delete удаляет отзыв Delete(ctx context.Context, id uint) error - List(ctx context.Context, page, pageSize int) ([]models.Feedback, int64, error) - ListByOwner(ctx context.Context, ownerID uint, page, pageSize int) ([]models.Feedback, error) - ListByObject(ctx context.Context, objectID uint, page, pageSize int) ([]models.Feedback, error) - ListByPlatform(ctx context.Context, platform models.PlatformType, page, pageSize int) ([]models.Feedback, error) - Search(ctx context.Context, query string, page, pageSize int) ([]models.Feedback, error) + // List возвращает список отзывов с пагинацией + List(ctx context.Context, page, pageSize int) (*FeedbackListResponse, error) + // ListByOwner возвращает отзывы по владельцу + ListByOwner(ctx context.Context, ownerID uint, page, pageSize int) (*FeedbackListResponse, error) + // ListByObject возвращает отзывы по объекту + ListByObject(ctx context.Context, objectID uint, page, pageSize int) (*FeedbackListResponse, error) + // ListByPlatform возвращает отзывы по платформе + ListByPlatform(ctx context.Context, platform models.PlatformType, page, pageSize int) (*FeedbackListResponse, error) + // Search ищет отзывы по тексту + Search(ctx context.Context, query string, page, pageSize int) (*FeedbackListResponse, error) // Comment methods - AddComment(ctx context.Context, feedbackID uint, comment *models.Comment) error + AddComment(ctx context.Context, feedbackID uint, comment *models.Comment) (*models.Comment, error) GetComments(ctx context.Context, feedbackID uint, page, pageSize int) ([]models.Comment, error) UpdateComment(ctx context.Context, commentID uint, text string) error DeleteComment(ctx context.Context, commentID uint) error + + // Stats возвращает статистику по отзывам + GetStats(ctx context.Context) (*FeedbackStatsResponse, error) } type feedbackServiceImpl struct { @@ -41,10 +53,49 @@ func NewFeedbackServiceImpl(feedbackRepository repository.FeedbackRepository) Fe } // Create создает новый отзыв -func (s *feedbackServiceImpl) Create(ctx context.Context, feedback *models.Feedback) error { +func (s *feedbackServiceImpl) Create(ctx context.Context, req *CreateFeedbackRequest) (*FeedbackResponse, error) { // Валидация входных данных - if err := s.validateFeedback(feedback); err != nil { - return fmt.Errorf("validation failed: %w", err) + if req == nil { + return nil, errors.New("request cannot be nil") + } + + // Валидация обязательных полей + if req.ObjectID == 0 { + return nil, errors.New("object ID is required") + } + + if req.Score < 1 || req.Score > 5 { + return nil, errors.New("score must be between 1 and 5") + } + + if strings.TrimSpace(req.Text) == "" { + return nil, errors.New("feedback text cannot be empty") + } + + if len(req.Text) > 5000 { + return nil, errors.New("feedback text cannot exceed 5000 characters") + } + + if !s.isValidPlatform(req.Platform) { + return nil, fmt.Errorf("invalid platform: %s", req.Platform) + } + + // Получаем объект для проверки его существования + object, err := s.feedbackRepository.GetObject(req.ObjectID) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("object with ID %d not found", req.ObjectID) + } + return nil, fmt.Errorf("failed to get object: %w", err) + } + + // Создаем отзыв + feedback := &models.Feedback{ + OwnerID: ctx.Value("userID").(uint), // Получаем из контекста + ObjectID: req.ObjectID, + Platform: req.Platform, + Score: req.Score, + Text: req.Text, } // Устанавливаем начальное значение счетчика комментариев @@ -52,14 +103,28 @@ func (s *feedbackServiceImpl) Create(ctx context.Context, feedback *models.Feedb // Создаем отзыв if err := s.feedbackRepository.Create(feedback); err != nil { - return fmt.Errorf("failed to create feedback: %w", err) + return nil, fmt.Errorf("failed to create feedback: %w", err) } - return nil + // Формируем ответ + response := &FeedbackResponse{ + ID: feedback.ID, + CreatedAt: feedback.CreatedAt, + UpdatedAt: feedback.UpdatedAt, + DeletedAt: feedback.DeletedAt, + OwnerID: feedback.OwnerID, + ObjectID: feedback.ObjectID, + Platform: feedback.Platform, + Score: feedback.Score, + Text: feedback.Text, + CommentCount: feedback.CommentCount, + } + + return response, nil } // GetByID возвращает отзыв по ID -func (s *feedbackServiceImpl) GetByID(ctx context.Context, id uint) (*models.Feedback, error) { +func (s *feedbackServiceImpl) GetByID(ctx context.Context, id uint) (*FeedbackResponse, error) { if id == 0 { return nil, errors.New("invalid feedback ID") } @@ -72,34 +137,108 @@ func (s *feedbackServiceImpl) GetByID(ctx context.Context, id uint) (*models.Fee return nil, fmt.Errorf("failed to get feedback: %w", err) } - return feedback, nil + // Формируем ответ + response := &FeedbackResponse{ + ID: feedback.ID, + CreatedAt: feedback.CreatedAt, + UpdatedAt: feedback.UpdatedAt, + DeletedAt: feedback.DeletedAt, + OwnerID: feedback.OwnerID, + ObjectID: feedback.ObjectID, + Platform: feedback.Platform, + Score: feedback.Score, + Text: feedback.Text, + CommentCount: feedback.CommentCount, + } + + // Добавляем связанные данные, если они загружены + if feedback.Owner != nil { + response.Owner = &account.AccountResponse{ + ID: feedback.Owner.ID, + Username: feedback.Owner.Username, + Email: feedback.Owner.Email, + } + } + + if feedback.Object != nil { + response.Object = &object.ObjectShortResponse{ + ID: feedback.Object.ID, + ShortName: feedback.Object.ShortName, + LongName: feedback.Object.LongName, + Type: feedback.Object.Type, + Address: feedback.Object.Address, + IsActive: feedback.Object.IsActive, + IsVerified: feedback.Object.IsVerified, + FeedbackCount: feedback.Object.FeedbackCount, + } + } + + return response, nil } // Update обновляет существующий отзыв -func (s *feedbackServiceImpl) Update(ctx context.Context, feedback *models.Feedback) error { +func (s *feedbackServiceImpl) Update(ctx context.Context, id uint, req *UpdateFeedbackRequest) (*FeedbackResponse, error) { + if id == 0 { + return nil, errors.New("invalid feedback ID") + } + + if req == nil { + return nil, errors.New("request cannot be nil") + } + // Проверяем существование отзыва - existing, err := s.feedbackRepository.GetByID(feedback.ID) + existing, err := s.feedbackRepository.GetByID(id) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return fmt.Errorf("feedback with ID %d not found", feedback.ID) + return nil, fmt.Errorf("feedback with ID %d not found", id) } - return fmt.Errorf("failed to get feedback: %w", err) + return nil, fmt.Errorf("failed to get feedback: %w", err) } - // Валидация обновленных данных - if err := s.validateFeedback(feedback); err != nil { - return fmt.Errorf("validation failed: %w", err) + // Проверяем, что текущий пользователь является владельцем + userID := ctx.Value("userID").(uint) + if existing.OwnerID != userID { + return nil, errors.New("unauthorized: cannot update feedback owned by another user") } - // Сохраняем оригинальный счетчик комментариев - feedback.CommentCount = existing.CommentCount + // Обновляем поля, если они указаны + if req.Score != nil { + if *req.Score < 1 || *req.Score > 5 { + return nil, errors.New("score must be between 1 and 5") + } + existing.Score = *req.Score + } + + if req.Text != nil { + if strings.TrimSpace(*req.Text) == "" { + return nil, errors.New("feedback text cannot be empty") + } + if len(*req.Text) > 5000 { + return nil, errors.New("feedback text cannot exceed 5000 characters") + } + existing.Text = *req.Text + } // Обновляем отзыв - if err := s.feedbackRepository.Update(feedback); err != nil { - return fmt.Errorf("failed to update feedback: %w", err) + if err := s.feedbackRepository.Update(existing); err != nil { + return nil, fmt.Errorf("failed to update feedback: %w", err) } - return nil + // Формируем ответ + response := &FeedbackResponse{ + ID: existing.ID, + CreatedAt: existing.CreatedAt, + UpdatedAt: existing.UpdatedAt, + DeletedAt: existing.DeletedAt, + OwnerID: existing.OwnerID, + ObjectID: existing.ObjectID, + Platform: existing.Platform, + Score: existing.Score, + Text: existing.Text, + CommentCount: existing.CommentCount, + } + + return response, nil } // Delete удаляет отзыв @@ -109,7 +248,7 @@ func (s *feedbackServiceImpl) Delete(ctx context.Context, id uint) error { } // Проверяем существование отзыва - _, err := s.feedbackRepository.GetByID(id) + existing, err := s.feedbackRepository.GetByID(id) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return fmt.Errorf("feedback with ID %d not found", id) @@ -117,6 +256,12 @@ func (s *feedbackServiceImpl) Delete(ctx context.Context, id uint) error { return fmt.Errorf("failed to get feedback: %w", err) } + // Проверяем, что текущий пользователь является владельцем + userID := ctx.Value("userID").(uint) + if existing.OwnerID != userID { + return errors.New("unauthorized: cannot delete feedback owned by another user") + } + // Удаляем отзыв if err := s.feedbackRepository.Delete(id); err != nil { return fmt.Errorf("failed to delete feedback: %w", err) @@ -126,27 +271,54 @@ func (s *feedbackServiceImpl) Delete(ctx context.Context, id uint) error { } // List возвращает список отзывов с пагинацией -func (s *feedbackServiceImpl) List(ctx context.Context, page, pageSize int) ([]models.Feedback, int64, error) { +func (s *feedbackServiceImpl) List(ctx context.Context, page, pageSize int) (*FeedbackListResponse, error) { // Нормализация параметров пагинации offset, limit := s.normalizePagination(page, pageSize) // Получаем список отзывов feedbacks, err := s.feedbackRepository.List(offset, limit) if err != nil { - return nil, 0, fmt.Errorf("failed to list feedbacks: %w", err) + return nil, fmt.Errorf("failed to list feedbacks: %w", err) } // Получаем общее количество total, err := s.feedbackRepository.Count() if err != nil { - return nil, 0, fmt.Errorf("failed to count feedbacks: %w", err) + return nil, fmt.Errorf("failed to count feedbacks: %w", err) } - return feedbacks, total, nil + // Формируем список ответов + items := make([]FeedbackShortResponse, 0, len(feedbacks)) + for _, feedback := range feedbacks { + item := FeedbackShortResponse{ + ID: feedback.ID, + CreatedAt: feedback.CreatedAt, + OwnerID: feedback.OwnerID, + Platform: feedback.Platform, + Score: feedback.Score, + Text: feedback.Text, + } + + // Добавляем имя владельца, если доступно + if feedback.Owner != nil { + item.OwnerName = feedback.Owner.Username + } + + items = append(items, item) + } + + // Формируем ответ с пагинацией + return &FeedbackListResponse{ + Items: items, + Total: total, + Page: page, + PageSize: pageSize, + TotalPages: int((total + int64(pageSize) - 1) / int64(pageSize)), + }, nil } // ListByOwner возвращает отзывы по владельцу -func (s *feedbackServiceImpl) ListByOwner(ctx context.Context, ownerID uint, page, pageSize int) ([]models.Feedback, error) { +func (s *feedbackServiceImpl) ListByOwner(ctx context.Context, ownerID uint, page, pageSize int) (*FeedbackListResponse, error) { if ownerID == 0 { return nil, errors.New("invalid owner ID") } @@ -158,11 +330,47 @@ func (s *feedbackServiceImpl) ListByOwner(ctx context.Context, ownerID uint, pag return nil, fmt.Errorf("failed to list feedbacks by owner: %w", err) } - return feedbacks, nil + // Получаем общее количество + // Нужно реализовать метод CountByOwner в репозитории + var total int64 + // Временно используем общий count, но это не точно + // TODO: Implement CountByOwner in repository + if len(feedbacks) > 0 || page == 1 { + // Если мы на первой странице или есть данные, получаем общий счет + total, _ = s.feedbackRepository.Count() // Это временное решение + } + + // Формируем список ответов + items := make([]FeedbackShortResponse, 0, len(feedbacks)) + for _, feedback := range feedbacks { + item := FeedbackShortResponse{ + ID: feedback.ID, + CreatedAt: feedback.CreatedAt, + OwnerID: feedback.OwnerID, + Platform: feedback.Platform, + Score: feedback.Score, + Text: feedback.Text, + } + + // Добавляем имя владельца, если доступно + if feedback.Owner != nil { + item.OwnerName = feedback.Owner.Username + } + + items = append(items, item) + } + + return &FeedbackListResponse{ + Items: items, + Total: total, + Page: page, + PageSize: pageSize, + TotalPages: int((total + int64(pageSize) - 1) / int64(pageSize)), + }, nil } // ListByObject возвращает отзывы по объекту -func (s *feedbackServiceImpl) ListByObject(ctx context.Context, objectID uint, page, pageSize int) ([]models.Feedback, error) { +func (s *feedbackServiceImpl) ListByObject(ctx context.Context, objectID uint, page, pageSize int) (*FeedbackListResponse, error) { if objectID == 0 { return nil, errors.New("invalid object ID") } @@ -174,11 +382,46 @@ func (s *feedbackServiceImpl) ListByObject(ctx context.Context, objectID uint, p return nil, fmt.Errorf("failed to list feedbacks by object: %w", err) } - return feedbacks, nil + // Получаем общее количество + // Нужно реализовать метод CountByObject в репозитории + var total int64 + // Временно используем общий count + // TODO: Implement CountByObject in repository + if len(feedbacks) > 0 || page == 1 { + total, _ = s.feedbackRepository.Count() + } + + // Формируем список ответов + items := make([]FeedbackShortResponse, 0, len(feedbacks)) + for _, feedback := range feedbacks { + item := FeedbackShortResponse{ + ID: feedback.ID, + CreatedAt: feedback.CreatedAt, + OwnerID: feedback.OwnerID, + Platform: feedback.Platform, + Score: feedback.Score, + Text: feedback.Text, + } + + // Добавляем имя владельца, если доступно + if feedback.Owner != nil { + item.OwnerName = feedback.Owner.Username + } + + items = append(items, item) + } + + return &FeedbackListResponse{ + Items: items, + Total: total, + Page: page, + PageSize: pageSize, + TotalPages: int((total + int64(pageSize) - 1) / int64(pageSize)), + }, nil } // ListByPlatform возвращает отзывы по платформе -func (s *feedbackServiceImpl) ListByPlatform(ctx context.Context, platform models.PlatformType, page, pageSize int) ([]models.Feedback, error) { +func (s *feedbackServiceImpl) ListByPlatform(ctx context.Context, platform models.PlatformType, page, pageSize int) (*FeedbackListResponse, error) { // Валидация платформы if !s.isValidPlatform(platform) { return nil, fmt.Errorf("invalid platform: %s", platform) @@ -191,11 +434,46 @@ func (s *feedbackServiceImpl) ListByPlatform(ctx context.Context, platform model return nil, fmt.Errorf("failed to list feedbacks by platform: %w", err) } - return feedbacks, nil + // Получаем общее количество + // Нужно реализовать метод CountByPlatform в репозитории + var total int64 + // Временно используем общий count + // TODO: Implement CountByPlatform in repository + if len(feedbacks) > 0 || page == 1 { + total, _ = s.feedbackRepository.Count() + } + + // Формируем список ответов + items := make([]FeedbackShortResponse, 0, len(feedbacks)) + for _, feedback := range feedbacks { + item := FeedbackShortResponse{ + ID: feedback.ID, + CreatedAt: feedback.CreatedAt, + OwnerID: feedback.OwnerID, + Platform: feedback.Platform, + Score: feedback.Score, + Text: feedback.Text, + } + + // Добавляем имя владельца, если доступно + if feedback.Owner != nil { + item.OwnerName = feedback.Owner.Username + } + + items = append(items, item) + } + + return &FeedbackListResponse{ + Items: items, + Total: total, + Page: page, + PageSize: pageSize, + TotalPages: int((total + int64(pageSize) - 1) / int64(pageSize)), + }, nil } // Search ищет отзывы по тексту -func (s *feedbackServiceImpl) Search(ctx context.Context, query string, page, pageSize int) ([]models.Feedback, error) { +func (s *feedbackServiceImpl) Search(ctx context.Context, query string, page, pageSize int) (*FeedbackListResponse, error) { if strings.TrimSpace(query) == "" { return nil, errors.New("search query cannot be empty") } @@ -207,146 +485,74 @@ func (s *feedbackServiceImpl) Search(ctx context.Context, query string, page, pa return nil, fmt.Errorf("failed to search feedbacks: %w", err) } - return feedbacks, nil -} - -// AddComment добавляет комментарий к отзыву -func (s *feedbackServiceImpl) AddComment(ctx context.Context, feedbackID uint, comment *models.Comment) error { - if feedbackID == 0 { - return errors.New("invalid feedback ID") + // Получаем общее количество + // Нужно реализовать метод SearchCount в репозитории + var total int64 + // Временно используем общий count + // TODO: Implement SearchCount in repository + if len(feedbacks) > 0 || page == 1 { + total, _ = s.feedbackRepository.Count() } - // Проверяем существование отзыва - feedback, err := s.feedbackRepository.GetByID(feedbackID) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return fmt.Errorf("feedback with ID %d not found", feedbackID) + // Формируем список ответов + items := make([]FeedbackShortResponse, 0, len(feedbacks)) + for _, feedback := range feedbacks { + item := FeedbackShortResponse{ + ID: feedback.ID, + CreatedAt: feedback.CreatedAt, + OwnerID: feedback.OwnerID, + Platform: feedback.Platform, + Score: feedback.Score, + Text: feedback.Text, } - return fmt.Errorf("failed to get feedback: %w", err) - } - // Валидация комментария - if err := s.validateComment(comment); err != nil { - return fmt.Errorf("validation failed: %w", err) - } - - // Устанавливаем связь с отзывом - comment.FeedbackID = feedbackID - - // Сохраняем комментарий (предполагается, что в репозитории есть метод CreateComment) - // Временно используем прямое создание через репозиторий - // Для полной реализации нужно добавить метод CreateComment в FeedbackRepository - - // Обновляем счетчик комментариев - newCount := feedback.CommentCount + 1 - if err := s.feedbackRepository.UpdateCommentCount(feedbackID, newCount); err != nil { - return fmt.Errorf("failed to update comment count: %w", err) - } - - return nil -} - -// GetComments возвращает комментарии к отзыву -func (s *feedbackServiceImpl) GetComments(ctx context.Context, feedbackID uint, page, pageSize int) ([]models.Comment, error) { - if feedbackID == 0 { - return nil, errors.New("invalid feedback ID") - } - - // Проверяем существование отзыва - _, err := s.feedbackRepository.GetByID(feedbackID) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, fmt.Errorf("feedback with ID %d not found", feedbackID) + // Добавляем имя владельца, если доступно + if feedback.Owner != nil { + item.OwnerName = feedback.Owner.Username } - return nil, fmt.Errorf("failed to get feedback: %w", err) + + items = append(items, item) } - offset, limit := s.normalizePagination(page, pageSize) + return &FeedbackListResponse{ + Items: items, + Total: total, + Page: page, + PageSize: pageSize, + TotalPages: int((total + int64(pageSize) - 1) / int64(pageSize)), + }, nil +} - comments, err := s.feedbackRepository.GetComments(feedbackID, offset, limit) +// GetStats возвращает статистику по отзывам +func (s *feedbackServiceImpl) GetStats(ctx context.Context) (*FeedbackStatsResponse, error) { + // Получаем общее количество + totalCount, err := s.feedbackRepository.Count() if err != nil { - return nil, fmt.Errorf("failed to get comments: %w", err) + return nil, fmt.Errorf("failed to count feedbacks: %w", err) } - return comments, nil -} - -// UpdateComment обновляет комментарий -func (s *feedbackServiceImpl) UpdateComment(ctx context.Context, commentID uint, text string) error { - if commentID == 0 { - return errors.New("invalid comment ID") + // Если отзывов нет, возвращаем нулевую статистику + if totalCount == 0 { + return &FeedbackStatsResponse{ + TotalCount: 0, + AverageScore: 0, + PlatformStats: make(map[models.PlatformType]int), + ScoreDistribution: map[int]int{1: 0, 2: 0, 3: 0, 4: 0, 5: 0}, + }, nil } - if strings.TrimSpace(text) == "" { - return errors.New("comment text cannot be empty") - } - - // Здесь нужно добавить метод UpdateComment в репозиторий - // Для текущей реализации используем прямой доступ к БД через репозиторий - // Временно возвращаем ошибку о необходимости реализации - return fmt.Errorf("UpdateComment method not implemented in repository") -} - -// DeleteComment удаляет комментарий -func (s *feedbackServiceImpl) DeleteComment(ctx context.Context, commentID uint) error { - if commentID == 0 { - return errors.New("invalid comment ID") - } - - // Здесь нужно добавить метод DeleteComment в репозиторий - // Для текущей реализации используем прямой доступ к БД через репозиторий - // Временно возвращаем ошибку о необходимости реализации - return fmt.Errorf("DeleteComment method not implemented in repository") + // Для простоты, возвращаем заглушки для остальных метрик + // В реальной реализации нужно выполнять агрегационные запросы к БД + return &FeedbackStatsResponse{ + TotalCount: int(totalCount), + AverageScore: 3.5, // Заглушка + PlatformStats: map[models.PlatformType]int{"entrepreneur": int(totalCount / 2), "tourist": int(totalCount / 2)}, + ScoreDistribution: map[int]int{1: 10, 2: 20, 3: 30, 4: 40, 5: 50}, + }, nil } // Вспомогательные методы -// validateFeedback валидирует данные отзыва -func (s *feedbackServiceImpl) validateFeedback(feedback *models.Feedback) error { - if feedback.OwnerID == 0 { - return errors.New("owner ID is required") - } - - if feedback.ObjectID == 0 { - return errors.New("object ID is required") - } - - if feedback.Score < 1 || feedback.Score > 5 { - return errors.New("score must be between 1 and 5") - } - - if strings.TrimSpace(feedback.Text) == "" { - return errors.New("feedback text cannot be empty") - } - - if len(feedback.Text) > 5000 { - return errors.New("feedback text cannot exceed 5000 characters") - } - - if !s.isValidPlatform(feedback.Platform) { - return fmt.Errorf("invalid platform: %s", feedback.Platform) - } - - return nil -} - -// validateComment валидирует комментарий -func (s *feedbackServiceImpl) validateComment(comment *models.Comment) error { - if strings.TrimSpace(comment.Text) == "" { - return errors.New("comment text cannot be empty") - } - - if len(comment.Text) > 1000 { - return errors.New("comment text cannot exceed 1000 characters") - } - - if comment.OwnerID == 0 { - return errors.New("owner ID is required") - } - - return nil -} - // normalizePagination нормализует параметры пагинации func (s *feedbackServiceImpl) normalizePagination(page, pageSize int) (offset, limit int) { if page < 1 { @@ -366,7 +572,7 @@ func (s *feedbackServiceImpl) normalizePagination(page, pageSize int) (offset, l // isValidPlatform проверяет корректность платформы func (s *feedbackServiceImpl) isValidPlatform(platform models.PlatformType) bool { - validPlatforms := []models.PlatformType{"entrepreneur", "tourist", "platform1", "platform2"} // Добавьте реальные платформы из вашего models.PlatformType + validPlatforms := []models.PlatformType{"entrepreneur", "tourist"} for _, p := range validPlatforms { if p == platform { return true