deleted: main_dc/yalarba/api_es/internal/models/busines_object.go
new file: main_dc/yalarba/api_es/internal/repository/review_repository.go add review repository
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
package models
|
|
||||||
@@ -0,0 +1,389 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"api_es/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrReviewNotFound = errors.New("review not found")
|
||||||
|
ErrDuplicateReview = errors.New("user already has review for this object")
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReviewRepository interface {
|
||||||
|
// Основные операции
|
||||||
|
Create(review *models.Review) error
|
||||||
|
GetByID(id uint) (*models.Review, error)
|
||||||
|
Update(id uint, updates map[string]interface{}) error
|
||||||
|
Delete(id uint) error
|
||||||
|
|
||||||
|
// Списки отзывов
|
||||||
|
GetByObject(objectID uint, pagination *Pagination) ([]models.Review, int64, error)
|
||||||
|
GetByAuthor(authorID uint, pagination *Pagination) ([]models.Review, int64, error)
|
||||||
|
GetByObjectAndAuthor(objectID, authorID uint) (*models.Review, error)
|
||||||
|
|
||||||
|
// Статистика
|
||||||
|
GetObjectRatingStats(objectID uint) (float64, int, error)
|
||||||
|
GetUserReviewStats(authorID uint) (int, float64, error)
|
||||||
|
|
||||||
|
// Административные методы
|
||||||
|
SetActive(id uint, isActive bool) error
|
||||||
|
GetAll(pagination *Pagination, filters *ReviewFilter) ([]models.Review, int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReviewFilter struct {
|
||||||
|
ObjectID uint
|
||||||
|
AuthorID uint
|
||||||
|
Rating int
|
||||||
|
IsActive *bool
|
||||||
|
MinRating int
|
||||||
|
MaxRating int
|
||||||
|
}
|
||||||
|
|
||||||
|
type reviewRepository struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReviewRepository(db *gorm.DB) ReviewRepository {
|
||||||
|
return &reviewRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create создает новый отзыв
|
||||||
|
func (r *reviewRepository) Create(review *models.Review) error {
|
||||||
|
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
// Проверяем, не оставлял ли пользователь уже отзыв на этот объект
|
||||||
|
var existingReview models.Review
|
||||||
|
err := tx.Where("object_id = ? AND author_id = ?", review.ObjectID, review.AuthorID).
|
||||||
|
First(&existingReview).Error
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return ErrDuplicateReview
|
||||||
|
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем отзыв
|
||||||
|
if err := tx.Create(review).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем рейтинг объекта
|
||||||
|
return r.updateObjectRating(tx, review.ObjectID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID возвращает отзыв по ID
|
||||||
|
func (r *reviewRepository) GetByID(id uint) (*models.Review, error) {
|
||||||
|
var review models.Review
|
||||||
|
err := r.db.
|
||||||
|
Preload("Author", func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Select("id, first_name, last_name, avatar")
|
||||||
|
}).
|
||||||
|
Preload("Object", func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Select("id, title, type")
|
||||||
|
}).
|
||||||
|
First(&review, id).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, ErrReviewNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &review, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update обновляет отзыв
|
||||||
|
func (r *reviewRepository) Update(id uint, updates map[string]interface{}) error {
|
||||||
|
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
// Получаем отзыв для получения object_id
|
||||||
|
var review models.Review
|
||||||
|
if err := tx.Select("object_id").First(&review, id).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return ErrReviewNotFound
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем отзыв
|
||||||
|
result := tx.Model(&models.Review{}).Where("id = ?", id).Updates(updates)
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
return ErrReviewNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем рейтинг объекта, если изменился рейтинг
|
||||||
|
if _, hasRating := updates["rating"]; hasRating {
|
||||||
|
return r.updateObjectRating(tx, review.ObjectID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete удаляет отзыв
|
||||||
|
func (r *reviewRepository) Delete(id uint) error {
|
||||||
|
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
// Получаем отзыв для получения object_id
|
||||||
|
var review models.Review
|
||||||
|
if err := tx.Select("object_id").First(&review, id).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return ErrReviewNotFound
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Удаляем отзыв
|
||||||
|
result := tx.Delete(&models.Review{}, id)
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
return ErrReviewNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем рейтинг объекта
|
||||||
|
return r.updateObjectRating(tx, review.ObjectID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByObject возвращает отзывы для объекта
|
||||||
|
func (r *reviewRepository) GetByObject(objectID uint, pagination *Pagination) ([]models.Review, int64, error) {
|
||||||
|
var reviews []models.Review
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
query := r.db.Model(&models.Review{}).Where("object_id = ? AND is_active = ?", objectID, true)
|
||||||
|
|
||||||
|
// Считаем общее количество
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Применяем пагинацию
|
||||||
|
if pagination != nil {
|
||||||
|
offset := (pagination.Page - 1) * pagination.PageSize
|
||||||
|
query = query.Offset(offset).Limit(pagination.PageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Загружаем данные
|
||||||
|
err := query.
|
||||||
|
Preload("Author", func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Select("id, first_name, last_name, avatar")
|
||||||
|
}).
|
||||||
|
Order("created_at DESC").
|
||||||
|
Find(&reviews).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return reviews, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByAuthor возвращает отзывы пользователя
|
||||||
|
func (r *reviewRepository) GetByAuthor(authorID uint, pagination *Pagination) ([]models.Review, int64, error) {
|
||||||
|
var reviews []models.Review
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
query := r.db.Model(&models.Review{}).Where("author_id = ?", authorID)
|
||||||
|
|
||||||
|
// Считаем общее количество
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Применяем пагинацию
|
||||||
|
if pagination != nil {
|
||||||
|
offset := (pagination.Page - 1) * pagination.PageSize
|
||||||
|
query = query.Offset(offset).Limit(pagination.PageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Загружаем данные
|
||||||
|
err := query.
|
||||||
|
Preload("Object", func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Select("id, title, type, city")
|
||||||
|
}).
|
||||||
|
Order("created_at DESC").
|
||||||
|
Find(&reviews).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return reviews, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByObjectAndAuthor возвращает отзыв конкретного пользователя для объекта
|
||||||
|
func (r *reviewRepository) GetByObjectAndAuthor(objectID, authorID uint) (*models.Review, error) {
|
||||||
|
var review models.Review
|
||||||
|
err := r.db.
|
||||||
|
Where("object_id = ? AND author_id = ?", objectID, authorID).
|
||||||
|
First(&review).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, ErrReviewNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &review, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetObjectRatingStats возвращает статистику рейтинга для объекта
|
||||||
|
func (r *reviewRepository) GetObjectRatingStats(objectID uint) (float64, int, error) {
|
||||||
|
var stats struct {
|
||||||
|
AverageRating float64
|
||||||
|
ReviewCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.db.Model(&models.Review{}).
|
||||||
|
Select("AVG(rating) as average_rating, COUNT(*) as review_count").
|
||||||
|
Where("object_id = ? AND is_active = ?", objectID, true).
|
||||||
|
Scan(&stats).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats.AverageRating, stats.ReviewCount, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserReviewStats возвращает статистику отзывов пользователя
|
||||||
|
func (r *reviewRepository) GetUserReviewStats(authorID uint) (int, float64, error) {
|
||||||
|
var stats struct {
|
||||||
|
ReviewCount int
|
||||||
|
AverageRating float64
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.db.Model(&models.Review{}).
|
||||||
|
Select("COUNT(*) as review_count, AVG(rating) as average_rating").
|
||||||
|
Where("author_id = ? AND is_active = ?", authorID, true).
|
||||||
|
Scan(&stats).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats.ReviewCount, stats.AverageRating, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetActive активирует/деактивирует отзыв
|
||||||
|
func (r *reviewRepository) SetActive(id uint, isActive bool) error {
|
||||||
|
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
// Получаем отзыв для получения object_id
|
||||||
|
var review models.Review
|
||||||
|
if err := tx.Select("object_id").First(&review, id).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return ErrReviewNotFound
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем статус
|
||||||
|
result := tx.Model(&models.Review{}).Where("id = ?", id).Update("is_active", isActive)
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
return ErrReviewNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем рейтинг объекта
|
||||||
|
return r.updateObjectRating(tx, review.ObjectID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll возвращает все отзывы с фильтрацией (для админки)
|
||||||
|
func (r *reviewRepository) GetAll(pagination *Pagination, filters *ReviewFilter) ([]models.Review, int64, error) {
|
||||||
|
var reviews []models.Review
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
query := r.db.Model(&models.Review{})
|
||||||
|
|
||||||
|
// Применяем фильтры
|
||||||
|
if filters != nil {
|
||||||
|
query = r.applyFilters(query, filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Считаем общее количество
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Применяем пагинацию
|
||||||
|
if pagination != nil {
|
||||||
|
offset := (pagination.Page - 1) * pagination.PageSize
|
||||||
|
query = query.Offset(offset).Limit(pagination.PageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Загружаем данные
|
||||||
|
err := query.
|
||||||
|
Preload("Author", func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Select("id, first_name, last_name, email")
|
||||||
|
}).
|
||||||
|
Preload("Object", func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Select("id, title, type")
|
||||||
|
}).
|
||||||
|
Order("created_at DESC").
|
||||||
|
Find(&reviews).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return reviews, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateObjectRating обновляет рейтинг объекта
|
||||||
|
func (r *reviewRepository) updateObjectRating(tx *gorm.DB, objectID uint) error {
|
||||||
|
stats, _, err := r.GetObjectRatingStats(objectID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
count_ := int64(0)
|
||||||
|
|
||||||
|
// Обновляем рейтинг объекта
|
||||||
|
return tx.Model(&models.Object{}).
|
||||||
|
Where("id = ?", objectID).
|
||||||
|
Updates(map[string]interface{}{
|
||||||
|
"rating": stats,
|
||||||
|
"review_count": tx.Model(&models.Review{}).
|
||||||
|
Where("object_id = ? AND is_active = ?", objectID, true).
|
||||||
|
Count(&count_),
|
||||||
|
}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyFilters применяет фильтры к запросу
|
||||||
|
func (r *reviewRepository) applyFilters(query *gorm.DB, filters *ReviewFilter) *gorm.DB {
|
||||||
|
if filters.ObjectID != 0 {
|
||||||
|
query = query.Where("object_id = ?", filters.ObjectID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filters.AuthorID != 0 {
|
||||||
|
query = query.Where("author_id = ?", filters.AuthorID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filters.Rating != 0 {
|
||||||
|
query = query.Where("rating = ?", filters.Rating)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filters.IsActive != nil {
|
||||||
|
query = query.Where("is_active = ?", *filters.IsActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filters.MinRating > 0 {
|
||||||
|
query = query.Where("rating >= ?", filters.MinRating)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filters.MaxRating > 0 {
|
||||||
|
query = query.Where("rating <= ?", filters.MaxRating)
|
||||||
|
}
|
||||||
|
|
||||||
|
return query
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user