NAM-APJATEL-BACKEND/DEVICE_BULK_IMAGES_GUIDE.md

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 objects
  • image_indexes (JSON array) - Number of images for each device
  • images (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 UUIDs
  • updates (JSON object) - Fields to update
  • image_indexes (JSON array) - Number of images for each device
  • images (files) - All image files in sequence
  • replace_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_indexes MUST equal number of devices/device_ids
  • Sum of image_indexes MUST 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

  1. Device Type: Must be "ODP", "OTB", or "closure"
  2. Port Amount: Required and > 0 for ODP and OTB devices
  3. Status: Must be "active", "inactive", or "maintenance"
  4. OLT Assignment: Only ODP devices can be assigned to OLT
  5. Tower: Must exist in the database if provided
  6. OLT: Must exist in the database if provided

Image Validation

  1. File Size: Maximum 5MB per image
  2. File Type: Only .jpg, .jpeg, .png, .webp
  3. Image Distribution: Must match the formula: sum(image_indexes) == len(images)
  4. Index Length: len(image_indexes) == len(devices) or len(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_urls array (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

  1. Invalid image_indexes length
{
  "error": "image_indexes length (3) must match devices length (2)"
}
  1. Image count mismatch
{
  "error": "total images (5) doesn't match sum of image_indexes (4)"
}
  1. Invalid device type
{
  "data": {
    "errors": [
      {
        "index": 0,
        "error": "Invalid device type",
        "details": "Type must be ODP, OTB, or closure, got: INVALID"
      }
    ]
  }
}
  1. 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

  1. Limit Batch Size: Keep requests under 50 devices for optimal performance
  2. Validate Before Upload: Verify device data before sending
  3. Use Proper Image Formats: Stick to JPG/PNG for best compatibility
  4. Handle Partial Failures: Check the errors array in response
  5. Image Naming: Use descriptive filenames before upload (for debugging)
  6. Test Image Distribution: Verify image_indexes sum matches image count

Testing with Postman

Setup

  1. Set URL: http://localhost:8080/device/bulk/create
  2. Method: POST
  3. Authorization: Bearer Token
  4. 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! 🚀