new file: main_dc/yalarba/api_es/internal/repository/object_repository.go
add object_repository
This commit is contained in:
@@ -0,0 +1,398 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gorm.io/gorm"
|
||||
"api_es/internal/models"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrObjectNotFound = errors.New("object not found")
|
||||
)
|
||||
|
||||
type ObjectRepository interface {
|
||||
// Основные операции
|
||||
Create(object *models.Object) error
|
||||
GetByID(id uint) (*models.Object, error)
|
||||
Update(id uint, updates *models.ObjectUpdateRequest) error
|
||||
Delete(id uint) error
|
||||
List(filter *ObjectFilter, pagination *Pagination) ([]models.Object, int64, error)
|
||||
|
||||
// Специфичные операции
|
||||
GetByOwner(ownerID uint, filter *ObjectFilter, pagination *Pagination) ([]models.Object, int64, error)
|
||||
UpdateStatus(id uint, status models.ObjectStatus) error
|
||||
IncrementViewCount(id uint) error
|
||||
UpdateRating(id uint, rating float64, reviewCount int) error
|
||||
|
||||
// Работа с изображениями
|
||||
AddImage(objectID uint, image *models.ObjectImage) error
|
||||
RemoveImage(objectID uint, imageID uint) error
|
||||
SetPrimaryImage(objectID uint, imageID uint) error
|
||||
GetImages(objectID uint) ([]models.ObjectImage, error)
|
||||
|
||||
// Работа с удобствами
|
||||
AddAmenities(objectID uint, amenityIDs []uint) error
|
||||
RemoveAmenities(objectID uint, amenityIDs []uint) error
|
||||
GetAmenities(objectID uint) ([]models.Amenity, error)
|
||||
}
|
||||
|
||||
type ObjectFilter struct {
|
||||
Type []models.ObjectType
|
||||
City string
|
||||
Status []models.ObjectStatus
|
||||
OwnerID uint
|
||||
MinPrice float64
|
||||
MaxPrice float64
|
||||
MinRating float64
|
||||
AmenityIDs []uint
|
||||
Search string
|
||||
}
|
||||
|
||||
type Pagination struct {
|
||||
Page int `form:"page" default:"1"`
|
||||
PageSize int `form:"page_size" default:"20"`
|
||||
}
|
||||
|
||||
type objectRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewObjectRepository(db *gorm.DB) ObjectRepository {
|
||||
return &objectRepository{db: db}
|
||||
}
|
||||
|
||||
// Create создает новый объект
|
||||
func (r *objectRepository) Create(object *models.Object) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// Создаем основной объект
|
||||
if err := tx.Create(object).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Добавляем связи с удобствами, если они есть
|
||||
if len(object.Amenities) > 0 {
|
||||
if err := tx.Model(object).Association("Amenities").Append(object.Amenities); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetByID возвращает объект по ID с связанными данными
|
||||
func (r *objectRepository) GetByID(id uint) (*models.Object, error) {
|
||||
var object models.Object
|
||||
err := r.db.
|
||||
Preload("Owner", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Select("id, first_name, last_name, email, phone")
|
||||
}).
|
||||
Preload("Images", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Order("is_primary DESC, order ASC")
|
||||
}).
|
||||
Preload("Amenities").
|
||||
First(&object, id).Error
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ErrObjectNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &object, nil
|
||||
}
|
||||
|
||||
// Update обновляет объект
|
||||
func (r *objectRepository) Update(id uint, updates *models.ObjectUpdateRequest) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// Обновляем основные поля
|
||||
updateData := map[string]interface{}{}
|
||||
|
||||
if updates.Title != "" {
|
||||
updateData["title"] = updates.Title
|
||||
}
|
||||
if updates.Type != "" {
|
||||
updateData["type"] = updates.Type
|
||||
}
|
||||
if updates.Description != "" {
|
||||
updateData["description"] = updates.Description
|
||||
}
|
||||
if updates.City != "" {
|
||||
updateData["city"] = updates.City
|
||||
}
|
||||
if updates.Address != "" {
|
||||
updateData["address"] = updates.Address
|
||||
}
|
||||
if updates.Latitude != 0 {
|
||||
updateData["latitude"] = updates.Latitude
|
||||
}
|
||||
if updates.Longitude != 0 {
|
||||
updateData["longitude"] = updates.Longitude
|
||||
}
|
||||
if updates.Price != 0 {
|
||||
updateData["price"] = updates.Price
|
||||
}
|
||||
if updates.PricePeriod != "" {
|
||||
updateData["price_period"] = updates.PricePeriod
|
||||
}
|
||||
if updates.Status != "" {
|
||||
updateData["status"] = updates.Status
|
||||
}
|
||||
|
||||
if len(updateData) > 0 {
|
||||
if err := tx.Model(&models.Object{}).Where("id = ?", id).Updates(updateData).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем удобства, если переданы
|
||||
if updates.AmenityIDs != nil {
|
||||
var object models.Object
|
||||
if err := tx.First(&object, id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var amenities []models.Amenity
|
||||
if err := tx.Where("id IN ?", updates.AmenityIDs).Find(&amenities).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Model(&object).Association("Amenities").Replace(amenities); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Delete удаляет объект (мягкое удаление)
|
||||
func (r *objectRepository) Delete(id uint) error {
|
||||
result := r.db.Delete(&models.Object{}, id)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return ErrObjectNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List возвращает список объектов с фильтрацией и пагинацией
|
||||
func (r *objectRepository) List(filter *ObjectFilter, pagination *Pagination) ([]models.Object, int64, error) {
|
||||
var objects []models.Object
|
||||
var total int64
|
||||
|
||||
query := r.db.Model(&models.Object{})
|
||||
|
||||
// Применяем фильтры
|
||||
if filter != nil {
|
||||
query = r.applyFilters(query, filter)
|
||||
}
|
||||
|
||||
// Считаем общее количество
|
||||
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("Images", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("is_primary = ?", true).Limit(1)
|
||||
}).
|
||||
Preload("Amenities").
|
||||
Order("created_at DESC").
|
||||
Find(&objects).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return objects, total, nil
|
||||
}
|
||||
|
||||
// GetByOwner возвращает объекты владельца
|
||||
func (r *objectRepository) GetByOwner(ownerID uint, filter *ObjectFilter, pagination *Pagination) ([]models.Object, int64, error) {
|
||||
if filter == nil {
|
||||
filter = &ObjectFilter{}
|
||||
}
|
||||
filter.OwnerID = ownerID
|
||||
return r.List(filter, pagination)
|
||||
}
|
||||
|
||||
// UpdateStatus обновляет статус объекта
|
||||
func (r *objectRepository) UpdateStatus(id uint, status models.ObjectStatus) error {
|
||||
result := r.db.Model(&models.Object{}).Where("id = ?", id).Update("status", status)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return ErrObjectNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IncrementViewCount увеличивает счетчик просмотров
|
||||
func (r *objectRepository) IncrementViewCount(id uint) error {
|
||||
return r.db.Model(&models.Object{}).
|
||||
Where("id = ?", id).
|
||||
Update("view_count", gorm.Expr("view_count + ?", 1)).
|
||||
Error
|
||||
}
|
||||
|
||||
// UpdateRating обновляет рейтинг и количество отзывов
|
||||
func (r *objectRepository) UpdateRating(id uint, rating float64, reviewCount int) error {
|
||||
return r.db.Model(&models.Object{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]interface{}{
|
||||
"rating": rating,
|
||||
"review_count": reviewCount,
|
||||
}).Error
|
||||
}
|
||||
|
||||
// AddImage добавляет изображение к объекту
|
||||
func (r *objectRepository) AddImage(objectID uint, image *models.ObjectImage) error {
|
||||
image.ObjectID = objectID
|
||||
return r.db.Create(image).Error
|
||||
}
|
||||
|
||||
// RemoveImage удаляет изображение объекта
|
||||
func (r *objectRepository) RemoveImage(objectID uint, imageID uint) error {
|
||||
result := r.db.Where("object_id = ? AND id = ?", objectID, imageID).Delete(&models.ObjectImage{})
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return ErrObjectNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPrimaryImage устанавливает основное изображение
|
||||
func (r *objectRepository) SetPrimaryImage(objectID uint, imageID uint) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
// Сбрасываем все is_primary для объекта
|
||||
if err := tx.Model(&models.ObjectImage{}).
|
||||
Where("object_id = ?", objectID).
|
||||
Update("is_primary", false).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Устанавливаем новое основное изображение
|
||||
result := tx.Model(&models.ObjectImage{}).
|
||||
Where("object_id = ? AND id = ?", objectID, imageID).
|
||||
Update("is_primary", true)
|
||||
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return ErrObjectNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetImages возвращает изображения объекта
|
||||
func (r *objectRepository) GetImages(objectID uint) ([]models.ObjectImage, error) {
|
||||
var images []models.ObjectImage
|
||||
err := r.db.Where("object_id = ?", objectID).
|
||||
Order("is_primary DESC, order ASC").
|
||||
Find(&images).Error
|
||||
return images, err
|
||||
}
|
||||
|
||||
// AddAmenities добавляет удобства к объекту
|
||||
func (r *objectRepository) AddAmenities(objectID uint, amenityIDs []uint) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
var object models.Object
|
||||
if err := tx.First(&object, objectID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var amenities []models.Amenity
|
||||
if err := tx.Where("id IN ?", amenityIDs).Find(&amenities).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Model(&object).Association("Amenities").Append(amenities)
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveAmenities удаляет удобства у объекта
|
||||
func (r *objectRepository) RemoveAmenities(objectID uint, amenityIDs []uint) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
var object models.Object
|
||||
if err := tx.First(&object, objectID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var amenities []models.Amenity
|
||||
if err := tx.Where("id IN ?", amenityIDs).Find(&amenities).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Model(&object).Association("Amenities").Delete(amenities)
|
||||
})
|
||||
}
|
||||
|
||||
// GetAmenities возвращает удобства объекта
|
||||
func (r *objectRepository) GetAmenities(objectID uint) ([]models.Amenity, error) {
|
||||
var amenities []models.Amenity
|
||||
err := r.db.Joins("JOIN object_amenities ON amenities.id = object_amenities.amenity_id").
|
||||
Where("object_amenities.object_id = ?", objectID).
|
||||
Find(&amenities).Error
|
||||
return amenities, err
|
||||
}
|
||||
|
||||
// applyFilters применяет фильтры к запросу
|
||||
func (r *objectRepository) applyFilters(query *gorm.DB, filter *ObjectFilter) *gorm.DB {
|
||||
if len(filter.Type) > 0 {
|
||||
query = query.Where("type IN ?", filter.Type)
|
||||
}
|
||||
|
||||
if filter.City != "" {
|
||||
query = query.Where("city = ?", filter.City)
|
||||
}
|
||||
|
||||
if len(filter.Status) > 0 {
|
||||
query = query.Where("status IN ?", filter.Status)
|
||||
}
|
||||
|
||||
if filter.OwnerID != 0 {
|
||||
query = query.Where("owner_id = ?", filter.OwnerID)
|
||||
}
|
||||
|
||||
if filter.MinPrice > 0 {
|
||||
query = query.Where("price >= ?", filter.MinPrice)
|
||||
}
|
||||
|
||||
if filter.MaxPrice > 0 {
|
||||
query = query.Where("price <= ?", filter.MaxPrice)
|
||||
}
|
||||
|
||||
if filter.MinRating > 0 {
|
||||
query = query.Where("rating >= ?", filter.MinRating)
|
||||
}
|
||||
|
||||
if filter.Search != "" {
|
||||
search := "%" + filter.Search + "%"
|
||||
query = query.Where("title ILIKE ? OR description ILIKE ?", search, search)
|
||||
}
|
||||
|
||||
// Фильтр по удобствам
|
||||
if len(filter.AmenityIDs) > 0 {
|
||||
query = query.Joins("JOIN object_amenities ON objects.id = object_amenities.object_id").
|
||||
Where("object_amenities.amenity_id IN ?", filter.AmenityIDs)
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
Reference in New Issue
Block a user