diff --git a/manager/usecase_manager.go b/manager/usecase_manager.go index ee9322b..630f6d6 100644 --- a/manager/usecase_manager.go +++ b/manager/usecase_manager.go @@ -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 { diff --git a/model/dto/res/fishbone_res.go b/model/dto/res/fishbone_res.go new file mode 100644 index 0000000..ac39d51 --- /dev/null +++ b/model/dto/res/fishbone_res.go @@ -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"` +} \ No newline at end of file diff --git a/model/dto/res/tower_res.go b/model/dto/res/tower_res.go new file mode 100644 index 0000000..3447ea7 --- /dev/null +++ b/model/dto/res/tower_res.go @@ -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"` +} diff --git a/repository/tower_repo.go b/repository/tower_repo.go index 6a65267..117f0e4 100644 --- a/repository/tower_repo.go +++ b/repository/tower_repo.go @@ -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 } diff --git a/usecase/tower_usecase.go b/usecase/tower_usecase.go index 11b0b8c..75dfe8a 100644 --- a/usecase/tower_usecase.go +++ b/usecase/tower_usecase.go @@ -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 } diff --git a/utils/helper/towerHelperRes.go b/utils/helper/towerHelperRes.go new file mode 100644 index 0000000..46b3bfe --- /dev/null +++ b/utils/helper/towerHelperRes.go @@ -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 +} \ No newline at end of file diff --git a/utils/service/geoCache_service.go b/utils/service/geoCache_service.go new file mode 100644 index 0000000..ade1dbe --- /dev/null +++ b/utils/service/geoCache_service.go @@ -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() +} diff --git a/utils/service/geo_service.go b/utils/service/geo_service.go new file mode 100644 index 0000000..14a92e2 --- /dev/null +++ b/utils/service/geo_service.go @@ -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 +} +