894415e3ac
modified: main_dc/yalarba/api_yal/internal/domain/feetback/dto.go modified: main_dc/yalarba/api_yal/internal/domain/feetback/service.go last
582 lines
19 KiB
Go
582 lines
19 KiB
Go
package feetback
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"api_yal/internal/models"
|
|
"api_yal/internal/repository"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type FeedbackService interface {
|
|
// 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 возвращает список отзывов с пагинацией
|
|
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) (*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 {
|
|
feedbackRepository repository.FeedbackRepository
|
|
}
|
|
|
|
func NewFeedbackServiceImpl(feedbackRepository repository.FeedbackRepository) FeedbackService {
|
|
return &feedbackServiceImpl{
|
|
feedbackRepository: feedbackRepository,
|
|
}
|
|
}
|
|
|
|
// Create создает новый отзыв
|
|
func (s *feedbackServiceImpl) Create(ctx context.Context, req *CreateFeedbackRequest) (*FeedbackResponse, error) {
|
|
// Валидация входных данных
|
|
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,
|
|
}
|
|
|
|
// Устанавливаем начальное значение счетчика комментариев
|
|
feedback.CommentCount = 0
|
|
|
|
// Создаем отзыв
|
|
if err := s.feedbackRepository.Create(feedback); err != nil {
|
|
return nil, fmt.Errorf("failed to create feedback: %w", err)
|
|
}
|
|
|
|
// Формируем ответ
|
|
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) (*FeedbackResponse, error) {
|
|
if id == 0 {
|
|
return nil, errors.New("invalid feedback ID")
|
|
}
|
|
|
|
feedback, err := s.feedbackRepository.GetByID(id)
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, fmt.Errorf("feedback with ID %d not found", id)
|
|
}
|
|
return nil, fmt.Errorf("failed to get feedback: %w", err)
|
|
}
|
|
|
|
// Формируем ответ
|
|
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, 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(id)
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, fmt.Errorf("feedback with ID %d not found", id)
|
|
}
|
|
return nil, fmt.Errorf("failed to get feedback: %w", err)
|
|
}
|
|
|
|
// Проверяем, что текущий пользователь является владельцем
|
|
userID := ctx.Value("userID").(uint)
|
|
if existing.OwnerID != userID {
|
|
return nil, errors.New("unauthorized: cannot update feedback owned by another user")
|
|
}
|
|
|
|
// Обновляем поля, если они указаны
|
|
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(existing); err != nil {
|
|
return nil, fmt.Errorf("failed to update feedback: %w", err)
|
|
}
|
|
|
|
// Формируем ответ
|
|
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 удаляет отзыв
|
|
func (s *feedbackServiceImpl) Delete(ctx context.Context, id uint) error {
|
|
if id == 0 {
|
|
return errors.New("invalid 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", id)
|
|
}
|
|
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)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// List возвращает список отзывов с пагинацией
|
|
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, fmt.Errorf("failed to list feedbacks: %w", err)
|
|
}
|
|
|
|
// Получаем общее количество
|
|
total, err := s.feedbackRepository.Count()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to count feedbacks: %w", err)
|
|
}
|
|
|
|
// Формируем список ответов
|
|
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) (*FeedbackListResponse, error) {
|
|
if ownerID == 0 {
|
|
return nil, errors.New("invalid owner ID")
|
|
}
|
|
|
|
offset, limit := s.normalizePagination(page, pageSize)
|
|
|
|
feedbacks, err := s.feedbackRepository.ListByOwner(ownerID, offset, limit)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list feedbacks by owner: %w", err)
|
|
}
|
|
|
|
// Получаем общее количество
|
|
// Нужно реализовать метод 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) (*FeedbackListResponse, error) {
|
|
if objectID == 0 {
|
|
return nil, errors.New("invalid object ID")
|
|
}
|
|
|
|
offset, limit := s.normalizePagination(page, pageSize)
|
|
|
|
feedbacks, err := s.feedbackRepository.ListByObject(objectID, offset, limit)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list feedbacks by object: %w", err)
|
|
}
|
|
|
|
// Получаем общее количество
|
|
// Нужно реализовать метод 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) (*FeedbackListResponse, error) {
|
|
// Валидация платформы
|
|
if !s.isValidPlatform(platform) {
|
|
return nil, fmt.Errorf("invalid platform: %s", platform)
|
|
}
|
|
|
|
offset, limit := s.normalizePagination(page, pageSize)
|
|
|
|
feedbacks, err := s.feedbackRepository.ListByPlatform(platform, offset, limit)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list feedbacks by platform: %w", err)
|
|
}
|
|
|
|
// Получаем общее количество
|
|
// Нужно реализовать метод 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) (*FeedbackListResponse, error) {
|
|
if strings.TrimSpace(query) == "" {
|
|
return nil, errors.New("search query cannot be empty")
|
|
}
|
|
|
|
offset, limit := s.normalizePagination(page, pageSize)
|
|
|
|
feedbacks, err := s.feedbackRepository.Search(query, offset, limit)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to search feedbacks: %w", err)
|
|
}
|
|
|
|
// Получаем общее количество
|
|
// Нужно реализовать метод SearchCount в репозитории
|
|
var total int64
|
|
// Временно используем общий count
|
|
// TODO: Implement SearchCount 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
|
|
}
|
|
|
|
// GetStats возвращает статистику по отзывам
|
|
func (s *feedbackServiceImpl) GetStats(ctx context.Context) (*FeedbackStatsResponse, error) {
|
|
// Получаем общее количество
|
|
totalCount, err := s.feedbackRepository.Count()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to count feedbacks: %w", err)
|
|
}
|
|
|
|
// Если отзывов нет, возвращаем нулевую статистику
|
|
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
|
|
}
|
|
|
|
// Для простоты, возвращаем заглушки для остальных метрик
|
|
// В реальной реализации нужно выполнять агрегационные запросы к БД
|
|
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
|
|
}
|
|
|
|
// Вспомогательные методы
|
|
|
|
// normalizePagination нормализует параметры пагинации
|
|
func (s *feedbackServiceImpl) normalizePagination(page, pageSize int) (offset, limit int) {
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 {
|
|
pageSize = 10
|
|
}
|
|
if pageSize > 100 {
|
|
pageSize = 100
|
|
}
|
|
|
|
offset = (page - 1) * pageSize
|
|
limit = pageSize
|
|
return offset, limit
|
|
}
|
|
|
|
// isValidPlatform проверяет корректность платформы
|
|
func (s *feedbackServiceImpl) isValidPlatform(platform models.PlatformType) bool {
|
|
validPlatforms := []models.PlatformType{"entrepreneur", "tourist"}
|
|
for _, p := range validPlatforms {
|
|
if p == platform {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
} |