package usecase import ( "fmt" "mime/multipart" "time" "users_management/m/model/dto/req" "users_management/m/model/dto/res" "users_management/m/model/entity" "users_management/m/repository" "users_management/m/utils/helper" "users_management/m/utils/service" "github.com/go-playground/validator/v10" "github.com/google/uuid" ) type DeviceUseCase interface { CreateDevice(device req.DeviceDTO) error GetAllDevices() ([]res.DeviceResponse, error) GetByID(id uuid.UUID) (res.DeviceResponse, error) UpdateDevice(id uuid.UUID, device req.UpdateDeviceDTO) error GetByType(deviceType string) ([]res.DeviceTypeResponse, error) BulkUploadImages(devices []req.BulkDeviceImageUploadDTO, imageFiles []*multipart.FileHeader) error CreateDeviceWithMultipleImages(device req.DeviceDTO, imageFiles []*multipart.FileHeader) error UpdateDeviceWithMultipleImages(id uuid.UUID, device req.UpdateDeviceDTO, imageFiles []*multipart.FileHeader, replaceImages ...bool) error BulkUploadImagesMultiple(devices []req.BulkDeviceImageUploadDTO, imageFiles []*multipart.FileHeader, fileDistribution []int) error } type deviceUseCase struct { deviceRepo repository.DevicesRepo validate *validator.Validate geocoder service.GeocodingService } func NewDeviceUseCase(deviceRepo repository.DevicesRepo, geocoder service.GeocodingService) DeviceUseCase { return &deviceUseCase{ deviceRepo: deviceRepo, geocoder: geocoder, validate: validator.New(), } } func (u *deviceUseCase) CreateDeviceWithMultipleImages(device req.DeviceDTO, imageFiles []*multipart.FileHeader) error { err := u.validate.Struct(device) if err != nil { return fmt.Errorf("validation error: %w", err) } if device.DeviceType == "OTB" || device.DeviceType == "ODP" { if device.PortAmount <= 0 { return fmt.Errorf("port amount must be greater than 0 for OTB or ODP devices") } } var imageURLs []string var primaryImageURL string if len(imageFiles) > 0 { // Save all images imageURLs, err = helper.SaveDeviceImagesBulk(imageFiles) if err != nil { return err } // First image becomes primary if len(imageURLs) > 0 && imageURLs[0] != "" { primaryImageURL = imageURLs[0] } } newDevice := entity.Device{ ID: uuid.New(), DeviceCode: device.DeviceCode, DeviceType: entity.DeviceType(device.DeviceType), Longitude: device.Longitude, Latitude: device.Latitude, PortAmount: device.PortAmount, Status: entity.DeviceStatus(device.Status), Province: device.Province, City: device.City, District: device.District, ImageURL: &primaryImageURL, // Primary image as pointer ImageURLs: entity.StringSlice(imageURLs), // All images CreatedAt: time.Now(), UpdatedAt: time.Now(), } return u.deviceRepo.Post(newDevice) } func (u *deviceUseCase) UpdateDeviceWithMultipleImages(id uuid.UUID, device req.UpdateDeviceDTO, imageFiles []*multipart.FileHeader, replaceImages ...bool) error { err := u.validate.Struct(device) if err != nil { return fmt.Errorf("validation error: %w", err) } updates := map[string]interface{}{} if device.DeviceCode != nil { updates["DeviceCode"] = *device.DeviceCode } if device.DeviceType != nil { updates["DeviceType"] = *device.DeviceType } if device.Longitude != nil { updates["Longitude"] = *device.Longitude } if device.Latitude != nil { updates["Latitude"] = *device.Latitude } if device.PortAmount != nil { updates["PortAmount"] = *device.PortAmount } if device.Status != nil { updates["Status"] = *device.Status } if device.Province != nil { updates["Province"] = *device.Province } if device.City != nil { updates["City"] = *device.City } if device.District != nil { updates["District"] = *device.District } // Handle multiple image uploads if len(imageFiles) > 0 { // Get current device to handle existing images currentDevice, err := u.deviceRepo.GetByID(id) if err != nil { return err } // Save new images newImageURLs, err := helper.SaveDeviceImagesBulk(imageFiles) if err != nil { return err } var finalImageURLs []string shouldReplace := len(replaceImages) > 0 && replaceImages[0] if shouldReplace { // Replace all images - delete old ones for _, oldImageURL := range currentDevice.GetAllImageURLs() { helper.DeleteDeviceImage(oldImageURL) } finalImageURLs = newImageURLs } else { // Append to existing images existingImages := currentDevice.GetAllImageURLs() finalImageURLs = append(existingImages, newImageURLs...) } // Update primary image (first image in the final list) if len(finalImageURLs) > 0 && finalImageURLs[0] != "" { updates["ImageURL"] = finalImageURLs[0] } // Update all images updates["ImageURLs"] = entity.StringSlice(finalImageURLs) } 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") } if len(updates) == 0 { return fmt.Errorf("no update data") } updates["UpdatedAt"] = time.Now() return u.deviceRepo.Update(id, updates) } func (u *deviceUseCase) BulkUploadImagesMultiple(devices []req.BulkDeviceImageUploadDTO, imageFiles []*multipart.FileHeader, fileDistribution []int) error { if len(devices) == 0 { return fmt.Errorf("no devices provided") } if len(fileDistribution) != len(devices) { return fmt.Errorf("file distribution count must match device count") } // Validate all device IDs exist for i, device := range devices { _, err := u.deviceRepo.GetByID(device.DeviceID) if err != nil { return fmt.Errorf("device %d with ID %s not found", i, device.DeviceID) } } // Save all images imageURLs, err := helper.SaveDeviceImagesBulk(imageFiles) if err != nil { return fmt.Errorf("failed to save images: %w", err) } // Distribute images to devices updates := make(map[uuid.UUID][]string) fileIndex := 0 for i, device := range devices { imageCount := fileDistribution[i] deviceImages := make([]string, 0) for j := 0; j < imageCount && fileIndex < len(imageURLs); j++ { if imageURLs[fileIndex] != "" { deviceImages = append(deviceImages, imageURLs[fileIndex]) } fileIndex++ } if len(deviceImages) > 0 { updates[device.DeviceID] = deviceImages } } // Update database return u.deviceRepo.BulkUpdateImagesMultiple(updates) } // Keep old methods for backward compatibility func (u *deviceUseCase) CreateDevice(device req.DeviceDTO) error { return u.CreateDeviceWithMultipleImages(device, nil) } func (u *deviceUseCase) GetAllDevices() ([]res.DeviceResponse, error) { devices, err := u.deviceRepo.GetAll() if err != nil { return []res.DeviceResponse{}, err } devicesResponse,err := helper.ConvertToDeviceResponse(devices, u.geocoder) if err != nil { return []res.DeviceResponse{}, err } return devicesResponse, nil } func (u *deviceUseCase) BulkUploadImages(devices []req.BulkDeviceImageUploadDTO, imageFiles []*multipart.FileHeader) error { // Default to one image per device fileDistribution := make([]int, len(devices)) for i := range fileDistribution { fileDistribution[i] = 1 } return u.BulkUploadImagesMultiple(devices, imageFiles, fileDistribution) } func (u *deviceUseCase) GetByID(id uuid.UUID) (res.DeviceResponse, error) { device, err := u.deviceRepo.GetByID(id) if err != nil { return res.DeviceResponse{}, err } deviceResp, err := helper.ConvertToDeviceResponseId(device, u.geocoder) if err != nil { return res.DeviceResponse{}, err } return deviceResp, nil } func (u *deviceUseCase) UpdateDevice(id uuid.UUID, device req.UpdateDeviceDTO) error { err := u.validate.Struct(device) if err != nil { return fmt.Errorf("validation error: %w", err) } updates := map[string]interface{}{} if device.DeviceCode != nil { updates["DeviceCode"] = *device.DeviceCode } if device.DeviceType != nil { updates["DeviceType"] = *device.DeviceType } if device.Longitude != nil { updates["Longitude"] = *device.Longitude } if device.Latitude != nil { updates["Latitude"] = *device.Latitude } if device.PortAmount != nil { updates["PortAmount"] = *device.PortAmount } if device.Status != nil { updates["Status"] = *device.Status } if device.Province != nil { updates["Province"] = *device.Province } if device.City != nil { updates["City"] = *device.City } if device.District != nil { updates["District"] = *device.District } 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") } if len(updates) == 0 { return fmt.Errorf("no update data") } updates["UpdatedAt"] = time.Now() return u.deviceRepo.Update(id, updates) } func (u *deviceUseCase) GetByType(deviceType string) ([]res.DeviceTypeResponse, error) { devices, err := u.deviceRepo.GetByType(deviceType) if err != nil { return nil, err } deviceTypeResponses := helper.ConvertToDeviceTypeResponse(devices) return deviceTypeResponses, nil }