package controller import ( "encoding/json" "fmt" "mime/multipart" "net/http" "strconv" "strings" "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 } func (dc *DeviceController) Route() { rg := dc.rg.Group("/devices") rg.Use(middleware.RequireAnyRole("Teknisi", "Admin", "Superadmin")) { rg.POST("", dc.CreateDevice()) rg.GET("", dc.GetAllDevices()) rg.GET("/:uuid", dc.GetDeviceByID()) rg.PUT("/:uuid", dc.UpdateDevice()) rg.POST("/bulk-upload-images", dc.BulkUploadImages()) } } func NewDeviceController(du usecase.DeviceUseCase, rg *gin.RouterGroup) *DeviceController { return &DeviceController{ du: du, rg: rg, } } 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 } } // Create DTO deviceDTO := req.DeviceDTO{ DeviceCode: deviceCode, DeviceType: deviceType, Longitude: longitude, Latitude: latitude, PortAmount: portAmount, Status: status, } // 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") uuid, err := uuid.Parse(id) 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(uuid, deviceDTO) if err != nil { common.ErrorResponses(c, http.StatusBadRequest, "Device not found") 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 } // 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(uuid, deviceUpdateDTO, imageFiles, replaceImages) if err != nil { common.ErrorResponses(c, http.StatusBadRequest, err.Error()) return } common.SingleResponses(c, "Device has been updated", nil) } }