turash/bugulma/backend/internal/repository/address_repository.go

140 lines
4.2 KiB
Go

package repository
import (
"context"
"bugulma/backend/internal/domain"
"bugulma/backend/internal/geospatial"
"gorm.io/gorm"
)
// AddressRepository implements domain.AddressRepository with GORM
type AddressRepository struct {
*BaseRepository[domain.Address]
}
// NewAddressRepository creates a new GORM-based address repository
func NewAddressRepository(db *gorm.DB) domain.AddressRepository {
return &AddressRepository{
BaseRepository: NewBaseRepository[domain.Address](db),
}
}
// GetByID retrieves an address by ID with preloaded associations
func (r *AddressRepository) GetByID(ctx context.Context, id string) (*domain.Address, error) {
var address domain.Address
err := r.DB().WithContext(ctx).
Preload("Organizations").
Preload("Sites").
First(&address, "id = ?", id).Error
if err != nil {
return nil, handleError(err)
}
return &address, nil
}
// GetAll retrieves all addresses with preloaded associations
func (r *AddressRepository) GetAll(ctx context.Context) ([]*domain.Address, error) {
var addresses []*domain.Address
err := r.DB().WithContext(ctx).
Preload("Organizations").
Preload("Sites").
Find(&addresses).Error
return addresses, err
}
// GetWithinRadius retrieves addresses within a geographic radius
func (r *AddressRepository) GetWithinRadius(ctx context.Context, lat, lng, radiusKm float64) ([]*domain.Address, error) {
dialector := r.DB().Dialector.Name()
var addresses []*domain.Address
if dialector == "postgres" {
// Use PostGIS for PostgreSQL
geo := geospatial.NewGeoHelper(r.DB())
query := `
SELECT * FROM addresses
WHERE latitude IS NOT NULL AND longitude IS NOT NULL
AND ` + geo.DWithinExpr("ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)") + `
ORDER BY ST_Distance(
ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)::geography,
` + geo.PointExpr() + `::geography
)
`
// Use includeOrderBy=true so PointRadiusArgs returns args in the order
// [lng, lat, radiusKm, lng, lat] matching DWithin + ORDER BY placeholders
args := geo.PointRadiusArgs(lng, lat, radiusKm, true)
result := r.DB().WithContext(ctx).Raw(query, args...).Scan(&addresses)
if result.Error != nil {
return nil, result.Error
}
} else {
// Fallback to bounding box for non-PostgreSQL databases
query := `
SELECT * FROM addresses
WHERE latitude IS NOT NULL AND longitude IS NOT NULL
AND abs(latitude - ?) <= 0.09
AND abs(longitude - ?) <= 0.15
`
result := r.DB().WithContext(ctx).Raw(query, lat, lng).Scan(&addresses)
if result.Error != nil {
return nil, result.Error
}
}
return addresses, nil
}
// GetByOrganizationID retrieves all addresses for a specific organization
func (r *AddressRepository) GetByOrganizationID(ctx context.Context, orgID string) ([]*domain.Address, error) {
var addresses []*domain.Address
err := r.DB().WithContext(ctx).
Joins("JOIN organization_addresses ON organization_addresses.address_id = addresses.id").
Where("organization_addresses.organization_id = ?", orgID).
Find(&addresses).Error
return addresses, err
}
// GetBySiteID retrieves all addresses for a specific site
func (r *AddressRepository) GetBySiteID(ctx context.Context, siteID string) ([]*domain.Address, error) {
var addresses []*domain.Address
err := r.DB().WithContext(ctx).
Joins("JOIN site_addresses ON site_addresses.address_id = addresses.id").
Where("site_addresses.site_id = ?", siteID).
Find(&addresses).Error
return addresses, err
}
// FindOrCreate finds an existing address or creates a new one (deduplication)
func (r *AddressRepository) FindOrCreate(ctx context.Context, address *domain.Address) (*domain.Address, error) {
var existing domain.Address
// Try to find existing address by formatted address or coordinates
err := r.DB().WithContext(ctx).
Where("formatted_ru = ? OR (latitude = ? AND longitude = ?)",
address.FormattedRu, address.Latitude, address.Longitude).
First(&existing).Error
if err == nil {
// Found existing address
return &existing, nil
}
if err == gorm.ErrRecordNotFound {
// Address doesn't exist, create it
if err := r.DB().WithContext(ctx).Create(address).Error; err != nil {
return nil, handleError(err)
}
return address, nil
}
// Actual error occurred
return nil, handleError(err)
}