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