Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
88.16% covered (warning)
88.16%
67 / 76
66.67% covered (warning)
66.67%
6 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
ImportController
88.16% covered (warning)
88.16%
67 / 76
66.67% covered (warning)
66.67%
6 / 9
15.37
0.00% covered (danger)
0.00%
0 / 1
 index
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 store
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 show
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 updateMapping
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
2.26
 confirm
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 retry
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
2.26
 report
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 findJob
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 serialize
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare(strict_types=1);
4
5namespace ImovelZapAi\Importing\Presentation\Http\Controllers\Api;
6
7use App\Http\Controllers\Controller;
8use Illuminate\Http\JsonResponse;
9use Illuminate\Http\Request;
10use ImovelZapAi\Importing\Application\Actions\ConfirmImportAction;
11use ImovelZapAi\Importing\Application\Actions\RetryImportAction;
12use ImovelZapAi\Importing\Application\Actions\UpdateImportMappingAction;
13use ImovelZapAi\Importing\Application\Actions\UploadImportAction;
14use ImovelZapAi\Importing\Application\Queries\GetImportReportQuery;
15use ImovelZapAi\Importing\Application\Queries\ListImportsQuery;
16use ImovelZapAi\Importing\Domain\Exceptions\ImportException;
17use ImovelZapAi\Importing\Infrastructure\Persistence\Eloquent\ImportJobModel;
18use ImovelZapAi\Importing\Presentation\Http\Requests\StoreImportRequest;
19use ImovelZapAi\Importing\Presentation\Http\Requests\UpdateImportMappingRequest;
20use ImovelZapAi\Tenancy\Support\TenantContext;
21
22final class ImportController extends Controller
23{
24    public function index(Request $request, ListImportsQuery $query): JsonResponse
25    {
26        $paginator = $query->execute((string) TenantContext::id(), [
27            'status' => $request->query('status'),
28            'per_page' => (int) $request->query('per_page', 15),
29        ]);
30
31        return response()->json($paginator);
32    }
33
34    public function store(StoreImportRequest $request, UploadImportAction $action): JsonResponse
35    {
36        $job = $action->execute(
37            (string) TenantContext::id(),
38            $request->user(),
39            $request->file('file'),
40            $request->validated('idempotency_key'),
41        );
42
43        return response()->json($this->serialize($job), 201);
44    }
45
46    public function show(string $id): JsonResponse
47    {
48        $job = $this->findJob($id);
49
50        return response()->json($this->serialize($job->load(['file', 'errors'])));
51    }
52
53    public function updateMapping(
54        string $id,
55        UpdateImportMappingRequest $request,
56        UpdateImportMappingAction $action,
57    ): JsonResponse {
58        $job = $this->findJob($id);
59
60        try {
61            $updated = $action->execute($job, $request->validated());
62        } catch (ImportException $e) {
63            return response()->json(['message' => $e->getMessage()], 422);
64        }
65
66        return response()->json($this->serialize($updated));
67    }
68
69    public function confirm(string $id, ConfirmImportAction $action): JsonResponse
70    {
71        $job = $this->findJob($id);
72
73        try {
74            $queued = $action->execute($job);
75        } catch (ImportException $e) {
76            return response()->json(['message' => $e->getMessage()], 422);
77        }
78
79        return response()->json($this->serialize($queued), 202);
80    }
81
82    public function retry(string $id, RetryImportAction $action): JsonResponse
83    {
84        $job = $this->findJob($id);
85
86        try {
87            $queued = $action->execute($job);
88        } catch (ImportException $e) {
89            return response()->json(['message' => $e->getMessage()], 422);
90        }
91
92        return response()->json($this->serialize($queued), 202);
93    }
94
95    public function report(string $id, GetImportReportQuery $query): JsonResponse
96    {
97        $job = $this->findJob($id);
98
99        return response()->json($query->execute($job));
100    }
101
102    private function findJob(string $id): ImportJobModel
103    {
104        return ImportJobModel::query()
105            ->where('tenant_id', TenantContext::id())
106            ->whereKey($id)
107            ->firstOrFail();
108    }
109
110    /**
111     * @return array<string, mixed>
112     */
113    private function serialize(ImportJobModel $job): array
114    {
115        return [
116            'id' => $job->id,
117            'status' => $job->status->value ?? (string) $job->status,
118            'format' => $job->format,
119            'total_listings' => $job->total_listings,
120            'valid_listings' => $job->valid_listings,
121            'error_listings' => $job->error_listings,
122            'warning_listings' => $job->warning_listings,
123            'imported_count' => $job->imported_count,
124            'updated_count' => $job->updated_count,
125            'unchanged_count' => $job->unchanged_count,
126            'ignored_count' => $job->ignored_count,
127            'failed_count' => $job->failed_count,
128            'photos_imported' => $job->photos_imported,
129            'checkpoint_index' => $job->checkpoint_index,
130            'progress_current' => $job->progress_current,
131            'progress_total' => $job->progress_total,
132            'mapping' => $job->mapping,
133            'preview_summary' => $job->preview_summary,
134            'report_summary' => $job->report_summary,
135            'failure_message' => $job->failure_message,
136            'file' => $job->relationLoaded('file') && $job->file !== null ? [
137                'original_name' => $job->file->original_name,
138                'size_bytes' => $job->file->size_bytes,
139                'sha256' => $job->file->sha256,
140                'mime_type' => $job->file->mime_type,
141            ] : null,
142            'errors' => $job->relationLoaded('errors')
143                ? $job->errors->map(static fn ($error) => [
144                    'id' => $error->id,
145                    'severity' => $error->severity->value ?? (string) $error->severity,
146                    'code' => $error->code,
147                    'external_id' => $error->external_id,
148                    'listing_index' => $error->listing_index,
149                    'path' => $error->path,
150                    'message' => $error->message,
151                    'status' => $error->status,
152                    'resolution' => $error->resolution,
153                ])->all()
154                : null,
155            'created_at' => $job->created_at?->toIso8601String(),
156            'completed_at' => $job->completed_at?->toIso8601String(),
157        ];
158    }
159}