adding new responses and caching for geocoding
This commit is contained in:
parent
83c8cb4d0d
commit
90d5f4f04a
|
|
@ -3,6 +3,7 @@ package manager
|
|||
import (
|
||||
"users_management/m/config"
|
||||
"users_management/m/usecase"
|
||||
"users_management/m/utils/service"
|
||||
)
|
||||
|
||||
type UsecaseManager interface {
|
||||
|
|
@ -19,11 +20,14 @@ type UsecaseManager interface {
|
|||
|
||||
type usecaseManager struct {
|
||||
repo RepositoryManager
|
||||
geocoder service.GeocodingService
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func NewUsecaseManager(repo RepositoryManager, cfg *config.Config) UsecaseManager {
|
||||
return &usecaseManager{repo: repo,cfg: cfg }
|
||||
basegeoCoder := service.NewGeocodingService()
|
||||
cachedGeocoder := service.NewCachedGeocodingService(basegeoCoder)
|
||||
return &usecaseManager{repo: repo,cfg: cfg , geocoder: cachedGeocoder}
|
||||
}
|
||||
|
||||
func (um *usecaseManager) NewUserUsecase() usecase.UsersUsecase {
|
||||
|
|
@ -47,7 +51,7 @@ func (um *usecaseManager) NewFishboneUsecase() usecase.FishboneUseCase {
|
|||
}
|
||||
|
||||
func (um *usecaseManager) NewTowerUsecase() usecase.TowerUseCase {
|
||||
return usecase.NewTowerUseCase(um.repo.NewTowerRepository())
|
||||
return usecase.NewTowerUseCase(um.repo.NewTowerRepository(), um.geocoder)
|
||||
}
|
||||
|
||||
func (um *usecaseManager) NewDevicePortUsecase() usecase.DevicePortUseCase {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package res
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type FishboneResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
DeviceName string `json:"device_name"`
|
||||
TowerCode string `json:"tower_code"`
|
||||
Longtitude float64 `json:"longtitude"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
Address string `json:"address"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package res
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type TowerResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
DeviceCode string `json:"device_code"`
|
||||
TowerCode string `json:"tower_code"`
|
||||
Longitude float64 `json:"longtitude"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
Address string `json:"address"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
|
@ -34,9 +34,10 @@ func (r *towerRepo) Post(tower entity.Tower) error {
|
|||
|
||||
func (r *towerRepo) GetAll() ([]entity.Tower, error) {
|
||||
var towers []entity.Tower
|
||||
err := r.db.Preload("Device").Find(&towers).Error
|
||||
if err != nil {
|
||||
return towers, err
|
||||
// Chain debug methods
|
||||
result := r.db.Preload("Device").Find(&towers)
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
return towers, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,11 @@ import (
|
|||
"errors"
|
||||
"time"
|
||||
"users_management/m/model/dto/req"
|
||||
"users_management/m/model/dto/res"
|
||||
"users_management/m/model/entity"
|
||||
"users_management/m/repository"
|
||||
"users_management/m/utils/helper"
|
||||
"users_management/m/utils/service"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/google/uuid"
|
||||
|
|
@ -13,7 +16,7 @@ import (
|
|||
|
||||
type TowerUseCase interface {
|
||||
Post(tower req.TowerDTO) error
|
||||
GetAll() ([]entity.Tower, error)
|
||||
GetAll() ([]res.TowerResponse, error)
|
||||
GetByID(id uuid.UUID) (entity.Tower, error)
|
||||
UpdateTower(id uuid.UUID, tower req.UpdateTowerDTO) error
|
||||
}
|
||||
|
|
@ -21,11 +24,13 @@ type TowerUseCase interface {
|
|||
type towerUsecase struct {
|
||||
towerRepo repository.TowerRepo
|
||||
validate *validator.Validate
|
||||
geocoder service.GeocodingService
|
||||
}
|
||||
|
||||
func NewTowerUseCase(towerRepo repository.TowerRepo) TowerUseCase {
|
||||
func NewTowerUseCase(towerRepo repository.TowerRepo, geocoder service.GeocodingService) TowerUseCase {
|
||||
return &towerUsecase{
|
||||
towerRepo: towerRepo,
|
||||
geocoder: geocoder,
|
||||
validate: validator.New(),
|
||||
}
|
||||
}
|
||||
|
|
@ -49,12 +54,16 @@ func (u *towerUsecase) Post(tower req.TowerDTO) error {
|
|||
return u.towerRepo.Post(newTower)
|
||||
}
|
||||
|
||||
func (u *towerUsecase) GetAll() ([]entity.Tower, error) {
|
||||
func (u *towerUsecase) GetAll() ([]res.TowerResponse, error) {
|
||||
towers, err := u.towerRepo.GetAll()
|
||||
if err != nil {
|
||||
return towers, err
|
||||
return nil, err
|
||||
}
|
||||
return towers, nil
|
||||
towerResp, err := helper.ConvertToTowerResponses(towers,u.geocoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return towerResp, nil
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"log"
|
||||
"users_management/m/model/dto/res"
|
||||
"users_management/m/model/entity"
|
||||
"users_management/m/utils/service"
|
||||
)
|
||||
|
||||
func ConvertToTowerResponses(towers []entity.Tower, geocoder service.GeocodingService) ([]res.TowerResponse, error) {
|
||||
var responses []res.TowerResponse
|
||||
|
||||
|
||||
for _, tower := range towers {
|
||||
var address string
|
||||
|
||||
if geocoder != nil {
|
||||
generatedAddress, err := geocoder.GetAddressFromCoordinates(tower.Latitude, tower.Longitude)
|
||||
if err != nil {
|
||||
// Log specific geocoding error
|
||||
log.Printf("Geocoding error for tower %s: %v", tower.TowerCode, err)
|
||||
} else {
|
||||
address = generatedAddress
|
||||
}
|
||||
} else {
|
||||
log.Println("WARNING: Geocoder is nil")
|
||||
}
|
||||
|
||||
towerResp := res.TowerResponse{
|
||||
ID: tower.ID,
|
||||
DeviceCode: tower.Device.DeviceCode,
|
||||
TowerCode: tower.TowerCode,
|
||||
Longitude: tower.Longitude,
|
||||
Latitude: tower.Latitude,
|
||||
Address: address,
|
||||
CreatedAt: tower.CreatedAt,
|
||||
}
|
||||
responses = append(responses, towerResp)
|
||||
}
|
||||
return responses, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type CacheStats struct {
|
||||
Hits int64
|
||||
Misses int64
|
||||
Size int
|
||||
}
|
||||
|
||||
type CachedGeocoder struct {
|
||||
underlying GeocodingService
|
||||
cache map[string]string
|
||||
mutex sync.RWMutex
|
||||
hits int64
|
||||
misses int64
|
||||
}
|
||||
|
||||
func NewCachedGeocodingService(underlying GeocodingService) *CachedGeocoder {
|
||||
return &CachedGeocoder{
|
||||
underlying: underlying,
|
||||
cache: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *CachedGeocoder) GetAddressFromCoordinates(latitude, longitude float64) (string, error) {
|
||||
cacheKey := fmt.Sprintf("%.6f,%.6f", latitude, longitude)
|
||||
|
||||
// Check cache first
|
||||
g.mutex.RLock()
|
||||
if address, ok := g.cache[cacheKey]; ok {
|
||||
g.mutex.RUnlock()
|
||||
atomic.AddInt64(&g.hits, 1)
|
||||
log.Printf("CACHE HIT: Coordinates (%.6f,%.6f) found in cache", latitude, longitude)
|
||||
return address, nil
|
||||
}
|
||||
g.mutex.RUnlock()
|
||||
|
||||
log.Printf("CACHE MISS: Coordinates (%.6f,%.6f) not in cache, fetching from service", latitude, longitude)
|
||||
atomic.AddInt64(&g.misses, 1)
|
||||
|
||||
// Not in cache, call the underlying service
|
||||
address, err := g.underlying.GetAddressFromCoordinates(latitude, longitude)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
g.mutex.Lock()
|
||||
g.cache[cacheKey] = address
|
||||
g.mutex.Unlock()
|
||||
log.Printf("CACHE UPDATE: Coordinates (%.6f,%.6f) added to cache", latitude, longitude)
|
||||
|
||||
return address, nil
|
||||
}
|
||||
|
||||
// GetStats returns the current cache statistics
|
||||
func (g *CachedGeocoder) GetStats() CacheStats {
|
||||
g.mutex.RLock()
|
||||
stats := CacheStats{
|
||||
Hits: g.hits,
|
||||
Misses: g.misses,
|
||||
Size: len(g.cache),
|
||||
}
|
||||
g.mutex.RUnlock()
|
||||
return stats
|
||||
}
|
||||
|
||||
// ClearCache empties the cache
|
||||
func (g *CachedGeocoder) ClearCache() {
|
||||
g.mutex.Lock()
|
||||
g.cache = make(map[string]string)
|
||||
g.mutex.Unlock()
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type GeocodingService interface {
|
||||
GetAddressFromCoordinates(latitude, longitude float64) (string, error)
|
||||
}
|
||||
|
||||
type nominatimGeocoder struct {
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// NewGeocodingService creates a new geocoding service using Nominatim (OpenStreetMap)
|
||||
func NewGeocodingService() GeocodingService {
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
return &nominatimGeocoder{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
type NominatimResponse struct {
|
||||
DisplayName string `json:"display_name"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
|
||||
func (g *nominatimGeocoder) GetAddressFromCoordinates(latitude, longitude float64) (string, error) {
|
||||
|
||||
|
||||
if latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180 {
|
||||
errMsg := fmt.Sprintf("Invalid coordinates: lat=%f, lon=%f", latitude, longitude)
|
||||
return "", fmt.Errorf(errMsg)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf(
|
||||
"https://nominatim.openstreetmap.org/reverse?format=json&lat=%f&lon=%f&zoom=18&addressdetails=1",
|
||||
latitude, longitude,
|
||||
)
|
||||
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
log.Printf("Request creation error: %v", err)
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("User-Agent", "AssetManagementApp")
|
||||
|
||||
resp, err := g.client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("HTTP request error: %v", err)
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read full response body
|
||||
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Printf("Error reading response body: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
||||
var result NominatimResponse
|
||||
if err := json.Unmarshal(bodyBytes, &result); err != nil {
|
||||
log.Printf("JSON unmarshal error: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Detailed error checking
|
||||
if result.Error != "" {
|
||||
log.Printf("Nominatim API Error: %s", result.Error)
|
||||
return "", fmt.Errorf("geocoding error: %s", result.Error)
|
||||
}
|
||||
|
||||
// Check for empty display name
|
||||
if result.DisplayName == "" {
|
||||
log.Printf("No address found for coordinates: %f, %f", latitude, longitude)
|
||||
return "", fmt.Errorf("no address found for these coordinates")
|
||||
}
|
||||
|
||||
return result.DisplayName, nil
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue