Skip to content

Commit 5c9afcf

Browse files
authored
Data Import/Export API: Extend import API (#1377)
* Refactor: Support background Import uploads * Extend coverage for import entities * Extend Import API Three major changes here; - Support async import by adding import jobs. - Extend Import API to support more core mpm entities - Update Import UI on settings page * Apply prettier lint fixes * Link available devices to assigned customers on import * Handle edge cases for imporrs * Review improvements * Fix import result responses
1 parent d22be2a commit 5c9afcf

45 files changed

Lines changed: 2701 additions & 212 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use App\Http\Requests\ApplianceImportRequest;
6+
use App\Http\Resources\ApiResource;
7+
use App\Jobs\ImportJob;
8+
use App\Services\ImportServices\ApplianceImportService;
9+
use Illuminate\Http\JsonResponse;
10+
use Illuminate\Support\Facades\Cache;
11+
use Illuminate\Support\Str;
12+
13+
class ApplianceImportController extends Controller {
14+
private const ASYNC_THRESHOLD = 50;
15+
private const CACHE_TTL_SECONDS = 3600;
16+
17+
public function __construct(
18+
private ApplianceImportService $applianceImportService,
19+
) {}
20+
21+
public function import(ApplianceImportRequest $request): JsonResponse|ApiResource {
22+
$data = $request->input('data');
23+
24+
if (isset($data['data']) && is_array($data['data'])) {
25+
$data = $data['data'];
26+
}
27+
28+
if (count($data) >= self::ASYNC_THRESHOLD) {
29+
return $this->dispatchAsync($data, $request);
30+
}
31+
32+
$result = $this->applianceImportService->import($data);
33+
34+
if (!$result['success'] && isset($result['errors'])) {
35+
return response()->json([
36+
'success' => false,
37+
'errors' => $result['errors'],
38+
], 422);
39+
}
40+
41+
return ApiResource::make($result);
42+
}
43+
44+
/**
45+
* @param array<int, array<string, mixed>> $data
46+
*/
47+
private function dispatchAsync(array $data, ApplianceImportRequest $request): JsonResponse {
48+
$companyId = (int) $request->attributes->get('companyId');
49+
$jobId = Str::uuid()->toString();
50+
51+
Cache::put("import:{$companyId}:{$jobId}", [
52+
'job_id' => $jobId,
53+
'status' => 'pending',
54+
'type' => 'appliances',
55+
'total' => count($data),
56+
'result' => null,
57+
'error' => null,
58+
'created_at' => now()->toISOString(),
59+
'completed_at' => null,
60+
], self::CACHE_TTL_SECONDS);
61+
62+
dispatch(new ImportJob($companyId, $jobId, ApplianceImportService::class, $data));
63+
64+
return response()->json([
65+
'data' => [
66+
'job_id' => $jobId,
67+
'status' => 'pending',
68+
'message' => 'Import queued for background processing',
69+
],
70+
], 202);
71+
}
72+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use App\Http\Requests\ClusterImportRequest;
6+
use App\Http\Resources\ApiResource;
7+
use App\Jobs\ImportJob;
8+
use App\Services\ImportServices\ClusterImportService;
9+
use Illuminate\Http\JsonResponse;
10+
use Illuminate\Support\Facades\Cache;
11+
use Illuminate\Support\Str;
12+
13+
class ClusterImportController extends Controller {
14+
private const ASYNC_THRESHOLD = 50;
15+
private const CACHE_TTL_SECONDS = 3600;
16+
17+
public function __construct(
18+
private ClusterImportService $clusterImportService,
19+
) {}
20+
21+
public function import(ClusterImportRequest $request): JsonResponse|ApiResource {
22+
$data = $request->input('data');
23+
24+
if (isset($data['data']) && is_array($data['data'])) {
25+
$data = $data['data'];
26+
}
27+
28+
if (count($data) >= self::ASYNC_THRESHOLD) {
29+
return $this->dispatchAsync($data, $request);
30+
}
31+
32+
$result = $this->clusterImportService->import($data);
33+
34+
if (!$result['success'] && isset($result['errors'])) {
35+
return response()->json([
36+
'success' => false,
37+
'errors' => $result['errors'],
38+
], 422);
39+
}
40+
41+
return ApiResource::make($result);
42+
}
43+
44+
/**
45+
* @param array<int, array<string, mixed>> $data
46+
*/
47+
private function dispatchAsync(array $data, ClusterImportRequest $request): JsonResponse {
48+
$companyId = (int) $request->attributes->get('companyId');
49+
$jobId = Str::uuid()->toString();
50+
51+
Cache::put("import:{$companyId}:{$jobId}", [
52+
'job_id' => $jobId,
53+
'status' => 'pending',
54+
'type' => 'clusters',
55+
'total' => count($data),
56+
'result' => null,
57+
'error' => null,
58+
'created_at' => now()->toISOString(),
59+
'completed_at' => null,
60+
], self::CACHE_TTL_SECONDS);
61+
62+
dispatch(new ImportJob($companyId, $jobId, ClusterImportService::class, $data));
63+
64+
return response()->json([
65+
'data' => [
66+
'job_id' => $jobId,
67+
'status' => 'pending',
68+
'message' => 'Import queued for background processing',
69+
],
70+
], 202);
71+
}
72+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use App\Http\Requests\CustomerImportRequest;
6+
use App\Http\Resources\ApiResource;
7+
use App\Jobs\ImportJob;
8+
use App\Services\ImportServices\CustomerImportService;
9+
use Illuminate\Http\JsonResponse;
10+
use Illuminate\Support\Facades\Cache;
11+
use Illuminate\Support\Str;
12+
13+
class CustomerImportController extends Controller {
14+
private const ASYNC_THRESHOLD = 50;
15+
private const CACHE_TTL_SECONDS = 3600;
16+
17+
public function __construct(
18+
private CustomerImportService $customerImportService,
19+
) {}
20+
21+
public function import(CustomerImportRequest $request): JsonResponse|ApiResource {
22+
$data = $request->input('data');
23+
24+
if (isset($data['data']) && is_array($data['data'])) {
25+
$data = $data['data'];
26+
}
27+
28+
if (count($data) >= self::ASYNC_THRESHOLD) {
29+
return $this->dispatchAsync($data, $request);
30+
}
31+
32+
$result = $this->customerImportService->import($data);
33+
34+
if (!$result['success'] && isset($result['errors'])) {
35+
return response()->json([
36+
'success' => false,
37+
'errors' => $result['errors'],
38+
], 422);
39+
}
40+
41+
return ApiResource::make($result);
42+
}
43+
44+
/**
45+
* @param array<int, array<string, mixed>> $data
46+
*/
47+
private function dispatchAsync(array $data, CustomerImportRequest $request): JsonResponse {
48+
$companyId = (int) $request->attributes->get('companyId');
49+
$jobId = Str::uuid()->toString();
50+
51+
Cache::put("import:{$companyId}:{$jobId}", [
52+
'job_id' => $jobId,
53+
'status' => 'pending',
54+
'type' => 'customers',
55+
'total' => count($data),
56+
'result' => null,
57+
'error' => null,
58+
'created_at' => now()->toISOString(),
59+
'completed_at' => null,
60+
], self::CACHE_TTL_SECONDS);
61+
62+
dispatch(new ImportJob($companyId, $jobId, CustomerImportService::class, $data));
63+
64+
return response()->json([
65+
'data' => [
66+
'job_id' => $jobId,
67+
'status' => 'pending',
68+
'message' => 'Import queued for background processing',
69+
],
70+
], 202);
71+
}
72+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use App\Http\Requests\DeviceImportRequest;
6+
use App\Http\Resources\ApiResource;
7+
use App\Jobs\ImportJob;
8+
use App\Services\ImportServices\DeviceImportService;
9+
use Illuminate\Http\JsonResponse;
10+
use Illuminate\Support\Facades\Cache;
11+
use Illuminate\Support\Str;
12+
13+
class DeviceImportController extends Controller {
14+
private const ASYNC_THRESHOLD = 50;
15+
private const CACHE_TTL_SECONDS = 3600;
16+
17+
public function __construct(
18+
private DeviceImportService $deviceImportService,
19+
) {}
20+
21+
public function import(DeviceImportRequest $request): JsonResponse|ApiResource {
22+
$data = $request->input('data');
23+
24+
if (isset($data['data']) && is_array($data['data'])) {
25+
$data = $data['data'];
26+
}
27+
28+
if (count($data) >= self::ASYNC_THRESHOLD) {
29+
return $this->dispatchAsync($data, $request);
30+
}
31+
32+
$result = $this->deviceImportService->import($data);
33+
34+
if (!$result['success'] && isset($result['errors'])) {
35+
return response()->json([
36+
'success' => false,
37+
'errors' => $result['errors'],
38+
], 422);
39+
}
40+
41+
return ApiResource::make($result);
42+
}
43+
44+
/**
45+
* @param array<int, array<string, mixed>> $data
46+
*/
47+
private function dispatchAsync(array $data, DeviceImportRequest $request): JsonResponse {
48+
$companyId = (int) $request->attributes->get('companyId');
49+
$jobId = Str::uuid()->toString();
50+
51+
Cache::put("import:{$companyId}:{$jobId}", [
52+
'job_id' => $jobId,
53+
'status' => 'pending',
54+
'type' => 'devices',
55+
'total' => count($data),
56+
'result' => null,
57+
'error' => null,
58+
'created_at' => now()->toISOString(),
59+
'completed_at' => null,
60+
], self::CACHE_TTL_SECONDS);
61+
62+
dispatch(new ImportJob($companyId, $jobId, DeviceImportService::class, $data));
63+
64+
return response()->json([
65+
'data' => [
66+
'job_id' => $jobId,
67+
'status' => 'pending',
68+
'message' => 'Import queued for background processing',
69+
],
70+
], 202);
71+
}
72+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use Illuminate\Http\JsonResponse;
6+
use Illuminate\Http\Request;
7+
use Illuminate\Support\Facades\Cache;
8+
9+
class ImportStatusController extends Controller {
10+
public function show(string $jobId, Request $request): JsonResponse {
11+
$companyId = (int) $request->attributes->get('companyId');
12+
$cacheKey = "import:{$companyId}:{$jobId}";
13+
14+
$status = Cache::get($cacheKey);
15+
16+
if ($status === null) {
17+
return response()->json(['message' => 'Import job not found'], 404);
18+
}
19+
20+
return response()->json(['data' => $status]);
21+
}
22+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use App\Http\Requests\TransactionImportRequest;
6+
use App\Http\Resources\ApiResource;
7+
use App\Jobs\ImportJob;
8+
use App\Services\ImportServices\TransactionImportService;
9+
use Illuminate\Http\JsonResponse;
10+
use Illuminate\Support\Facades\Cache;
11+
use Illuminate\Support\Str;
12+
13+
class TransactionImportController extends Controller {
14+
private const ASYNC_THRESHOLD = 50;
15+
private const CACHE_TTL_SECONDS = 3600;
16+
17+
public function __construct(
18+
private TransactionImportService $transactionImportService,
19+
) {}
20+
21+
public function import(TransactionImportRequest $request): JsonResponse|ApiResource {
22+
$data = $request->input('data');
23+
24+
if (isset($data['data']) && is_array($data['data'])) {
25+
$data = $data['data'];
26+
}
27+
28+
if (count($data) >= self::ASYNC_THRESHOLD) {
29+
return $this->dispatchAsync($data, $request);
30+
}
31+
32+
$result = $this->transactionImportService->import($data);
33+
34+
if (!$result['success'] && isset($result['errors'])) {
35+
return response()->json([
36+
'success' => false,
37+
'errors' => $result['errors'],
38+
], 422);
39+
}
40+
41+
return ApiResource::make($result);
42+
}
43+
44+
/**
45+
* @param array<int, array<string, mixed>> $data
46+
*/
47+
private function dispatchAsync(array $data, TransactionImportRequest $request): JsonResponse {
48+
$companyId = (int) $request->attributes->get('companyId');
49+
$jobId = Str::uuid()->toString();
50+
51+
Cache::put("import:{$companyId}:{$jobId}", [
52+
'job_id' => $jobId,
53+
'status' => 'pending',
54+
'type' => 'transactions',
55+
'total' => count($data),
56+
'result' => null,
57+
'error' => null,
58+
'created_at' => now()->toISOString(),
59+
'completed_at' => null,
60+
], self::CACHE_TTL_SECONDS);
61+
62+
dispatch(new ImportJob($companyId, $jobId, TransactionImportService::class, $data));
63+
64+
return response()->json([
65+
'data' => [
66+
'job_id' => $jobId,
67+
'status' => 'pending',
68+
'message' => 'Import queued for background processing',
69+
],
70+
], 202);
71+
}
72+
}

0 commit comments

Comments
 (0)