14 KiB
Device Bulk Operations with Images - Complete Guide
Overview
This guide explains how to use bulk create and update operations for devices with support for multiple images per device. The system allows you to upload images along with device data in a single request.
Features
✅ Bulk Create with Images - Create multiple devices, each with multiple images ✅ Bulk Update with Images - Update multiple devices and add/replace images ✅ Flexible Image Distribution - Each device can have a different number of images ✅ Transaction Safety - All operations are database transaction-safe ✅ Individual Validation - Each device is validated separately ✅ Detailed Error Reporting - Get specific errors for failed items ✅ Image Management - Replace or append images to existing devices
Endpoints
1. Bulk Create Devices (without images)
Endpoint: POST /device/bulk/create
Content-Type: application/json
{
"devices": [
{
"device_code": "ODP-001",
"device_type": "ODP",
"longitude": 106.8456,
"latitude": -6.2088,
"port_amount": 8,
"status": "active",
"province": "DKI Jakarta",
"city": "Jakarta Selatan",
"district": "Kebayoran Baru",
"tower_id": "123e4567-e89b-12d3-a456-426614174000",
"olt_id": "123e4567-e89b-12d3-a456-426614174001"
},
{
"device_code": "OTB-001",
"device_type": "OTB",
"longitude": 106.8556,
"latitude": -6.2188,
"port_amount": 16,
"status": "active",
"province": "DKI Jakarta",
"city": "Jakarta Selatan"
}
]
}
2. Bulk Create Devices (with images)
Endpoint: POST /device/bulk/create
Content-Type: multipart/form-data
Form Fields:
devices(JSON string) - Array of device objectsimage_indexes(JSON array) - Number of images for each deviceimages(files) - All image files in sequence
Example cURL:
curl -X POST http://localhost:8080/device/bulk/create \
-H "Authorization: Bearer YOUR_TOKEN" \
-F 'devices=[{"device_code":"ODP-001","device_type":"ODP","longitude":106.8456,"latitude":-6.2088,"port_amount":8,"status":"active","province":"DKI Jakarta","city":"Jakarta Selatan"}]' \
-F 'image_indexes=[2]' \
-F "images=@device1_photo1.jpg" \
-F "images=@device1_photo2.jpg"
JavaScript/Fetch Example:
const formData = new FormData();
// Device data
const devices = [
{
device_code: "ODP-001",
device_type: "ODP",
longitude: 106.8456,
latitude: -6.2088,
port_amount: 8,
status: "active",
province: "DKI Jakarta",
city: "Jakarta Selatan"
},
{
device_code: "OTB-001",
device_type: "OTB",
longitude: 106.8556,
latitude: -6.2188,
port_amount: 16,
status: "active"
}
];
formData.append('devices', JSON.stringify(devices));
// Image distribution: first device has 2 images, second has 1 image
formData.append('image_indexes', JSON.stringify([2, 1]));
// Add images in order
formData.append('images', file1); // for device 0, image 1
formData.append('images', file2); // for device 0, image 2
formData.append('images', file3); // for device 1, image 1
const response = await fetch('/device/bulk/create', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_TOKEN'
},
body: formData
});
3. Bulk Update Devices (without images)
Endpoint: PUT /device/bulk/update
Content-Type: application/json
{
"device_ids": [
"123e4567-e89b-12d3-a456-426614174000",
"123e4567-e89b-12d3-a456-426614174001"
],
"updates": {
"status": "maintenance",
"province": "DKI Jakarta"
}
}
4. Bulk Update Devices (with images)
Endpoint: PUT /device/bulk/update
Content-Type: multipart/form-data
Form Fields:
device_ids(JSON array) - Array of device UUIDsupdates(JSON object) - Fields to updateimage_indexes(JSON array) - Number of images for each deviceimages(files) - All image files in sequencereplace_images(boolean string) - "true" to replace, "false" to append (default: false)
Example cURL:
curl -X PUT http://localhost:8080/device/bulk/update \
-H "Authorization: Bearer YOUR_TOKEN" \
-F 'device_ids=["123e4567-e89b-12d3-a456-426614174000","123e4567-e89b-12d3-a456-426614174001"]' \
-F 'updates={"status":"active"}' \
-F 'image_indexes=[1,2]' \
-F 'replace_images=false' \
-F "images=@new_image1.jpg" \
-F "images=@new_image2.jpg" \
-F "images=@new_image3.jpg"
JavaScript/Fetch Example:
const formData = new FormData();
const deviceIds = [
"123e4567-e89b-12d3-a456-426614174000",
"123e4567-e89b-12d3-a456-426614174001"
];
const updates = {
status: "active",
province: "DKI Jakarta"
};
formData.append('device_ids', JSON.stringify(deviceIds));
formData.append('updates', JSON.stringify(updates));
formData.append('image_indexes', JSON.stringify([1, 2])); // 1 image for first device, 2 for second
formData.append('replace_images', 'false'); // Append to existing images
formData.append('images', newFile1); // for device 0
formData.append('images', newFile2); // for device 1, image 1
formData.append('images', newFile3); // for device 1, image 2
const response = await fetch('/device/bulk/update', {
method: 'PUT',
headers: {
'Authorization': 'Bearer YOUR_TOKEN'
},
body: formData
});
Image Distribution Logic
The image_indexes array controls how images are distributed to devices:
// Example 1: Create 3 devices with different image counts
devices = [device1, device2, device3]
image_indexes = [2, 0, 3] // device1: 2 images, device2: 0 images, device3: 3 images
images = [img1, img2, img3, img4, img5] // Total: 5 images (2+0+3)
// Distribution:
// device1 gets: [img1, img2]
// device2 gets: []
// device3 gets: [img3, img4, img5]
// Example 2: Update 2 devices
device_ids = [uuid1, uuid2]
image_indexes = [1, 1] // Each device gets 1 image
images = [img1, img2] // Total: 2 images (1+1)
// Distribution:
// uuid1 gets: [img1]
// uuid2 gets: [img2]
Important Rules:
- Length of
image_indexesMUST equal number of devices/device_ids - Sum of
image_indexesMUST equal total number of image files - Images are assigned in order based on the index distribution
Response Format
Success Response
{
"message": "Bulk create completed: 3 successful, 1 failed out of 4 requested (with 7 images)",
"data": {
"total_requested": 4,
"successful": 3,
"failed": 1,
"errors": [
{
"index": 2,
"error": "Tower not found",
"details": "123e4567-e89b-12d3-a456-426614174000"
}
],
"results": [
{
"id": "123e4567-e89b-12d3-a456-426614174100",
"device_code": "ODP-001",
"device_type": "ODP",
"longitude": 106.8456,
"latitude": -6.2088,
"port_amount": 8,
"status": "active",
"address": "Jakarta Selatan, DKI Jakarta",
"province": "DKI Jakarta",
"city": "Jakarta Selatan",
"district": "Kebayoran Baru",
"image_url": "/uploads/devices/abc123.jpg",
"image_urls": [
"/uploads/devices/abc123.jpg",
"/uploads/devices/def456.jpg"
],
"created_at": "2025-10-10T10:30:00Z",
"updated_at": "2025-10-10T10:30:00Z"
}
],
"execution_time": "245ms"
}
}
Validation Rules
Device Validation
- Device Type: Must be "ODP", "OTB", or "closure"
- Port Amount: Required and > 0 for ODP and OTB devices
- Status: Must be "active", "inactive", or "maintenance"
- OLT Assignment: Only ODP devices can be assigned to OLT
- Tower: Must exist in the database if provided
- OLT: Must exist in the database if provided
Image Validation
- File Size: Maximum 5MB per image
- File Type: Only .jpg, .jpeg, .png, .webp
- Image Distribution: Must match the formula:
sum(image_indexes) == len(images) - Index Length:
len(image_indexes) == len(devices)orlen(device_ids)
Image Handling
Primary Image
- The first image for each device becomes the primary image (
image_url) - Primary image is displayed as the main device photo
Multiple Images
- All images are stored in
image_urlsarray (JSONB field) - Images are saved in
/uploads/devices/directory - Filenames are auto-generated with UUID + timestamp
Replace vs Append (Update only)
- Replace (
replace_images=true): Remove all existing images, add new ones - Append (
replace_images=false): Keep existing images, add new ones
Complete Examples
Example 1: Create 2 Devices with Images
const formData = new FormData();
// Device 1: ODP with 3 images
// Device 2: OTB with 1 image
const devices = [
{
device_code: "ODP-BULK-001",
device_type: "ODP",
longitude: 106.8456,
latitude: -6.2088,
port_amount: 8,
status: "active",
province: "DKI Jakarta",
city: "Jakarta Selatan",
olt_id: "550e8400-e29b-41d4-a716-446655440000"
},
{
device_code: "OTB-BULK-001",
device_type: "OTB",
longitude: 106.8556,
latitude: -6.2188,
port_amount: 16,
status: "active",
tower_id: "550e8400-e29b-41d4-a716-446655440001"
}
];
formData.append('devices', JSON.stringify(devices));
formData.append('image_indexes', JSON.stringify([3, 1])); // 3 images for first, 1 for second
// Add 4 total images (3+1)
formData.append('images', odpImage1);
formData.append('images', odpImage2);
formData.append('images', odpImage3);
formData.append('images', otbImage1);
fetch('/device/bulk/create', {
method: 'POST',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' },
body: formData
})
.then(res => res.json())
.then(data => console.log(data));
Example 2: Update Devices and Add Images
const formData = new FormData();
const deviceIds = [
"550e8400-e29b-41d4-a716-446655440010",
"550e8400-e29b-41d4-a716-446655440011"
];
const updates = {
status: "maintenance"
};
formData.append('device_ids', JSON.stringify(deviceIds));
formData.append('updates', JSON.stringify(updates));
formData.append('image_indexes', JSON.stringify([2, 0])); // Add 2 images to first device, 0 to second
formData.append('replace_images', 'false'); // Append to existing images
formData.append('images', newImage1);
formData.append('images', newImage2);
fetch('/device/bulk/update', {
method: 'PUT',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' },
body: formData
})
.then(res => res.json())
.then(data => console.log(data));
Example 3: No Images (JSON only)
For operations without images, use regular JSON:
// Create without images
fetch('/device/bulk/create', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
devices: [
{
device_code: "ODP-NO-IMG-001",
device_type: "ODP",
longitude: 106.8456,
latitude: -6.2088,
port_amount: 8,
status: "active"
}
]
})
});
// Update without images
fetch('/device/bulk/update', {
method: 'PUT',
headers: {
'Authorization': 'Bearer YOUR_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
device_ids: ["550e8400-e29b-41d4-a716-446655440010"],
updates: {
status: "active"
}
})
});
Error Handling
Common Errors
- Invalid image_indexes length
{
"error": "image_indexes length (3) must match devices length (2)"
}
- Image count mismatch
{
"error": "total images (5) doesn't match sum of image_indexes (4)"
}
- Invalid device type
{
"data": {
"errors": [
{
"index": 0,
"error": "Invalid device type",
"details": "Type must be ODP, OTB, or closure, got: INVALID"
}
]
}
}
- Image upload failed
{
"data": {
"errors": [
{
"index": 1,
"error": "Failed to save images",
"details": "file 0: file size exceeds 5MB limit"
}
]
}
}
Performance
- Batch Processing: 50 devices per database batch
- Transaction Safety: All creates/updates in transactions
- Typical Performance: 4-10x faster than individual requests
- Max File Size: 5MB per image
- Max Request Size: 100MB total (for bulk operations)
Authorization
All bulk operations require authentication and proper role:
- Roles Allowed: Teknisi, Admin, Super Admin
- Header:
Authorization: Bearer YOUR_JWT_TOKEN
Best Practices
- Limit Batch Size: Keep requests under 50 devices for optimal performance
- Validate Before Upload: Verify device data before sending
- Use Proper Image Formats: Stick to JPG/PNG for best compatibility
- Handle Partial Failures: Check the
errorsarray in response - Image Naming: Use descriptive filenames before upload (for debugging)
- Test Image Distribution: Verify
image_indexessum matches image count
Testing with Postman
Setup
- Set URL:
http://localhost:8080/device/bulk/create - Method:
POST - Authorization: Bearer Token
- Body:
form-data
Form Data Fields
| Key | Type | Value |
|---|---|---|
| devices | Text | [{"device_code":"TEST-001","device_type":"ODP","longitude":106.8,"latitude":-6.2,"port_amount":8,"status":"active"}] |
| image_indexes | Text | [2] |
| images | File | Select image 1 |
| images | File | Select image 2 |
Send Request
Click "Send" and verify response contains uploaded image URLs.
Summary
✅ Two modes: JSON (no images) or multipart/form-data (with images)
✅ Flexible distribution: Each device can have 0-N images
✅ Replace or append: Control image update behavior
✅ Transaction safe: Rollback on failures
✅ Detailed errors: Know exactly what failed and why
✅ High performance: Batch processing with GORM
Use bulk operations with images to efficiently manage large numbers of devices with their photos in a single request! 🚀