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:
2025-11-21 16:48:09 +05:00
parent ca44a2e6a5
commit de017cbe6c
2 changed files with 389 additions and 1 deletions
@@ -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
}