package controller import ( "bytes" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "strconv" "strings" "users_management/m/config" "users_management/m/middleware" "users_management/m/model/dto/req" "users_management/m/model/dto/res" "users_management/m/usecase" "users_management/m/utils/common" "github.com/gin-gonic/gin" "github.com/google/uuid" ) type DeviceDetailsController struct { deviceDetailsUC usecase.DeviceDetailsUseCase rg *gin.RouterGroup cfg *config.Config // Add config field for middleware } func NewDeviceDetailsController(deviceDetailsUC usecase.DeviceDetailsUseCase, rg *gin.RouterGroup, cfg *config.Config) *DeviceDetailsController { return &DeviceDetailsController{ deviceDetailsUC: deviceDetailsUC, rg: rg, cfg: cfg, } } func (c *DeviceDetailsController) Route() { deviceDetails := c.rg.Group("/device-details") deviceDetails.Use(middleware.ConditionalRequireAnyRole(c.cfg, "Teknisi", "Admin", "Super Admin")) { deviceDetails.GET("", c.getAllDeviceDetails) deviceDetails.POST("", c.createDeviceDetails) deviceDetails.GET("/:id", c.getDeviceDetailsByID) deviceDetails.PUT("/:id", c.updateDeviceDetails) deviceDetails.DELETE("/:id", c.deleteDeviceDetails) deviceDetails.POST("/:id/recalculate-ports", c.recalculatePortUsage) deviceDetails.POST("/:id/assign-customer", c.assignCustomer) deviceDetails.PUT("/:id/port-usage", c.updatePortUsage) // New endpoint deviceDetails.PUT("/:id/port-assignments", c.updatePortAssignments) // New endpoint deviceDetails.PUT("/:id/update-customer-by-port", c.updateCustomerByPort) // Updated endpoint name deviceDetails.PUT("/:id/bulk-update-customers-by-port", c.bulkUpdateCustomersByPort) // Updated endpoint name deviceDetails.DELETE("/:id/remove-customer-by-port", c.removeCustomerByPort) // delete images by filename deviceDetails.DELETE("/:id/images/:filename", c.deleteDeviceImage) deviceDetails.GET("/without-towers", c.getDevicesWithoutTowers) deviceDetails.GET("/without-connections", c.getDevicesWithoutConnections) } } func (c *DeviceDetailsController) getDevicesWithoutConnections(ctx *gin.Context) { // Get device types from query parameters (optional) deviceTypesParam := ctx.Query("device_types") var deviceTypes []string if deviceTypesParam != "" { // Split by comma if multiple types provided deviceTypes = strings.Split(deviceTypesParam, ",") // Trim whitespace and normalize case for i, dt := range deviceTypes { trimmed := strings.TrimSpace(dt) if strings.ToUpper(trimmed) == "OTB" { deviceTypes[i] = "OTB" } else if strings.ToLower(trimmed) == "closure" { deviceTypes[i] = "closure" } else { deviceTypes[i] = trimmed } } } devices, err := c.deviceDetailsUC.GetDevicesWithoutConnections(deviceTypes) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } // Categorize devices by type for better response structure closureDevices := make([]res.DeviceDetailsResponse, 0) otbDevices := make([]res.DeviceDetailsResponse, 0) for _, device := range devices { if device.DeviceType == "closure" { closureDevices = append(closureDevices, device) } else if device.DeviceType == "OTB" { otbDevices = append(otbDevices, device) } } response := gin.H{ "devices": devices, "total": len(devices), "breakdown": gin.H{ "closure": gin.H{ "count": len(closureDevices), "devices": closureDevices, }, "otb": gin.H{ "count": len(otbDevices), "devices": otbDevices, }, }, "filter": gin.H{ "device_types": deviceTypes, "criteria": "without_fishbones_and_backbones", }, } common.SingleResponses(ctx, "Devices without connections retrieved successfully", response) } func (c *DeviceDetailsController) getDevicesWithoutTowers(ctx *gin.Context) { // Get device types from query parameters (optional) deviceTypesParam := ctx.Query("device_types") var deviceTypes []string if deviceTypesParam != "" { // Split by comma if multiple types provided deviceTypes = strings.Split(deviceTypesParam, ",") // Trim whitespace for i, dt := range deviceTypes { deviceTypes[i] = strings.TrimSpace(strings.ToUpper(dt)) } } devices, err := c.deviceDetailsUC.GetDevicesWithoutTowers(deviceTypes) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } response := gin.H{ "devices": devices, "total": len(devices), "filter": gin.H{ "device_types": deviceTypes, "without_towers": true, }, } common.SingleResponses(ctx, "Devices without towers retrieved successfully", response) } func (c *DeviceDetailsController) deleteDeviceImage(ctx *gin.Context) { // Parse device ID id := ctx.Param("id") deviceID, err := uuid.Parse(id) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID") return } // Get filename from URL parameter filename := ctx.Param("filename") if filename == "" { common.ErrorResponses(ctx, http.StatusBadRequest, "Filename is required") return } // Validate filename format (basic security check) if strings.Contains(filename, "..") || strings.Contains(filename, "/") || strings.Contains(filename, "\\") { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid filename format") return } // Call use case to delete the image err = c.deviceDetailsUC.DeleteDeviceImage(deviceID, filename) if err != nil { if err.Error() == "device not found" { common.ErrorResponses(ctx, http.StatusNotFound, err.Error()) return } if err.Error() == "image not found in device" { common.ErrorResponses(ctx, http.StatusNotFound, err.Error()) return } common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } common.SingleResponses(ctx, fmt.Sprintf("Image %s deleted successfully", filename), nil) } func (c *DeviceDetailsController) updateCustomerByPort(ctx *gin.Context) { id := ctx.Param("id") deviceID, err := uuid.Parse(id) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID") return } var request req.UpdateCustomerByPortDTO if err := ctx.ShouldBindJSON(&request); err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } err = c.deviceDetailsUC.UpdateCustomerByPort(deviceID, request) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } if request.NewCustomerName == nil { common.SingleResponses(ctx, fmt.Sprintf("Customer removed from port %d successfully", request.PortNumber), nil) } else { common.SingleResponses(ctx, fmt.Sprintf("Customer assigned to port %d successfully", request.PortNumber), nil) } } func (c *DeviceDetailsController) bulkUpdateCustomersByPort(ctx *gin.Context) { id := ctx.Param("id") deviceID, err := uuid.Parse(id) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID") return } var request req.BulkUpdateCustomersByPortDTO if err := ctx.ShouldBindJSON(&request); err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } err = c.deviceDetailsUC.BulkUpdateCustomersByPort(deviceID, request.Updates) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } common.SingleResponses(ctx, fmt.Sprintf("%d port assignments updated successfully", len(request.Updates)), nil) } func (c *DeviceDetailsController) removeCustomerByPort(ctx *gin.Context) { id := ctx.Param("id") deviceID, err := uuid.Parse(id) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID") return } var request req.RemoveCustomerByPortDTO if err := ctx.ShouldBindJSON(&request); err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } err = c.deviceDetailsUC.RemoveCustomerByPort(deviceID, request.PortNumber) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } common.SingleResponses(ctx, fmt.Sprintf("Customer removed from port %d successfully", request.PortNumber), nil) } func (c *DeviceDetailsController) assignCustomer(ctx *gin.Context) { id := ctx.Param("id") deviceID, err := uuid.Parse(id) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID") return } // Try to parse as array first var arrayRequest []req.AssignMultipleCustomersDTO // Try to parse as single object var singleRequest struct { CustomerName string `json:"customer_name" binding:"required"` PortNumber *int `json:"port_number,omitempty"` } // Get raw JSON to determine the structure rawData, err := ctx.GetRawData() if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid JSON data") return } // Reset the request body for subsequent parsing ctx.Request.Body = io.NopCloser(bytes.NewBuffer(rawData)) // Check if it's an array by looking at the first character trimmed := bytes.TrimSpace(rawData) if len(trimmed) > 0 && trimmed[0] == '[' { // It's an array if err := json.Unmarshal(rawData, &arrayRequest); err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid array format: "+err.Error()) return } // Process array of assignments err = c.deviceDetailsUC.AssignMultipleCustomersToPort(deviceID, arrayRequest) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } common.SingleResponses(ctx, fmt.Sprintf("%d customers assigned successfully", len(arrayRequest)), nil) } else { // It's a single object if err := json.Unmarshal(rawData, &singleRequest); err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid object format: "+err.Error()) return } // Process single assignment err = c.deviceDetailsUC.AssignCustomerToPort(deviceID, singleRequest.CustomerName, singleRequest.PortNumber) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } common.SingleResponses(ctx, "Customer assigned successfully", nil) } } func (c *DeviceDetailsController) getAllDeviceDetails(ctx *gin.Context) { devices, err := c.deviceDetailsUC.GetAllDeviceDetails() if err != nil { common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error()) return } common.SingleResponses(ctx, "Device details retrieved successfully", devices) } func (c *DeviceDetailsController) createDeviceDetails(ctx *gin.Context) { var request req.DeviceDetailsDTO if err := ctx.ShouldBindJSON(&request); err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } err := c.deviceDetailsUC.CreateDeviceDetails(request) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } common.SingleResponses(ctx, "Device details created successfully", nil) } func (c *DeviceDetailsController) getDeviceDetailsByID(ctx *gin.Context) { id := ctx.Param("id") deviceID, err := uuid.Parse(id) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID") return } device, err := c.deviceDetailsUC.GetDeviceDetailsByID(deviceID) if err != nil { common.ErrorResponses(ctx, http.StatusNotFound, err.Error()) return } common.SingleResponses(ctx, "Device details retrieved successfully", device) } func (c *DeviceDetailsController) updateDeviceDetails(ctx *gin.Context) { id := ctx.Param("id") deviceID, err := uuid.Parse(id) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID") return } contentType := ctx.GetHeader("Content-Type") // Handle JSON request (no images) if strings.Contains(contentType, "application/json") { var request req.UpdateDeviceDetailsDTO if err := ctx.ShouldBindJSON(&request); err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } err = c.deviceDetailsUC.UpdateDeviceDetails(deviceID, request) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } common.SingleResponses(ctx, "Device details updated successfully", nil) return } // Handle multipart form request err = ctx.Request.ParseMultipartForm(50 << 20) // 50MB for multiple images if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, "Failed to parse multipart form") return } // Create update DTO from form data deviceUpdateDTO := req.UpdateDeviceDetailsDTO{} if deviceCode := ctx.PostForm("device_code"); deviceCode != "" { deviceUpdateDTO.DeviceCode = &deviceCode } if deviceType := ctx.PostForm("device_type"); deviceType != "" { deviceUpdateDTO.DeviceType = &deviceType } if longitudeStr := ctx.PostForm("longitude"); longitudeStr != "" { if longitude, err := strconv.ParseFloat(longitudeStr, 64); err == nil { deviceUpdateDTO.Longitude = &longitude } else { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid longitude format") return } } if latitudeStr := ctx.PostForm("latitude"); latitudeStr != "" { if latitude, err := strconv.ParseFloat(latitudeStr, 64); err == nil { deviceUpdateDTO.Latitude = &latitude } else { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid latitude format") return } } if portAmountStr := ctx.PostForm("port_amount"); portAmountStr != "" { if portAmount, err := strconv.Atoi(portAmountStr); err == nil { deviceUpdateDTO.PortAmount = &portAmount } else { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid port amount format") return } } if status := ctx.PostForm("status"); status != "" { deviceUpdateDTO.Status = &status } if region := ctx.PostForm("region"); region != "" { deviceUpdateDTO.Region = ®ion } if province := ctx.PostForm("province"); province != "" { deviceUpdateDTO.Province = &province } if city := ctx.PostForm("city"); city != "" { deviceUpdateDTO.City = &city } if district := ctx.PostForm("district"); district != "" { deviceUpdateDTO.District = &district } // Handle TowerID if towerIDStr := ctx.PostForm("tower_id"); towerIDStr != "" { if towerID, err := uuid.Parse(towerIDStr); err == nil { deviceUpdateDTO.TowerID = &towerID } else { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid tower ID format") return } } if oltIDStr := ctx.PostForm("olt_id"); oltIDStr != "" { if oltID, err := uuid.Parse(oltIDStr); err == nil { deviceUpdateDTO.OLTID = &oltID } else { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid OLT ID format") return } } // Get multiple image files form := ctx.Request.MultipartForm imageFiles := form.File["images"] // Support multiple images // Also support single image upload for backward compatibility if len(imageFiles) == 0 { if singleImage, err := ctx.FormFile("image"); err == nil { imageFiles = []*multipart.FileHeader{singleImage} } } // Handle replace_images flag replaceImages := ctx.PostForm("replace_images") == "true" err = c.deviceDetailsUC.UpdateDeviceDetailsWithMultipleImages(deviceID, deviceUpdateDTO, imageFiles, replaceImages) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } common.SingleResponses(ctx, "Device details updated successfully", nil) } func (c *DeviceDetailsController) deleteDeviceDetails(ctx *gin.Context) { id := ctx.Param("id") deviceID, err := uuid.Parse(id) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID") return } err = c.deviceDetailsUC.DeleteDeviceDetails(deviceID) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } common.SingleResponses(ctx, "Device details deleted successfully", nil) } func (c *DeviceDetailsController) recalculatePortUsage(ctx *gin.Context) { id := ctx.Param("id") deviceID, err := uuid.Parse(id) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID") return } err = c.deviceDetailsUC.RecalculatePortUsage(deviceID) if err != nil { common.ErrorResponses(ctx, http.StatusInternalServerError, err.Error()) return } common.SingleResponses(ctx, "Port usage recalculated successfully", nil) } func (c *DeviceDetailsController) updatePortUsage(ctx *gin.Context) { id := ctx.Param("id") deviceID, err := uuid.Parse(id) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID") return } var request req.UpdatePortUsageDTO if err := ctx.ShouldBindJSON(&request); err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } err = c.deviceDetailsUC.UpdatePortUsage(deviceID, request.PortUsed) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } common.SingleResponses(ctx, "Port usage updated successfully", nil) } func (c *DeviceDetailsController) updatePortAssignments(ctx *gin.Context) { id := ctx.Param("id") deviceID, err := uuid.Parse(id) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, "Invalid device ID") return } var request req.UpdatePortAssignmentsDTO if err := ctx.ShouldBindJSON(&request); err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } err = c.deviceDetailsUC.UpdatePortAssignments(deviceID, request.PortAssignments) if err != nil { common.ErrorResponses(ctx, http.StatusBadRequest, err.Error()) return } common.SingleResponses(ctx, "Port assignments updated successfully", nil) }