adding new responses and caching for geocoding

This commit is contained in:
areeqakbr 2025-03-27 01:25:54 +07:00
parent 83c8cb4d0d
commit 90d5f4f04a
8 changed files with 270 additions and 10 deletions

View File

@ -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 {

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -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
}