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.POST("/search", c.getNearestDevices)
|
||||||
nearestDevices.GET("/:id", c.getNearestDeviceByID)
|
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) {
|
func (c *NearestDeviceController) getNearestDevices(ctx *gin.Context) {
|
||||||
var request req.NearestDeviceDTO
|
var request req.NearestDeviceDTO
|
||||||
if err := ctx.ShouldBindJSON(&request); err != nil {
|
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"`
|
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
|
// Helper method to get all image URLs
|
||||||
func (t *Tower) GetAllImageURLs() []string {
|
func (t *Tower) GetAllImageURLs() []string {
|
||||||
var allImages []string
|
var allImages []string
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ type NearestDeviceRepo interface {
|
||||||
GetFishbonesByDeviceID(deviceID uuid.UUID) ([]entity.Fishbone, error)
|
GetFishbonesByDeviceID(deviceID uuid.UUID) ([]entity.Fishbone, error)
|
||||||
GetTowersByDeviceID(deviceID uuid.UUID) ([]entity.Tower, error)
|
GetTowersByDeviceID(deviceID uuid.UUID) ([]entity.Tower, error)
|
||||||
CountConnectionsByDeviceID(deviceID uuid.UUID) (backboneCount, fishboneCount, towerCount int, err 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) {
|
func (r *nearestDeviceRepo) GetNearestDevices(longitude, latitude, radius float64, limit int, province, city, district *string) ([]entity.DeviceWithDistance, error) {
|
||||||
var devices []entity.DeviceWithDistance
|
var devices []entity.DeviceWithDistance
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ func NewTowerRepo(db *gorm.DB) TowerRepo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (r *towerRepo) Post(tower entity.Tower) error {
|
func (r *towerRepo) Post(tower entity.Tower) error {
|
||||||
err := r.db.Create(&tower).Error
|
err := r.db.Create(&tower).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import (
|
||||||
type NearestDeviceUseCase interface {
|
type NearestDeviceUseCase interface {
|
||||||
GetNearestDevices(request req.NearestDeviceDTO) ([]res.NearestDeviceResponse, error)
|
GetNearestDevices(request req.NearestDeviceDTO) ([]res.NearestDeviceResponse, error)
|
||||||
GetNearestDeviceByID(id uuid.UUID, userLat, userLng float64) (res.NearestDeviceDetailResponse, 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 {
|
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) {
|
func (u *nearestDeviceUseCase) GetNearestDevices(request req.NearestDeviceDTO) ([]res.NearestDeviceResponse, error) {
|
||||||
err := u.validate.Struct(request)
|
err := u.validate.Struct(request)
|
||||||
if err != nil {
|
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