Merge pull request 'adding olt on device creation' (#36) from feature/responses-v2 into dev

Reviewed-on: winter-access/backend_nam#36
This commit is contained in:
areeqakbr 2025-06-30 04:16:36 +00:00
commit d303dd3fcf
8 changed files with 221 additions and 21 deletions

View File

@ -201,6 +201,27 @@ func (dc *DeviceController) CreateDevice() gin.HandlerFunc {
} }
} }
var towerID *uuid.UUID
if towerIDStr := c.PostForm("tower_id"); towerIDStr != "" {
if parsedTowerID, err := uuid.Parse(towerIDStr); err == nil {
towerID = &parsedTowerID
} else {
common.ErrorResponses(c, http.StatusBadRequest, "Invalid tower ID format")
return
}
}
// Handle OLTID in form data
var oltID *uuid.UUID
if oltIDStr := c.PostForm("olt_id"); oltIDStr != "" {
if parsedOLTID, err := uuid.Parse(oltIDStr); err == nil {
oltID = &parsedOLTID
} else {
common.ErrorResponses(c, http.StatusBadRequest, "Invalid OLT ID format")
return
}
}
// Create DTO // Create DTO
deviceDTO := req.DeviceDTO{ deviceDTO := req.DeviceDTO{
DeviceCode: deviceCode, DeviceCode: deviceCode,
@ -209,6 +230,8 @@ func (dc *DeviceController) CreateDevice() gin.HandlerFunc {
Latitude: latitude, Latitude: latitude,
PortAmount: portAmount, PortAmount: portAmount,
Status: status, Status: status,
TowerID: towerID, // Add TowerID
OLTID: oltID, // Add OLTID
} }
// Handle optional string fields // Handle optional string fields
@ -288,7 +311,7 @@ func (dc *DeviceController) GetDeviceByID() gin.HandlerFunc {
func (dc *DeviceController) UpdateDevice() gin.HandlerFunc { func (dc *DeviceController) UpdateDevice() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
id := c.Param("uuid") id := c.Param("uuid")
uuid, err := uuid.Parse(id) deviceUUID, err := uuid.Parse(id) // Change variable name from 'uuid' to 'deviceUUID'
if err != nil { if err != nil {
common.ErrorResponses(c, http.StatusBadRequest, "Invalid UUID") common.ErrorResponses(c, http.StatusBadRequest, "Invalid UUID")
return return
@ -305,9 +328,9 @@ func (dc *DeviceController) UpdateDevice() gin.HandlerFunc {
return return
} }
err = dc.du.UpdateDevice(uuid, deviceDTO) err = dc.du.UpdateDevice(deviceUUID, deviceDTO) // Use the new variable name
if err != nil { if err != nil {
common.ErrorResponses(c, http.StatusBadRequest, "Device not found") common.ErrorResponses(c, http.StatusBadRequest, err.Error()) // Also fix error message
return return
} }
@ -315,7 +338,6 @@ func (dc *DeviceController) UpdateDevice() gin.HandlerFunc {
return return
} }
// Handle multipart form request // Handle multipart form request
err = c.Request.ParseMultipartForm(50 << 20) // 50MB for multiple images err = c.Request.ParseMultipartForm(50 << 20) // 50MB for multiple images
if err != nil { if err != nil {
@ -369,6 +391,26 @@ func (dc *DeviceController) UpdateDevice() gin.HandlerFunc {
deviceUpdateDTO.District = &district deviceUpdateDTO.District = &district
} }
// Handle TowerID in form data
if towerIDStr := c.PostForm("tower_id"); towerIDStr != "" {
if towerID, err := uuid.Parse(towerIDStr); err == nil {
deviceUpdateDTO.TowerID = &towerID
} else {
common.ErrorResponses(c, http.StatusBadRequest, "Invalid tower ID format")
return
}
}
// Handle OLTID in form data
if oltIDStr := c.PostForm("olt_id"); oltIDStr != "" {
if oltID, err := uuid.Parse(oltIDStr); err == nil {
deviceUpdateDTO.OLTID = &oltID
} else {
common.ErrorResponses(c, http.StatusBadRequest, "Invalid OLT ID format")
return
}
}
// Get multiple image files // Get multiple image files
form := c.Request.MultipartForm form := c.Request.MultipartForm
imageFiles := form.File["images"] // Support multiple images imageFiles := form.File["images"] // Support multiple images
@ -383,7 +425,7 @@ func (dc *DeviceController) UpdateDevice() gin.HandlerFunc {
// Handle replace_images flag // Handle replace_images flag
replaceImages := c.PostForm("replace_images") == "true" replaceImages := c.PostForm("replace_images") == "true"
err = dc.du.UpdateDeviceWithMultipleImages(uuid, deviceUpdateDTO, imageFiles, replaceImages) err = dc.du.UpdateDeviceWithMultipleImages(deviceUUID, deviceUpdateDTO, imageFiles, replaceImages) // Use the new variable name
if err != nil { if err != nil {
common.ErrorResponses(c, http.StatusBadRequest, err.Error()) common.ErrorResponses(c, http.StatusBadRequest, err.Error())
return return

View File

@ -42,10 +42,11 @@ func NewUsecaseManager(repo RepositoryManager, cfg *config.Config) UsecaseManage
} }
func (um *usecaseManager) NewDeviceDetailsUsecase() usecase.DeviceDetailsUseCase { func (um *usecaseManager) NewDeviceDetailsUsecase() usecase.DeviceDetailsUseCase {
return usecase.NewDeviceDetailsUseCase( return usecase.NewDeviceDetailsUseCase(
um.repo.NewDeviceDetailsRepository(), um.repo.NewDeviceDetailsRepository(),
um.geocoder, um.repo.NewOLTRepo(),
) um.geocoder,
)
} }
func (um *usecaseManager) NewNearestDeviceUsecase() usecase.NearestDeviceUseCase { func (um *usecaseManager) NewNearestDeviceUsecase() usecase.NearestDeviceUseCase {
@ -64,7 +65,7 @@ func (um *usecaseManager) NewAuthUsecase() usecase.AuthUsecase {
} }
func (um *usecaseManager) NewDeviceUsecase() usecase.DeviceUseCase { func (um *usecaseManager) NewDeviceUsecase() usecase.DeviceUseCase {
return usecase.NewDeviceUseCase(um.repo.NewDeviceRepository(),um.geocoder) return usecase.NewDeviceUseCase(um.repo.NewDeviceRepository(), um.repo.NewOLTRepo(), um.geocoder)
} }
func (um *usecaseManager) NewBackboneUsecase() usecase.BackboneUseCase { func (um *usecaseManager) NewBackboneUsecase() usecase.BackboneUseCase {

View File

@ -18,6 +18,7 @@ type DeviceDetailsDTO struct {
City *string `json:"city,omitempty" validate:"omitempty,min=3"` City *string `json:"city,omitempty" validate:"omitempty,min=3"`
District *string `json:"district,omitempty" validate:"omitempty,min=3"` District *string `json:"district,omitempty" validate:"omitempty,min=3"`
TowerID *uuid.UUID `json:"tower_id,omitempty"` TowerID *uuid.UUID `json:"tower_id,omitempty"`
OLTID *uuid.UUID `json:"olt_id,omitempty"`
} }
type UpdateDeviceDetailsDTO struct { type UpdateDeviceDetailsDTO struct {
@ -32,6 +33,7 @@ type UpdateDeviceDetailsDTO struct {
City *string `json:"city,omitempty" validate:"omitempty,min=3"` City *string `json:"city,omitempty" validate:"omitempty,min=3"`
District *string `json:"district,omitempty" validate:"omitempty,min=3"` District *string `json:"district,omitempty" validate:"omitempty,min=3"`
TowerID *uuid.UUID `json:"tower_id,omitempty"` TowerID *uuid.UUID `json:"tower_id,omitempty"`
OLTID *uuid.UUID `json:"olt_id,omitempty"`
} }
type AssignMultipleCustomersDTO struct { type AssignMultipleCustomersDTO struct {

View File

@ -13,6 +13,7 @@ type DeviceDTO struct {
City *string `json:"city,omitempty" validate:"omitempty,min=3"` City *string `json:"city,omitempty" validate:"omitempty,min=3"`
District *string `json:"district,omitempty" validate:"omitempty,min=3"` District *string `json:"district,omitempty" validate:"omitempty,min=3"`
TowerID *uuid.UUID `json:"tower_id,omitempty"` TowerID *uuid.UUID `json:"tower_id,omitempty"`
OLTID *uuid.UUID `json:"olt_id,omitempty"`
} }
type UpdateDeviceDTO struct { type UpdateDeviceDTO struct {
@ -26,6 +27,7 @@ type UpdateDeviceDTO struct {
City *string `json:"city,omitempty" validate:"omitempty,min=3"` City *string `json:"city,omitempty" validate:"omitempty,min=3"`
District *string `json:"district,omitempty" validate:"omitempty,min=3"` District *string `json:"district,omitempty" validate:"omitempty,min=3"`
TowerID *uuid.UUID `json:"tower_id,omitempty"` TowerID *uuid.UUID `json:"tower_id,omitempty"`
OLTID *uuid.UUID `json:"olt_id,omitempty"`
} }
type BulkDeviceImageUploadDTO struct { type BulkDeviceImageUploadDTO struct {

View File

@ -27,8 +27,7 @@ type DeviceDetailsResponse struct {
ImageURL *string `json:"image_url,omitempty"` ImageURL *string `json:"image_url,omitempty"`
ImageURLs []string `json:"image_urls"` ImageURLs []string `json:"image_urls"`
TowerID *uuid.UUID `json:"tower_id,omitempty"` // Add TowerID TowerID *uuid.UUID `json:"tower_id,omitempty"` // Add TowerID
OLTID *uuid.UUID `json:"olt_id"` // Add OLTID OLT *OLTInfo `json:"olt"` // Add OLT name
OLTName *string `json:"olt_name"` // Add OLT name
// Connection details // Connection details
Backbones []BackboneConnectionInfo `json:"backbones"` Backbones []BackboneConnectionInfo `json:"backbones"`
@ -39,6 +38,11 @@ type DeviceDetailsResponse struct {
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
} }
type OLTInfo struct {
OLTID uuid.UUID `json:"olt_id"`
OLTName string `json:"olt_name"`
}
type TowerConnectionDetail struct { type TowerConnectionDetail struct {
ID uuid.UUID `json:"id"` ID uuid.UUID `json:"id"`
TowerCode string `json:"tower_code"` TowerCode string `json:"tower_code"`

View File

@ -39,22 +39,34 @@ type DeviceDetailsUseCase interface {
DeleteDeviceImage(deviceID uuid.UUID, filename string) error DeleteDeviceImage(deviceID uuid.UUID, filename string) error
GetDevicesWithoutTowers(deviceTypes []string) ([]res.DeviceDetailsResponse, error) GetDevicesWithoutTowers(deviceTypes []string) ([]res.DeviceDetailsResponse, error)
GetDevicesWithoutConnections(deviceTypes []string) ([]res.DeviceDetailsResponse, error) GetDevicesWithoutConnections(deviceTypes []string) ([]res.DeviceDetailsResponse, error)
ValidateOLTExists(oltID uuid.UUID) (bool, error)
} }
type deviceDetailsUseCase struct { type deviceDetailsUseCase struct {
deviceDetailsRepo repository.DeviceDetailsRepo deviceDetailsRepo repository.DeviceDetailsRepo
oltRepo repository.OLTRepo
geocoder service.GeocodingService geocoder service.GeocodingService
validate *validator.Validate validate *validator.Validate
} }
func NewDeviceDetailsUseCase(deviceDetailsRepo repository.DeviceDetailsRepo, geocoder service.GeocodingService) DeviceDetailsUseCase { func NewDeviceDetailsUseCase(deviceDetailsRepo repository.DeviceDetailsRepo, oltRepo repository.OLTRepo,geocoder service.GeocodingService) DeviceDetailsUseCase {
return &deviceDetailsUseCase{ return &deviceDetailsUseCase{
deviceDetailsRepo: deviceDetailsRepo, deviceDetailsRepo: deviceDetailsRepo,
oltRepo: oltRepo,
geocoder: geocoder, geocoder: geocoder,
validate: validator.New(), validate: validator.New(),
} }
} }
func (u *deviceDetailsUseCase) ValidateOLTExists(oltID uuid.UUID) (bool, error) {
_, err := u.oltRepo.GetByID(oltID)
if err != nil {
return false, nil
}
return true, nil
}
func (u *deviceDetailsUseCase) GetDevicesWithoutConnections(deviceTypes []string) ([]res.DeviceDetailsResponse, error) { func (u *deviceDetailsUseCase) GetDevicesWithoutConnections(deviceTypes []string) ([]res.DeviceDetailsResponse, error) {
// Validate device types and convert to uppercase // Validate device types and convert to uppercase
validTypes := map[string]bool{"CLOSURE": true, "OTB": true} validTypes := map[string]bool{"CLOSURE": true, "OTB": true}
@ -233,6 +245,45 @@ func (u *deviceDetailsUseCase) UpdateDeviceDetailsWithMultipleImages(id uuid.UUI
updates["district"] = *deviceDTO.District updates["district"] = *deviceDTO.District
} }
if deviceDTO.TowerID != nil {
towerExists, err := u.deviceDetailsRepo.ValidateTowerExists(*deviceDTO.TowerID)
if err != nil {
return fmt.Errorf("failed to validate tower: %w", err)
}
if !towerExists {
return fmt.Errorf("tower with ID %s not found", deviceDTO.TowerID.String())
}
updates["tower_id"] = *deviceDTO.TowerID
}
// Handle OLTID validation and update
if deviceDTO.OLTID!= nil {
// Get current device to check type
currentDevice, err := u.deviceDetailsRepo.GetByID(id)
if err != nil {
return fmt.Errorf("device not found: %w", err)
}
// Only ODP devices can be assigned to OLT
deviceType := string(currentDevice.DeviceType)
if deviceDTO.DeviceType != nil {
deviceType = *deviceDTO.DeviceType
}
if deviceType != "ODP" {
return fmt.Errorf("only ODP devices can be assigned to OLT")
}
oltExists, err := u.ValidateOLTExists(*deviceDTO.OLTID)
if err != nil {
return fmt.Errorf("failed to validate OLT: %w", err)
}
if !oltExists {
return fmt.Errorf("OLT with ID %s not found", deviceDTO.OLTID.String())
}
updates["olt_id"] = *deviceDTO.OLTID
}
// Handle multiple image uploads // Handle multiple image uploads
if len(imageFiles) > 0 { if len(imageFiles) > 0 {
// Get current device to handle existing images // Get current device to handle existing images

View File

@ -28,22 +28,34 @@ type DeviceUseCase interface {
BulkUploadImagesMultiple(devices []req.BulkDeviceImageUploadDTO, imageFiles []*multipart.FileHeader, fileDistribution []int) error BulkUploadImagesMultiple(devices []req.BulkDeviceImageUploadDTO, imageFiles []*multipart.FileHeader, fileDistribution []int) error
ValidateTowerExists(towerID uuid.UUID) (bool, error) ValidateTowerExists(towerID uuid.UUID) (bool, error)
ValidateOLTExists(oltID uuid.UUID) (bool, error)
} }
type deviceUseCase struct { type deviceUseCase struct {
deviceRepo repository.DevicesRepo deviceRepo repository.DevicesRepo
oltRepo repository.OLTRepo
validate *validator.Validate validate *validator.Validate
geocoder service.GeocodingService geocoder service.GeocodingService
} }
func NewDeviceUseCase(deviceRepo repository.DevicesRepo, geocoder service.GeocodingService) DeviceUseCase { func NewDeviceUseCase(deviceRepo repository.DevicesRepo,oltRepo repository.OLTRepo ,geocoder service.GeocodingService) DeviceUseCase {
return &deviceUseCase{ return &deviceUseCase{
deviceRepo: deviceRepo, deviceRepo: deviceRepo,
oltRepo: oltRepo,
geocoder: geocoder, geocoder: geocoder,
validate: validator.New(), validate: validator.New(),
} }
} }
func (u *deviceUseCase) ValidateOLTExists(oltID uuid.UUID) (bool, error) {
_, err := u.oltRepo.GetByID(oltID)
if err != nil {
return false, nil
}
return true, nil
}
func (u *deviceUseCase) ValidateTowerExists(towerID uuid.UUID) (bool, error) { func (u *deviceUseCase) ValidateTowerExists(towerID uuid.UUID) (bool, error) {
return u.deviceRepo.ValidateTowerExists(towerID) return u.deviceRepo.ValidateTowerExists(towerID)
} }
@ -60,6 +72,32 @@ func (u *deviceUseCase) CreateDeviceWithMultipleImages(device req.DeviceDTO, ima
} }
} }
if device.TowerID != nil {
towerExists, err := u.ValidateTowerExists(*device.TowerID)
if err != nil {
return fmt.Errorf("failed to validate tower: %w", err)
}
if !towerExists {
return fmt.Errorf("tower with ID %s not found", device.TowerID.String())
}
}
if device.OLTID != nil {
// Only ODP devices can be assigned to OLT
if device.DeviceType != "ODP" {
return fmt.Errorf("only ODP devices can be assigned to OLT")
}
oltExists, err := u.ValidateOLTExists(*device.OLTID)
if err != nil {
return fmt.Errorf("failed to validate OLT: %w", err)
}
if !oltExists {
return fmt.Errorf("OLT with ID %s not found", device.OLTID.String())
}
}
var imageURLs []string var imageURLs []string
var primaryImageURL string var primaryImageURL string
@ -88,6 +126,7 @@ func (u *deviceUseCase) CreateDeviceWithMultipleImages(device req.DeviceDTO, ima
City: device.City, City: device.City,
District: device.District, District: device.District,
TowerID: device.TowerID, // Add TowerID field TowerID: device.TowerID, // Add TowerID field
OLTID: device.OLTID,
ImageURL: &primaryImageURL, // Primary image as pointer ImageURL: &primaryImageURL, // Primary image as pointer
ImageURLs: entity.StringSlice(imageURLs), // All images ImageURLs: entity.StringSlice(imageURLs), // All images
CreatedAt: time.Now(), CreatedAt: time.Now(),
@ -145,6 +184,34 @@ func (u *deviceUseCase) UpdateDeviceWithMultipleImages(id uuid.UUID, device req.
updates["TowerID"] = *device.TowerID updates["TowerID"] = *device.TowerID
} }
// Handle OLTID validation and update
if device.OLTID != nil {
// Get current device to check type
currentDevice, err := u.deviceRepo.GetByID(id)
if err != nil {
return fmt.Errorf("device not found: %w", err)
}
// Only ODP devices can be assigned to OLT
deviceType := string(currentDevice.DeviceType)
if device.DeviceType != nil {
deviceType = *device.DeviceType
}
if deviceType != "ODP" {
return fmt.Errorf("only ODP devices can be assigned to OLT")
}
oltExists, err := u.ValidateOLTExists(*device.OLTID)
if err != nil {
return fmt.Errorf("failed to validate OLT: %w", err)
}
if !oltExists {
return fmt.Errorf("OLT with ID %s not found", device.OLTID.String())
}
updates["OLTID"] = *device.OLTID
}
// Handle multiple image uploads // Handle multiple image uploads
if len(imageFiles) > 0 { if len(imageFiles) > 0 {
// Get current device to handle existing images // Get current device to handle existing images
@ -326,6 +393,36 @@ func (u *deviceUseCase) UpdateDevice(id uuid.UUID, device req.UpdateDeviceDTO) e
} }
updates["TowerID"] = *device.TowerID updates["TowerID"] = *device.TowerID
} }
// Handle OLTID validation and update
if device.OLTID != nil {
// Get current device to check type
currentDevice, err := u.deviceRepo.GetByID(id)
if err != nil {
return fmt.Errorf("device not found: %w", err)
}
// Only ODP devices can be assigned to OLT
deviceType := string(currentDevice.DeviceType)
if device.DeviceType != nil {
deviceType = *device.DeviceType
}
if deviceType != "ODP" {
return fmt.Errorf("only ODP devices can be assigned to OLT")
}
oltExists, err := u.ValidateOLTExists(*device.OLTID)
if err != nil {
return fmt.Errorf("failed to validate OLT: %w", err)
}
if !oltExists {
return fmt.Errorf("OLT with ID %s not found", device.OLTID.String())
}
updates["OLTID"] = *device.OLTID
}
if device.DeviceType != nil && (*device.DeviceType == "OTB" || *device.DeviceType == "ODP") && device.PortAmount != nil && *device.PortAmount <= 0 { if device.DeviceType != nil && (*device.DeviceType == "OTB" || *device.DeviceType == "ODP") && device.PortAmount != nil && *device.PortAmount <= 0 {
return fmt.Errorf("port amount must be greater than 0 for OTB or ODP devices") return fmt.Errorf("port amount must be greater than 0 for OTB or ODP devices")
} }

View File

@ -199,11 +199,13 @@ func ConvertToDeviceDetailsResponse(device entity.DeviceDetails, geocoder servic
if len(allImageURLs) == 0 { if len(allImageURLs) == 0 {
allImageURLs = []string{} // Return empty array instead of nil allImageURLs = []string{} // Return empty array instead of nil
} }
// Handle OLT information - create nested object structure
// Handle OLT information var oltInfo *res.OLTInfo
var oltName *string if device.OLTID != nil && device.OLT != nil && device.OLT.OLTName != "" {
if device.OLT != nil && device.OLT.OLTName != "" { oltInfo = &res.OLTInfo{
oltName = &device.OLT.OLTName OLTID: *device.OLTID,
OLTName: device.OLT.OLTName,
}
} }
response := res.DeviceDetailsResponse{ response := res.DeviceDetailsResponse{
@ -226,8 +228,7 @@ func ConvertToDeviceDetailsResponse(device entity.DeviceDetails, geocoder servic
ImageURLs: allImageURLs, ImageURLs: allImageURLs,
TowerID: device.TowerID, // Keep TowerID for reference TowerID: device.TowerID, // Keep TowerID for reference
// Remove AssignedTower field - now everything is in Towers // Remove AssignedTower field - now everything is in Towers
OLTID: device.OLTID, // Add OLTID OLT: oltInfo, // Add OLT name
OLTName: oltName, // Add OLT name
Backbones: backboneInfos, Backbones: backboneInfos,
Fishbones: fishboneInfos, Fishbones: fishboneInfos,
Towers: towerInfos, // All towers combined here Towers: towerInfos, // All towers combined here