package controller import ( "encoding/json" "fmt" "mime/multipart" "net/http" "strconv" "strings" "users_management/m/config" "users_management/m/middleware" "users_management/m/model/dto/req" "users_management/m/usecase" "users_management/m/utils/common" "github.com/gin-gonic/gin" "github.com/google/uuid" ) type DeviceController struct { du usecase.DeviceUseCase rg *gin.RouterGroup cfg *config.Config } func NewDeviceController(du usecase.DeviceUseCase, rg *gin.RouterGroup, cfg *config.Config) *DeviceController { return &DeviceController{ du: du, rg: rg, cfg: cfg, } } func (dc *DeviceController) Route() { rg := dc.rg.Group("/devices") rg.Use(middleware.ConditionalRequireAnyRole(dc.cfg, "Teknisi", "Admin", "Super Admin")) { rg.POST("", dc.CreateDevice()) rg.GET("", dc.GetAllDevices()) rg.GET("/:uuid", dc.GetDeviceByID()) rg.PUT("/:uuid", dc.UpdateDevice()) // Bulk operations rg.POST("/bulk-create", dc.BulkCreateDevices()) rg.PUT("/bulk-update", dc.BulkUpdateDevices()) rg.DELETE("/bulk-delete", dc.BulkDeleteDevices()) rg.POST("/bulk-upload-images", dc.BulkUploadImages()) } } func (dc *DeviceController) BulkUploadImages() gin.HandlerFunc { return func(c *gin.Context) { // Parse multipart form err := c.Request.ParseMultipartForm(100 << 20) // 100MB for bulk upload if err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Failed to parse multipart form") return } // Get device data from form deviceDataStr := c.PostForm("devices") if deviceDataStr == "" { common.ErrorResponses(c, http.StatusBadRequest, "devices data is required") return } // Parse device data var devicesData []req.BulkDeviceImageUploadDTO if err := json.Unmarshal([]byte(deviceDataStr), &devicesData); err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Invalid devices data format") return } // Get all image files form := c.Request.MultipartForm allFiles := form.File["images"] if len(allFiles) == 0 { common.ErrorResponses(c, http.StatusBadRequest, "No image files provided") return } // Parse file distribution from form data fileDistributionStr := c.PostForm("file_distribution") var fileDistribution []int if fileDistributionStr != "" { if err := json.Unmarshal([]byte(fileDistributionStr), &fileDistribution); err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Invalid file_distribution format") return } } else { // Default: distribute files evenly filesPerDevice := len(allFiles) / len(devicesData) remainder := len(allFiles) % len(devicesData) fileDistribution = make([]int, len(devicesData)) for i := range fileDistribution { fileDistribution[i] = filesPerDevice if i < remainder { fileDistribution[i]++ } } } // Validate file distribution if len(fileDistribution) != len(devicesData) { common.ErrorResponses(c, http.StatusBadRequest, fmt.Sprintf("File distribution count (%d) must match device count (%d)", len(fileDistribution), len(devicesData))) return } totalExpectedFiles := 0 for _, count := range fileDistribution { totalExpectedFiles += count } if totalExpectedFiles != len(allFiles) { common.ErrorResponses(c, http.StatusBadRequest, fmt.Sprintf("Total files (%d) must match file distribution sum (%d)", len(allFiles), totalExpectedFiles)) return } // Call use case err = dc.du.BulkUploadImagesMultiple(devicesData, allFiles, fileDistribution) if err != nil { common.ErrorResponses(c, http.StatusBadRequest, err.Error()) return } totalImages := len(allFiles) common.SingleResponses(c, fmt.Sprintf("%d images uploaded successfully for %d devices", totalImages, len(devicesData)), nil) } } func (dc *DeviceController) CreateDevice() gin.HandlerFunc { return func(c *gin.Context) { contentType := c.GetHeader("Content-Type") // Handle JSON request (no images) if strings.Contains(contentType, "application/json") { var deviceDTO req.DeviceDTO err := c.ShouldBindJSON(&deviceDTO) if err != nil { common.ErrorResponses(c, http.StatusBadRequest, err.Error()) return } err = dc.du.CreateDevice(deviceDTO) if err != nil { common.ErrorResponses(c, http.StatusBadRequest, err.Error()) return } common.SingleResponses(c, "Device has been created", nil) return } // Parse multipart form err := c.Request.ParseMultipartForm(50 << 20) // 50MB for multiple images if err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Failed to parse multipart form") return } // Extract form data for device deviceCode := c.PostForm("device_code") deviceType := c.PostForm("device_type") longitudeStr := c.PostForm("longitude") latitudeStr := c.PostForm("latitude") portAmountStr := c.PostForm("port_amount") status := c.PostForm("status") province := c.PostForm("province") city := c.PostForm("city") district := c.PostForm("district") // Validate required fields if deviceCode == "" || deviceType == "" || longitudeStr == "" || latitudeStr == "" || status == "" { common.ErrorResponses(c, http.StatusBadRequest, "Missing required fields") return } // Parse coordinates longitude, err := strconv.ParseFloat(longitudeStr, 64) if err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Invalid longitude") return } latitude, err := strconv.ParseFloat(latitudeStr, 64) if err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Invalid latitude") return } // Parse port amount portAmount := 0 if portAmountStr != "" { portAmount, err = strconv.Atoi(portAmountStr) if err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Invalid port amount") return } } 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 deviceDTO := req.DeviceDTO{ DeviceCode: deviceCode, DeviceType: deviceType, Longitude: longitude, Latitude: latitude, PortAmount: portAmount, Status: status, TowerID: towerID, // Add TowerID OLTID: oltID, // Add OLTID } // Handle optional string fields if province != "" { deviceDTO.Province = &province } if city != "" { deviceDTO.City = &city } if district != "" { deviceDTO.District = &district } // Get multiple image files form := c.Request.MultipartForm imageFiles := form.File["images"] // Multiple images // Also support single image upload for backward compatibility if len(imageFiles) == 0 { if singleImage, err := c.FormFile("image"); err == nil { imageFiles = []*multipart.FileHeader{singleImage} } } err = dc.du.CreateDeviceWithMultipleImages(deviceDTO, imageFiles) if err != nil { common.ErrorResponses(c, http.StatusBadRequest, err.Error()) return } common.SingleResponses(c, "Device has been created", nil) } } func (dc *DeviceController) GetAllDevices() gin.HandlerFunc { return func(c *gin.Context) { deviceType := c.Query("type") if deviceType != "" { deviceResp, err := dc.du.GetByType(deviceType) if err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Device type not found") return } common.SingleResponses(c, "Success", deviceResp) return } devices, err := dc.du.GetAllDevices() if err != nil { common.ErrorResponses(c, http.StatusBadRequest, "something went wrong") return } common.SingleResponses(c, "Success", devices) } } func (dc *DeviceController) GetDeviceByID() gin.HandlerFunc { return func(c *gin.Context) { id := c.Param("uuid") uuid, err := uuid.Parse(id) if err != nil { common.ErrorResponses(c, http.StatusBadGateway, "Invalid UUID") return } device, err := dc.du.GetByID(uuid) if err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Device not found") return } common.SingleResponses(c, "Success", device) } } func (dc *DeviceController) UpdateDevice() gin.HandlerFunc { return func(c *gin.Context) { id := c.Param("uuid") deviceUUID, err := uuid.Parse(id) // Change variable name from 'uuid' to 'deviceUUID' if err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Invalid UUID") return } contentType := c.GetHeader("Content-Type") // Handle JSON request (no images) if strings.Contains(contentType, "application/json") { var deviceDTO req.UpdateDeviceDTO err = c.ShouldBindJSON(&deviceDTO) if err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Invalid request") return } err = dc.du.UpdateDevice(deviceUUID, deviceDTO) // Use the new variable name if err != nil { common.ErrorResponses(c, http.StatusBadRequest, err.Error()) // Also fix error message return } common.SingleResponses(c, "Device has been updated", nil) return } // Handle multipart form request err = c.Request.ParseMultipartForm(50 << 20) // 50MB for multiple images if err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Failed to parse multipart form") return } // Create update DTO from form data deviceUpdateDTO := req.UpdateDeviceDTO{} if deviceCode := c.PostForm("device_code"); deviceCode != "" { deviceUpdateDTO.DeviceCode = &deviceCode } if deviceType := c.PostForm("device_type"); deviceType != "" { deviceUpdateDTO.DeviceType = &deviceType } if longitudeStr := c.PostForm("longitude"); longitudeStr != "" { if longitude, err := strconv.ParseFloat(longitudeStr, 64); err == nil { deviceUpdateDTO.Longitude = &longitude } else { common.ErrorResponses(c, http.StatusBadRequest, "Invalid longitude format") return } } if latitudeStr := c.PostForm("latitude"); latitudeStr != "" { if latitude, err := strconv.ParseFloat(latitudeStr, 64); err == nil { deviceUpdateDTO.Latitude = &latitude } else { common.ErrorResponses(c, http.StatusBadRequest, "Invalid latitude format") return } } if portAmountStr := c.PostForm("port_amount"); portAmountStr != "" { if portAmount, err := strconv.Atoi(portAmountStr); err == nil { deviceUpdateDTO.PortAmount = &portAmount } else { common.ErrorResponses(c, http.StatusBadRequest, "Invalid port amount format") return } } if status := c.PostForm("status"); status != "" { deviceUpdateDTO.Status = &status } if province := c.PostForm("province"); province != "" { deviceUpdateDTO.Province = &province } if city := c.PostForm("city"); city != "" { deviceUpdateDTO.City = &city } if district := c.PostForm("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 form := c.Request.MultipartForm imageFiles := form.File["images"] // Support multiple images // Also support single image upload for backward compatibility if len(imageFiles) == 0 { if singleImage, err := c.FormFile("image"); err == nil { imageFiles = []*multipart.FileHeader{singleImage} } } // Handle replace_images flag replaceImages := c.PostForm("replace_images") == "true" err = dc.du.UpdateDeviceWithMultipleImages(deviceUUID, deviceUpdateDTO, imageFiles, replaceImages) // Use the new variable name if err != nil { common.ErrorResponses(c, http.StatusBadRequest, err.Error()) return } common.SingleResponses(c, "Device has been updated", nil) } } // BulkCreateDevices creates multiple devices at once func (dc *DeviceController) BulkCreateDevices() gin.HandlerFunc { return func(c *gin.Context) { contentType := c.GetHeader("Content-Type") // Handle multipart/form-data with images if strings.Contains(contentType, "multipart/form-data") { err := c.Request.ParseMultipartForm(100 << 20) // 100MB for bulk images if err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Failed to parse multipart form") return } // Parse devices JSON from form field devicesJSON := c.PostForm("devices") if devicesJSON == "" { common.ErrorResponses(c, http.StatusBadRequest, "Devices data is required") return } var request req.BulkCreateDeviceDTO if err := json.Unmarshal([]byte(devicesJSON), &request); err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Invalid devices JSON: "+err.Error()) return } // Parse image_indexes from form field imageIndexesJSON := c.PostForm("image_indexes") if imageIndexesJSON == "" { common.ErrorResponses(c, http.StatusBadRequest, "image_indexes is required") return } var imageIndexes []int if err := json.Unmarshal([]byte(imageIndexesJSON), &imageIndexes); err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Invalid image_indexes JSON: "+err.Error()) return } // Get all image files form := c.Request.MultipartForm imageFiles := form.File["images"] // Call usecase with images result, err := dc.du.BulkCreateDevicesWithImages(request, imageFiles, imageIndexes) if err != nil { common.ErrorResponses(c, http.StatusBadRequest, err.Error()) return } message := fmt.Sprintf("Bulk create completed: %d successful, %d failed out of %d requested (with %d images)", result.Successful, result.Failed, result.TotalRequested, len(imageFiles)) common.SingleResponses(c, message, result) return } // Handle JSON request (no images) var request req.BulkCreateDeviceDTO if err := c.ShouldBindJSON(&request); err != nil { common.ErrorResponses(c, http.StatusBadRequest, err.Error()) return } result, err := dc.du.BulkCreateDevices(request) if err != nil { common.ErrorResponses(c, http.StatusBadRequest, err.Error()) return } message := fmt.Sprintf("Bulk create completed: %d successful, %d failed out of %d requested", result.Successful, result.Failed, result.TotalRequested) common.SingleResponses(c, message, result) } } // BulkUpdateDevices updates multiple devices with the same values func (dc *DeviceController) BulkUpdateDevices() gin.HandlerFunc { return func(c *gin.Context) { contentType := c.GetHeader("Content-Type") // Handle multipart/form-data with images if strings.Contains(contentType, "multipart/form-data") { err := c.Request.ParseMultipartForm(100 << 20) // 100MB for bulk images if err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Failed to parse multipart form") return } // Parse device_ids JSON from form field deviceIDsJSON := c.PostForm("device_ids") if deviceIDsJSON == "" { common.ErrorResponses(c, http.StatusBadRequest, "device_ids is required") return } var deviceIDs []uuid.UUID if err := json.Unmarshal([]byte(deviceIDsJSON), &deviceIDs); err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Invalid device_ids JSON: "+err.Error()) return } // Parse updates JSON from form field updatesJSON := c.PostForm("updates") if updatesJSON == "" { common.ErrorResponses(c, http.StatusBadRequest, "updates is required") return } var updates req.UpdateDeviceDTO if err := json.Unmarshal([]byte(updatesJSON), &updates); err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Invalid updates JSON: "+err.Error()) return } // Parse image_indexes from form field imageIndexesJSON := c.PostForm("image_indexes") if imageIndexesJSON == "" { common.ErrorResponses(c, http.StatusBadRequest, "image_indexes is required") return } var imageIndexes []int if err := json.Unmarshal([]byte(imageIndexesJSON), &imageIndexes); err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Invalid image_indexes JSON: "+err.Error()) return } // Get replace_images flag replaceImages := c.PostForm("replace_images") == "true" // Get all image files form := c.Request.MultipartForm imageFiles := form.File["images"] // Build request request := req.BulkUpdateDeviceDTO{ DeviceIDs: deviceIDs, Updates: updates, } // Call usecase with images result, err := dc.du.BulkUpdateDevicesWithImages(request, imageFiles, imageIndexes, replaceImages) if err != nil { common.ErrorResponses(c, http.StatusBadRequest, err.Error()) return } message := fmt.Sprintf("Bulk update completed: %d successful, %d failed out of %d requested (with %d images)", result.Successful, result.Failed, result.TotalRequested, len(imageFiles)) common.SingleResponses(c, message, result) return } // Handle JSON request (no images) var request req.BulkUpdateDeviceDTO if err := c.ShouldBindJSON(&request); err != nil { common.ErrorResponses(c, http.StatusBadRequest, err.Error()) return } result, err := dc.du.BulkUpdateDevices(request) if err != nil { common.ErrorResponses(c, http.StatusBadRequest, err.Error()) return } message := fmt.Sprintf("Bulk update completed: %d successful, %d failed out of %d requested", result.Successful, result.Failed, result.TotalRequested) common.SingleResponses(c, message, result) } } // BulkDeleteDevices deletes multiple devices func (dc *DeviceController) BulkDeleteDevices() gin.HandlerFunc { return func(c *gin.Context) { var request req.BulkDeleteDeviceDTO if err := c.ShouldBindJSON(&request); err != nil { common.ErrorResponses(c, http.StatusBadRequest, err.Error()) return } result, err := dc.du.BulkDeleteDevices(request) if err != nil { common.ErrorResponses(c, http.StatusBadRequest, err.Error()) return } message := fmt.Sprintf("Bulk delete completed: %d successful, %d failed out of %d requested", result.Successful, result.Failed, result.TotalRequested) common.SingleResponses(c, message, result) } }