adding new responses and caching for geocoding
This commit is contained in:
parent
83c8cb4d0d
commit
90d5f4f04a
|
|
@ -3,6 +3,7 @@ package manager
|
||||||
import (
|
import (
|
||||||
"users_management/m/config"
|
"users_management/m/config"
|
||||||
"users_management/m/usecase"
|
"users_management/m/usecase"
|
||||||
|
"users_management/m/utils/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UsecaseManager interface {
|
type UsecaseManager interface {
|
||||||
|
|
@ -19,11 +20,14 @@ type UsecaseManager interface {
|
||||||
|
|
||||||
type usecaseManager struct {
|
type usecaseManager struct {
|
||||||
repo RepositoryManager
|
repo RepositoryManager
|
||||||
|
geocoder service.GeocodingService
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUsecaseManager(repo RepositoryManager, cfg *config.Config) UsecaseManager {
|
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 {
|
func (um *usecaseManager) NewUserUsecase() usecase.UsersUsecase {
|
||||||
|
|
@ -47,7 +51,7 @@ func (um *usecaseManager) NewFishboneUsecase() usecase.FishboneUseCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (um *usecaseManager) NewTowerUsecase() usecase.TowerUseCase {
|
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 {
|
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) {
|
func (r *towerRepo) GetAll() ([]entity.Tower, error) {
|
||||||
var towers []entity.Tower
|
var towers []entity.Tower
|
||||||
err := r.db.Preload("Device").Find(&towers).Error
|
// Chain debug methods
|
||||||
if err != nil {
|
result := r.db.Preload("Device").Find(&towers)
|
||||||
return towers, err
|
if result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
}
|
}
|
||||||
return towers, nil
|
return towers, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,11 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
"users_management/m/model/dto/req"
|
"users_management/m/model/dto/req"
|
||||||
|
"users_management/m/model/dto/res"
|
||||||
"users_management/m/model/entity"
|
"users_management/m/model/entity"
|
||||||
"users_management/m/repository"
|
"users_management/m/repository"
|
||||||
|
"users_management/m/utils/helper"
|
||||||
|
"users_management/m/utils/service"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
@ -13,7 +16,7 @@ import (
|
||||||
|
|
||||||
type TowerUseCase interface {
|
type TowerUseCase interface {
|
||||||
Post(tower req.TowerDTO) error
|
Post(tower req.TowerDTO) error
|
||||||
GetAll() ([]entity.Tower, error)
|
GetAll() ([]res.TowerResponse, error)
|
||||||
GetByID(id uuid.UUID) (entity.Tower, error)
|
GetByID(id uuid.UUID) (entity.Tower, error)
|
||||||
UpdateTower(id uuid.UUID, tower req.UpdateTowerDTO) error
|
UpdateTower(id uuid.UUID, tower req.UpdateTowerDTO) error
|
||||||
}
|
}
|
||||||
|
|
@ -21,11 +24,13 @@ type TowerUseCase interface {
|
||||||
type towerUsecase struct {
|
type towerUsecase struct {
|
||||||
towerRepo repository.TowerRepo
|
towerRepo repository.TowerRepo
|
||||||
validate *validator.Validate
|
validate *validator.Validate
|
||||||
|
geocoder service.GeocodingService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTowerUseCase(towerRepo repository.TowerRepo) TowerUseCase {
|
func NewTowerUseCase(towerRepo repository.TowerRepo, geocoder service.GeocodingService) TowerUseCase {
|
||||||
return &towerUsecase{
|
return &towerUsecase{
|
||||||
towerRepo: towerRepo,
|
towerRepo: towerRepo,
|
||||||
|
geocoder: geocoder,
|
||||||
validate: validator.New(),
|
validate: validator.New(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -49,12 +54,16 @@ func (u *towerUsecase) Post(tower req.TowerDTO) error {
|
||||||
return u.towerRepo.Post(newTower)
|
return u.towerRepo.Post(newTower)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *towerUsecase) GetAll() ([]entity.Tower, error) {
|
func (u *towerUsecase) GetAll() ([]res.TowerResponse, error) {
|
||||||
towers, err := u.towerRepo.GetAll()
|
towers, err := u.towerRepo.GetAll()
|
||||||
if err != nil {
|
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