adding nearest device for towers
This commit is contained in:
parent
0f034cc332
commit
caee765b8b
|
|
@ -33,9 +33,79 @@ func (c *NearestDeviceController) Route() {
|
|||
{
|
||||
nearestDevices.POST("/search", c.getNearestDevices)
|
||||
nearestDevices.GET("/:id", c.getNearestDeviceByID)
|
||||
|
||||
nearestDevices.POST("/towers/search", c.getNearestTowers)
|
||||
nearestDevices.GET("/towers/:id", c.getNearestTowerByID)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *NearestDeviceController) getNearestTowers(ctx *gin.Context) {
|
||||
var request req.NearestTowerDTO
|
||||
if err := ctx.ShouldBindJSON(&request); err != nil {
|
||||
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
towers, err := c.nearestDeviceUC.GetNearestTowers(request)
|
||||
if err != nil {
|
||||
common.ErrorResponses(ctx, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response := gin.H{
|
||||
"towers": towers,
|
||||
"total": len(towers),
|
||||
"search_params": gin.H{
|
||||
"latitude": request.Latitude,
|
||||
"longitude": request.Longitude,
|
||||
"radius": request.Radius,
|
||||
"province": request.Province,
|
||||
"city": request.City,
|
||||
"district": request.District,
|
||||
},
|
||||
}
|
||||
|
||||
common.SingleResponses(ctx, "Nearest towers retrieved successfully", response)
|
||||
}
|
||||
|
||||
func (c *NearestDeviceController) getNearestTowerByID(ctx *gin.Context) {
|
||||
id := ctx.Param("id")
|
||||
towerID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid tower ID")
|
||||
return
|
||||
}
|
||||
|
||||
// Get user coordinates from query params
|
||||
latStr := ctx.Query("lat")
|
||||
lngStr := ctx.Query("lng")
|
||||
|
||||
if latStr == "" || lngStr == "" {
|
||||
common.ErrorResponses(ctx, http.StatusBadRequest, "User coordinates (lat, lng) are required")
|
||||
return
|
||||
}
|
||||
|
||||
userLat, err := strconv.ParseFloat(latStr, 64)
|
||||
if err != nil {
|
||||
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid latitude")
|
||||
return
|
||||
}
|
||||
|
||||
userLng, err := strconv.ParseFloat(lngStr, 64)
|
||||
if err != nil {
|
||||
common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid longitude")
|
||||
return
|
||||
}
|
||||
|
||||
tower, err := c.nearestDeviceUC.GetNearestTowerByID(towerID, userLat, userLng)
|
||||
if err != nil {
|
||||
common.ErrorResponses(ctx, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
common.SingleResponses(ctx, "Tower details retrieved successfully", tower)
|
||||
}
|
||||
|
||||
func (c *NearestDeviceController) getNearestDevices(ctx *gin.Context) {
|
||||
var request req.NearestDeviceDTO
|
||||
if err := ctx.ShouldBindJSON(&request); err != nil {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package req
|
||||
|
||||
type NearestTowerDTO struct {
|
||||
Longitude float64 `json:"longitude" validate:"required"`
|
||||
Latitude float64 `json:"latitude" validate:"required"`
|
||||
Radius float64 `json:"radius" validate:"omitempty,min=0.1,max=50"` // Default 5km, max 50km
|
||||
Limit int `json:"limit" validate:"omitempty,min=1,max=100"` // Default 10, max 100
|
||||
Province *string `json:"province,omitempty"`
|
||||
City *string `json:"city,omitempty"`
|
||||
District *string `json:"district,omitempty"`
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package res
|
||||
|
||||
import (
|
||||
"time"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Simplified response for list
|
||||
type NearestTowerResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
TowerCode string `json:"tower_code"`
|
||||
Distance float64 `json:"distance_km"`
|
||||
Address string `json:"address"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
ImageURL *string `json:"image_url,omitempty"`
|
||||
ExternalTower *bool `json:"external_tower,omitempty"`
|
||||
DeviceCode *string `json:"device_code,omitempty"`
|
||||
Province *string `json:"province,omitempty"`
|
||||
City *string `json:"city,omitempty"`
|
||||
District *string `json:"district,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// Detailed response for single tower
|
||||
type NearestTowerDetailResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
TowerCode string `json:"tower_code"`
|
||||
Distance float64 `json:"distance_km"`
|
||||
Address string `json:"address"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
ImageURL *string `json:"image_url,omitempty"`
|
||||
ImageURLs []string `json:"image_urls,omitempty"`
|
||||
ExternalTower *bool `json:"external_tower,omitempty"`
|
||||
|
||||
// Connected device information
|
||||
Device *DeviceConnectionInfo `json:"device,omitempty"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type DeviceConnectionInfo struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
DeviceCode string `json:"device_code"`
|
||||
DeviceType string `json:"device_type"`
|
||||
Distance float64 `json:"distance_from_towers_to_device_km"` // Distance from tower to device
|
||||
Address string `json:"address"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
Status string `json:"status"`
|
||||
PortAmount int `json:"port_amount"`
|
||||
}
|
||||
|
|
@ -22,6 +22,23 @@ type Tower struct {
|
|||
Device Device `json:"device,omitempty" gorm:"foreignKey:DeviceID"`
|
||||
}
|
||||
|
||||
type TowerWithDistance struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
TowerCode string `json:"tower_code"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
ImageURL *string `json:"image_url"`
|
||||
ExternalTower *bool `json:"external_tower"`
|
||||
DevID *uuid.UUID `json:"dev_id"`
|
||||
DeviceCode *string `json:"device_code"`
|
||||
Province *string `json:"province"`
|
||||
City *string `json:"city"`
|
||||
District *string `json:"district"`
|
||||
Distance float64 `json:"distance"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// Helper method to get all image URLs
|
||||
func (t *Tower) GetAllImageURLs() []string {
|
||||
var allImages []string
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ type NearestDeviceRepo interface {
|
|||
GetFishbonesByDeviceID(deviceID uuid.UUID) ([]entity.Fishbone, error)
|
||||
GetTowersByDeviceID(deviceID uuid.UUID) ([]entity.Tower, error)
|
||||
CountConnectionsByDeviceID(deviceID uuid.UUID) (backboneCount, fishboneCount, towerCount int, err error)
|
||||
GetNearestTowers(longitude, latitude, radius float64, limit int, province, city, district *string) ([]entity.TowerWithDistance, error)
|
||||
GetTowerByIDWithConnections(id uuid.UUID) (entity.Tower, error)
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -26,6 +28,45 @@ func NewNearestDeviceRepo(db *gorm.DB) NearestDeviceRepo {
|
|||
}
|
||||
}
|
||||
|
||||
func (r *nearestDeviceRepo) GetNearestTowers(longitude, latitude, radius float64, limit int, province, city, district *string) ([]entity.TowerWithDistance, error) {
|
||||
var towers []entity.TowerWithDistance
|
||||
|
||||
// Build the subquery first - join with devices to get location filters
|
||||
subQuery := r.db.Table("towers").
|
||||
Select(`towers.id, towers.tower_code, towers.longitude, towers.latitude,
|
||||
towers.image_url, towers.external_tower, towers.dev_id, towers.created_at, towers.updated_at,
|
||||
devices.device_code, devices.province, devices.city, devices.district,
|
||||
(6371 * acos(cos(radians(?)) * cos(radians(towers.latitude)) * cos(radians(towers.longitude) - radians(?)) + sin(radians(?)) * sin(radians(towers.latitude)))) AS distance`,
|
||||
latitude, longitude, latitude).
|
||||
Joins("LEFT JOIN devices ON towers.dev_id = devices.id")
|
||||
|
||||
// Apply location filters to subquery
|
||||
if province != nil && *province != "" {
|
||||
subQuery = subQuery.Where("devices.province = ?", *province)
|
||||
}
|
||||
if city != nil && *city != "" {
|
||||
subQuery = subQuery.Where("devices.city = ?", *city)
|
||||
}
|
||||
if district != nil && *district != "" {
|
||||
subQuery = subQuery.Where("devices.district = ?", *district)
|
||||
}
|
||||
|
||||
// Use the subquery in the main query
|
||||
err := r.db.Table("(?) as towers_with_distance", subQuery).
|
||||
Where("distance <= ?", radius).
|
||||
Order("distance ASC").
|
||||
Limit(limit).
|
||||
Scan(&towers).Error
|
||||
|
||||
return towers, err
|
||||
}
|
||||
|
||||
func (r *nearestDeviceRepo) GetTowerByIDWithConnections(id uuid.UUID) (entity.Tower, error) {
|
||||
var tower entity.Tower
|
||||
err := r.db.Preload("Device").Where("id = ?", id).First(&tower).Error
|
||||
return tower, err
|
||||
}
|
||||
|
||||
func (r *nearestDeviceRepo) GetNearestDevices(longitude, latitude, radius float64, limit int, province, city, district *string) ([]entity.DeviceWithDistance, error) {
|
||||
var devices []entity.DeviceWithDistance
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ func NewTowerRepo(db *gorm.DB) TowerRepo {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
func (r *towerRepo) Post(tower entity.Tower) error {
|
||||
err := r.db.Create(&tower).Error
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import (
|
|||
type NearestDeviceUseCase interface {
|
||||
GetNearestDevices(request req.NearestDeviceDTO) ([]res.NearestDeviceResponse, error)
|
||||
GetNearestDeviceByID(id uuid.UUID, userLat, userLng float64) (res.NearestDeviceDetailResponse, error)
|
||||
GetNearestTowers(request req.NearestTowerDTO) ([]res.NearestTowerResponse, error)
|
||||
GetNearestTowerByID(id uuid.UUID, userLat, userLng float64) (res.NearestTowerDetailResponse, error)
|
||||
}
|
||||
|
||||
type nearestDeviceUseCase struct {
|
||||
|
|
@ -31,6 +33,61 @@ func NewNearestDeviceUseCase(nearestDeviceRepo repository.NearestDeviceRepo, geo
|
|||
}
|
||||
}
|
||||
|
||||
func (u *nearestDeviceUseCase) GetNearestTowers(request req.NearestTowerDTO) ([]res.NearestTowerResponse, error) {
|
||||
err := u.validate.Struct(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set defaults
|
||||
radius := request.Radius
|
||||
if radius == 0 {
|
||||
radius = 5.0 // Default 5km
|
||||
}
|
||||
|
||||
limit := request.Limit
|
||||
if limit == 0 {
|
||||
limit = 10 // Default 10 towers
|
||||
}
|
||||
|
||||
towers, err := u.nearestDeviceRepo.GetNearestTowers(
|
||||
request.Longitude,
|
||||
request.Latitude,
|
||||
radius,
|
||||
limit,
|
||||
request.Province,
|
||||
request.City,
|
||||
request.District,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
responses, err := helper.ConvertToNearestTowerResponses(towers, u.geocoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
func (u *nearestDeviceUseCase) GetNearestTowerByID(id uuid.UUID, userLat, userLng float64) (res.NearestTowerDetailResponse, error) {
|
||||
tower, err := u.nearestDeviceRepo.GetTowerByIDWithConnections(id)
|
||||
if err != nil {
|
||||
return res.NearestTowerDetailResponse{}, err
|
||||
}
|
||||
|
||||
// Calculate distance
|
||||
distance := calculateDistance(userLat, userLng, tower.Latitude, tower.Longitude)
|
||||
|
||||
response, err := helper.ConvertToNearestTowerDetailResponse(tower, distance, u.nearestDeviceRepo, u.geocoder)
|
||||
if err != nil {
|
||||
return res.NearestTowerDetailResponse{}, err
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (u *nearestDeviceUseCase) GetNearestDevices(request req.NearestDeviceDTO) ([]res.NearestDeviceResponse, error) {
|
||||
err := u.validate.Struct(request)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
package helper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"users_management/m/model/dto/res"
|
||||
"users_management/m/model/entity"
|
||||
"users_management/m/repository"
|
||||
"users_management/m/utils/service"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func ConvertToNearestTowerResponses(towers []entity.TowerWithDistance, geocoder service.GeocodingService) ([]res.NearestTowerResponse, error) {
|
||||
var responses []res.NearestTowerResponse
|
||||
|
||||
for _, tower := range towers {
|
||||
// Get address
|
||||
address := ""
|
||||
if geocoder != nil {
|
||||
addr, err := geocoder.GetAddressFromCoordinates(tower.Latitude, tower.Longitude)
|
||||
if err != nil {
|
||||
log.Printf("Geocoding error for tower %s: %v", tower.TowerCode, err)
|
||||
address = fmt.Sprintf("Coordinates: %.6f, %.6f", tower.Latitude, tower.Longitude)
|
||||
} else {
|
||||
address = addr
|
||||
}
|
||||
}
|
||||
|
||||
response := res.NearestTowerResponse{
|
||||
ID: tower.ID,
|
||||
TowerCode: tower.TowerCode,
|
||||
Distance: tower.Distance,
|
||||
Address: address,
|
||||
Longitude: tower.Longitude,
|
||||
Latitude: tower.Latitude,
|
||||
ImageURL: tower.ImageURL,
|
||||
ExternalTower: tower.ExternalTower,
|
||||
DeviceCode: tower.DeviceCode,
|
||||
Province: tower.Province,
|
||||
City: tower.City,
|
||||
District: tower.District,
|
||||
CreatedAt: tower.CreatedAt,
|
||||
}
|
||||
|
||||
responses = append(responses, response)
|
||||
}
|
||||
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
func ConvertToNearestTowerDetailResponse(tower entity.Tower, distance float64, repo repository.NearestDeviceRepo, geocoder service.GeocodingService) (res.NearestTowerDetailResponse, error) {
|
||||
// Get address
|
||||
address := ""
|
||||
if geocoder != nil {
|
||||
addr, err := geocoder.GetAddressFromCoordinates(tower.Latitude, tower.Longitude)
|
||||
if err != nil {
|
||||
log.Printf("Geocoding error for tower %s: %v", tower.TowerCode, err)
|
||||
address = fmt.Sprintf("Coordinates: %.6f, %.6f", tower.Latitude, tower.Longitude)
|
||||
} else {
|
||||
address = addr
|
||||
}
|
||||
}
|
||||
|
||||
// Get all image URLs
|
||||
allImageURLs := tower.GetAllImageURLs()
|
||||
|
||||
var deviceInfo *res.DeviceConnectionInfo
|
||||
if tower.Device.ID != uuid.Nil {
|
||||
// Get device address
|
||||
deviceAddress := ""
|
||||
if geocoder != nil {
|
||||
addr, err := geocoder.GetAddressFromCoordinates(tower.Device.Latitude, tower.Device.Longitude)
|
||||
if err != nil {
|
||||
log.Printf("Geocoding error for device %s: %v", tower.Device.DeviceCode, err)
|
||||
deviceAddress = fmt.Sprintf("Coordinates: %.6f, %.6f", tower.Device.Latitude, tower.Device.Longitude)
|
||||
} else {
|
||||
deviceAddress = addr
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate distance from tower to device
|
||||
deviceDistance := calculateDistance(tower.Latitude, tower.Longitude, tower.Device.Latitude, tower.Device.Longitude)
|
||||
|
||||
deviceInfo = &res.DeviceConnectionInfo{
|
||||
ID: tower.Device.ID,
|
||||
DeviceCode: tower.Device.DeviceCode,
|
||||
DeviceType: string(tower.Device.DeviceType),
|
||||
Distance: deviceDistance,
|
||||
Address: deviceAddress,
|
||||
Longitude: tower.Device.Longitude,
|
||||
Latitude: tower.Device.Latitude,
|
||||
Status: string(tower.Device.Status),
|
||||
PortAmount: tower.Device.PortAmount,
|
||||
}
|
||||
}
|
||||
|
||||
response := res.NearestTowerDetailResponse{
|
||||
ID: tower.ID,
|
||||
TowerCode: tower.TowerCode,
|
||||
Distance: distance,
|
||||
Address: address,
|
||||
Longitude: tower.Longitude,
|
||||
Latitude: tower.Latitude,
|
||||
ImageURL: &tower.ImageURL,
|
||||
ImageURLs: allImageURLs,
|
||||
ExternalTower: tower.ExternalTower,
|
||||
Device: deviceInfo,
|
||||
CreatedAt: tower.CreatedAt,
|
||||
UpdatedAt: tower.UpdatedAt,
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
Loading…
Reference in New Issue