mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
Repository Structure:
- Move files from cluttered root directory into organized structure
- Create archive/ for archived data and scraper results
- Create bugulma/ for the complete application (frontend + backend)
- Create data/ for sample datasets and reference materials
- Create docs/ for comprehensive documentation structure
- Create scripts/ for utility scripts and API tools
Backend Implementation:
- Implement 3 missing backend endpoints identified in gap analysis:
* GET /api/v1/organizations/{id}/matching/direct - Direct symbiosis matches
* GET /api/v1/users/me/organizations - User organizations
* POST /api/v1/proposals/{id}/status - Update proposal status
- Add complete proposal domain model, repository, and service layers
- Create database migration for proposals table
- Fix CLI server command registration issue
API Documentation:
- Add comprehensive proposals.md API documentation
- Update README.md with Users and Proposals API sections
- Document all request/response formats, error codes, and business rules
Code Quality:
- Follow existing Go backend architecture patterns
- Add proper error handling and validation
- Match frontend expected response schemas
- Maintain clean separation of concerns (handler -> service -> repository)
138 lines
4.0 KiB
Go
138 lines
4.0 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
|
|
"bugulma/backend/internal/domain"
|
|
|
|
"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
|
|
query := `
|
|
SELECT * FROM addresses
|
|
WHERE latitude IS NOT NULL AND longitude IS NOT NULL
|
|
AND ST_DWithin(
|
|
ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)::geography,
|
|
ST_SetSRID(ST_MakePoint(?, ?), 4326)::geography,
|
|
?
|
|
)
|
|
ORDER BY ST_Distance(
|
|
ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)::geography,
|
|
ST_SetSRID(ST_MakePoint(?, ?), 4326)::geography
|
|
)
|
|
`
|
|
result := r.DB().WithContext(ctx).Raw(query, lng, lat, radiusKm*1000, lng, lat).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)
|
|
}
|