@fleetbase/solid-engine 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ACL_SOLUTION.md +72 -0
- package/CSS_SCOPE_ISSUE.md +140 -0
- package/HOTFIX_SYNTAX_ERROR.md +100 -0
- package/MANUAL_ACL_SETUP.md +135 -0
- package/REFACTORING_SUMMARY.md +330 -0
- package/VERIFICATION_CHECKLIST.md +82 -0
- package/addon/components/modals/create-solid-folder.hbs +29 -0
- package/addon/components/modals/import-solid-resources.hbs +85 -0
- package/addon/controllers/data/content.js +17 -0
- package/addon/controllers/data/index.js +219 -0
- package/addon/controllers/home.js +84 -0
- package/addon/engine.js +1 -24
- package/addon/extension.js +26 -0
- package/addon/routes/data/content.js +11 -0
- package/addon/routes/data/index.js +17 -0
- package/addon/routes.js +2 -7
- package/addon/styles/solid-engine.css +1 -2
- package/addon/templates/account.hbs +3 -3
- package/addon/templates/application.hbs +2 -12
- package/addon/templates/data/content.hbs +48 -0
- package/addon/templates/{pods/explorer.hbs → data/index.hbs} +6 -5
- package/addon/templates/home.hbs +168 -10
- package/app/components/modals/{backup-pod.js → create-solid-folder.js} +1 -1
- package/app/components/modals/{resync-pod.js → import-solid-resources.js} +1 -1
- package/app/components/modals/{create-pod.js → setup-css-credentials.js} +1 -1
- package/composer.json +4 -10
- package/extension.json +1 -1
- package/index.js +0 -11
- package/package.json +8 -8
- package/server/migrations/2024_12_21_add_css_credentials_to_solid_identities_table.php +32 -0
- package/server/src/Client/OpenIDConnectClient.php +686 -15
- package/server/src/Client/SolidClient.php +104 -8
- package/server/src/Http/Controllers/DataController.php +261 -0
- package/server/src/Http/Controllers/OIDCController.php +42 -8
- package/server/src/Http/Controllers/SolidController.php +179 -85
- package/server/src/Models/SolidIdentity.php +13 -3
- package/server/src/Services/AclService.php +146 -0
- package/server/src/Services/PodService.php +863 -0
- package/server/src/Services/ResourceSyncService.php +336 -0
- package/server/src/Services/VehicleSyncService.php +289 -0
- package/server/src/Support/Utils.php +10 -0
- package/server/src/routes.php +25 -1
- package/addon/components/modals/backup-pod.hbs +0 -3
- package/addon/components/modals/create-pod.hbs +0 -5
- package/addon/components/modals/resync-pod.hbs +0 -3
- package/addon/controllers/pods/explorer/content.js +0 -12
- package/addon/controllers/pods/explorer.js +0 -149
- package/addon/controllers/pods/index/pod.js +0 -12
- package/addon/controllers/pods/index.js +0 -137
- package/addon/routes/pods/explorer/content.js +0 -10
- package/addon/routes/pods/explorer.js +0 -44
- package/addon/routes/pods/index/pod.js +0 -3
- package/addon/routes/pods/index.js +0 -21
- package/addon/templates/pods/explorer/content.hbs +0 -19
- package/addon/templates/pods/index/pod.hbs +0 -11
- package/addon/templates/pods/index.hbs +0 -19
- package/server/src/LegacyClient/Identity/IdentityProvider.php +0 -174
- package/server/src/LegacyClient/Identity/Profile.php +0 -18
- package/server/src/LegacyClient/OIDCClient.php +0 -350
- package/server/src/LegacyClient/Profile/WebID.php +0 -26
- package/server/src/LegacyClient/SolidClient.php +0 -271
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\Solid\Services;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\Solid\Models\SolidIdentity;
|
|
6
|
+
use Fleetbase\Solid\Client\SolidClient;
|
|
7
|
+
use Illuminate\Support\Facades\Log;
|
|
8
|
+
|
|
9
|
+
class ResourceSyncService
|
|
10
|
+
{
|
|
11
|
+
/**
|
|
12
|
+
* Import resources into a pod.
|
|
13
|
+
*
|
|
14
|
+
* @param SolidIdentity $identity
|
|
15
|
+
* @param string $podUrl
|
|
16
|
+
* @param array $resourceTypes
|
|
17
|
+
* @return array
|
|
18
|
+
*/
|
|
19
|
+
public function importResources(SolidIdentity $identity, string $podUrl, array $resourceTypes): array
|
|
20
|
+
{
|
|
21
|
+
$imported = [];
|
|
22
|
+
$errors = [];
|
|
23
|
+
$totalCount = 0;
|
|
24
|
+
|
|
25
|
+
foreach ($resourceTypes as $resourceType) {
|
|
26
|
+
try {
|
|
27
|
+
Log::info('[IMPORTING RESOURCE TYPE]', ['type' => $resourceType, 'pod_url' => $podUrl]);
|
|
28
|
+
|
|
29
|
+
$result = $this->importResourceType($identity, $podUrl, $resourceType);
|
|
30
|
+
$imported[$resourceType] = $result['count'];
|
|
31
|
+
$totalCount += $result['count'];
|
|
32
|
+
|
|
33
|
+
Log::info('[RESOURCE TYPE IMPORTED]', [
|
|
34
|
+
'type' => $resourceType,
|
|
35
|
+
'count' => $result['count'],
|
|
36
|
+
]);
|
|
37
|
+
} catch (\Throwable $e) {
|
|
38
|
+
Log::error('[RESOURCE IMPORT ERROR]', [
|
|
39
|
+
'type' => $resourceType,
|
|
40
|
+
'error' => $e->getMessage(),
|
|
41
|
+
]);
|
|
42
|
+
$errors[$resourceType] = $e->getMessage();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return [
|
|
47
|
+
'imported' => $imported,
|
|
48
|
+
'errors' => $errors,
|
|
49
|
+
'total_count' => $totalCount,
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Import a specific resource type.
|
|
55
|
+
*
|
|
56
|
+
* @param SolidIdentity $identity
|
|
57
|
+
* @param string $podUrl
|
|
58
|
+
* @param string $resourceType
|
|
59
|
+
* @return array
|
|
60
|
+
*/
|
|
61
|
+
protected function importResourceType(SolidIdentity $identity, string $podUrl, string $resourceType): array
|
|
62
|
+
{
|
|
63
|
+
// Get resources from Fleetops
|
|
64
|
+
$resources = $this->getFleetopsResources($resourceType);
|
|
65
|
+
|
|
66
|
+
if (empty($resources)) {
|
|
67
|
+
return ['count' => 0];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Create container for this resource type
|
|
71
|
+
$containerUrl = rtrim($podUrl, '/') . '/' . $resourceType . '/';
|
|
72
|
+
$this->createContainer($identity, $containerUrl);
|
|
73
|
+
|
|
74
|
+
// Import each resource
|
|
75
|
+
$count = 0;
|
|
76
|
+
foreach ($resources as $resource) {
|
|
77
|
+
try {
|
|
78
|
+
$turtle = $this->convertToRDF($resourceType, $resource);
|
|
79
|
+
$resourceUrl = $containerUrl . $resource->public_id . '.ttl';
|
|
80
|
+
|
|
81
|
+
$this->storeResource($identity, $resourceUrl, $turtle);
|
|
82
|
+
$count++;
|
|
83
|
+
} catch (\Throwable $e) {
|
|
84
|
+
Log::warning('[RESOURCE IMPORT FAILED]', [
|
|
85
|
+
'type' => $resourceType,
|
|
86
|
+
'id' => $resource->public_id ?? 'unknown',
|
|
87
|
+
'error' => $e->getMessage(),
|
|
88
|
+
]);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return ['count' => $count];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get Fleetops resources by type.
|
|
97
|
+
*
|
|
98
|
+
* @param string $resourceType
|
|
99
|
+
* @return \Illuminate\Support\Collection
|
|
100
|
+
*/
|
|
101
|
+
protected function getFleetopsResources(string $resourceType)
|
|
102
|
+
{
|
|
103
|
+
$modelMap = [
|
|
104
|
+
'vehicles' => \Fleetbase\FleetOps\Models\Vehicle::class,
|
|
105
|
+
'drivers' => \Fleetbase\FleetOps\Models\Driver::class,
|
|
106
|
+
'contacts' => \Fleetbase\FleetOps\Models\Contact::class,
|
|
107
|
+
'orders' => \Fleetbase\FleetOps\Models\Order::class,
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
if (!isset($modelMap[$resourceType])) {
|
|
111
|
+
throw new \Exception("Unknown resource type: {$resourceType}");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
$modelClass = $modelMap[$resourceType];
|
|
115
|
+
|
|
116
|
+
// Get current company's resources
|
|
117
|
+
$companyId = session('company');
|
|
118
|
+
|
|
119
|
+
Log::info('[FETCHING FLEETOPS RESOURCES]', [
|
|
120
|
+
'resource_type' => $resourceType,
|
|
121
|
+
'model_class' => $modelClass,
|
|
122
|
+
'company_id' => $companyId,
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
if (!$companyId) {
|
|
126
|
+
Log::warning('[NO COMPANY ID]', ['session' => session()->all()]);
|
|
127
|
+
// Try to get from auth user
|
|
128
|
+
$user = auth()->user();
|
|
129
|
+
if ($user && isset($user->company_uuid)) {
|
|
130
|
+
$companyId = $user->company_uuid;
|
|
131
|
+
Log::info('[USING USER COMPANY]', ['company_id' => $companyId]);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!$companyId) {
|
|
136
|
+
Log::error('[CANNOT DETERMINE COMPANY]');
|
|
137
|
+
return collect([]);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
$query = $modelClass::where('company_uuid', $companyId)
|
|
141
|
+
->limit(100); // Limit for now to avoid overwhelming the pod
|
|
142
|
+
|
|
143
|
+
$count = $query->count();
|
|
144
|
+
Log::info('[RESOURCE QUERY]', [
|
|
145
|
+
'resource_type' => $resourceType,
|
|
146
|
+
'count' => $count,
|
|
147
|
+
'sql' => $query->toSql(),
|
|
148
|
+
'bindings' => $query->getBindings(),
|
|
149
|
+
]);
|
|
150
|
+
|
|
151
|
+
return $query->get();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Convert a resource to RDF/Turtle format.
|
|
156
|
+
*
|
|
157
|
+
* @param string $resourceType
|
|
158
|
+
* @param mixed $resource
|
|
159
|
+
* @return string
|
|
160
|
+
*/
|
|
161
|
+
protected function convertToRDF(string $resourceType, $resource): string
|
|
162
|
+
{
|
|
163
|
+
$baseUri = "http://fleetbase.io/ns/{$resourceType}/";
|
|
164
|
+
$resourceUri = $baseUri . $resource->public_id;
|
|
165
|
+
|
|
166
|
+
$turtle = "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n";
|
|
167
|
+
$turtle .= "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n";
|
|
168
|
+
$turtle .= "@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n";
|
|
169
|
+
$turtle .= "@prefix fb: <http://fleetbase.io/ns/> .\n";
|
|
170
|
+
$turtle .= "@prefix fb{$resourceType}: <{$baseUri}> .\n\n";
|
|
171
|
+
|
|
172
|
+
$turtle .= "<{$resourceUri}>\n";
|
|
173
|
+
$turtle .= " a fb:{$this->getResourceClass($resourceType)} ;\n";
|
|
174
|
+
$turtle .= " fb:id \"{$resource->public_id}\" ;\n";
|
|
175
|
+
|
|
176
|
+
// Add resource-specific properties
|
|
177
|
+
$properties = $this->getResourceProperties($resourceType, $resource);
|
|
178
|
+
foreach ($properties as $property => $value) {
|
|
179
|
+
if ($value !== null && $value !== '') {
|
|
180
|
+
$turtle .= " fb:{$property} " . $this->formatRDFValue($value) . " ;\n";
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Remove trailing semicolon and add period
|
|
185
|
+
$turtle = rtrim($turtle, " ;\n") . " .\n";
|
|
186
|
+
|
|
187
|
+
return $turtle;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Get RDF class name for resource type.
|
|
192
|
+
*
|
|
193
|
+
* @param string $resourceType
|
|
194
|
+
* @return string
|
|
195
|
+
*/
|
|
196
|
+
protected function getResourceClass(string $resourceType): string
|
|
197
|
+
{
|
|
198
|
+
return ucfirst(rtrim($resourceType, 's'));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get properties for a resource type.
|
|
203
|
+
*
|
|
204
|
+
* @param string $resourceType
|
|
205
|
+
* @param mixed $resource
|
|
206
|
+
* @return array
|
|
207
|
+
*/
|
|
208
|
+
protected function getResourceProperties(string $resourceType, $resource): array
|
|
209
|
+
{
|
|
210
|
+
switch ($resourceType) {
|
|
211
|
+
case 'vehicles':
|
|
212
|
+
return [
|
|
213
|
+
'name' => $resource->name,
|
|
214
|
+
'make' => $resource->make,
|
|
215
|
+
'model' => $resource->model,
|
|
216
|
+
'year' => $resource->year,
|
|
217
|
+
'vin' => $resource->vin,
|
|
218
|
+
'plate_number' => $resource->plate_number,
|
|
219
|
+
'status' => $resource->status,
|
|
220
|
+
'created_at' => $resource->created_at?->toIso8601String(),
|
|
221
|
+
'updated_at' => $resource->updated_at?->toIso8601String(),
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
case 'drivers':
|
|
225
|
+
return [
|
|
226
|
+
'name' => $resource->name,
|
|
227
|
+
'email' => $resource->email,
|
|
228
|
+
'phone' => $resource->phone,
|
|
229
|
+
'license_number' => $resource->drivers_license_number,
|
|
230
|
+
'status' => $resource->status,
|
|
231
|
+
'created_at' => $resource->created_at?->toIso8601String(),
|
|
232
|
+
'updated_at' => $resource->updated_at?->toIso8601String(),
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
case 'contacts':
|
|
236
|
+
return [
|
|
237
|
+
'name' => $resource->name,
|
|
238
|
+
'email' => $resource->email,
|
|
239
|
+
'phone' => $resource->phone,
|
|
240
|
+
'type' => $resource->type,
|
|
241
|
+
'created_at' => $resource->created_at?->toIso8601String(),
|
|
242
|
+
'updated_at' => $resource->updated_at?->toIso8601String(),
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
case 'orders':
|
|
246
|
+
return [
|
|
247
|
+
'tracking_number' => $resource->public_id,
|
|
248
|
+
'status' => $resource->status,
|
|
249
|
+
'type' => $resource->type,
|
|
250
|
+
'scheduled_at' => $resource->scheduled_at?->toIso8601String(),
|
|
251
|
+
'created_at' => $resource->created_at?->toIso8601String(),
|
|
252
|
+
'updated_at' => $resource->updated_at?->toIso8601String(),
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
default:
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Format a value for RDF.
|
|
262
|
+
*
|
|
263
|
+
* @param mixed $value
|
|
264
|
+
* @return string
|
|
265
|
+
*/
|
|
266
|
+
protected function formatRDFValue($value): string
|
|
267
|
+
{
|
|
268
|
+
if (is_numeric($value)) {
|
|
269
|
+
return "\"{$value}\"^^xsd:integer";
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (is_bool($value)) {
|
|
273
|
+
return $value ? '"true"^^xsd:boolean' : '"false"^^xsd:boolean';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Escape quotes and special characters
|
|
277
|
+
$escaped = str_replace(['"', '\\'], ['\\"', '\\\\'], (string)$value);
|
|
278
|
+
return "\"{$escaped}\"";
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Create a container in the pod.
|
|
283
|
+
*
|
|
284
|
+
* @param SolidIdentity $identity
|
|
285
|
+
* @param string $containerUrl
|
|
286
|
+
* @return void
|
|
287
|
+
*/
|
|
288
|
+
protected function createContainer(SolidIdentity $identity, string $containerUrl): void
|
|
289
|
+
{
|
|
290
|
+
try {
|
|
291
|
+
$response = $identity->request('put', $containerUrl, '', [
|
|
292
|
+
'headers' => [
|
|
293
|
+
'Content-Type' => 'text/turtle',
|
|
294
|
+
'Link' => '<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"',
|
|
295
|
+
],
|
|
296
|
+
]);
|
|
297
|
+
|
|
298
|
+
Log::info('[CONTAINER CREATED]', [
|
|
299
|
+
'url' => $containerUrl,
|
|
300
|
+
'status' => $response->status(),
|
|
301
|
+
]);
|
|
302
|
+
} catch (\Throwable $e) {
|
|
303
|
+
// Container might already exist, that's okay
|
|
304
|
+
Log::debug('[CONTAINER CREATION SKIPPED]', [
|
|
305
|
+
'url' => $containerUrl,
|
|
306
|
+
'reason' => $e->getMessage(),
|
|
307
|
+
]);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Store a resource in the pod.
|
|
313
|
+
*
|
|
314
|
+
* @param SolidIdentity $identity
|
|
315
|
+
* @param string $resourceUrl
|
|
316
|
+
* @param string $turtle
|
|
317
|
+
* @return void
|
|
318
|
+
*/
|
|
319
|
+
protected function storeResource(SolidIdentity $identity, string $resourceUrl, string $turtle): void
|
|
320
|
+
{
|
|
321
|
+
$response = $identity->request('put', $resourceUrl, $turtle, [
|
|
322
|
+
'headers' => [
|
|
323
|
+
'Content-Type' => 'text/turtle',
|
|
324
|
+
],
|
|
325
|
+
]);
|
|
326
|
+
|
|
327
|
+
if (!$response->successful()) {
|
|
328
|
+
throw new \Exception("Failed to store resource: {$response->body()}");
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
Log::debug('[RESOURCE STORED]', [
|
|
332
|
+
'url' => $resourceUrl,
|
|
333
|
+
'status' => $response->status(),
|
|
334
|
+
]);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\Solid\Services;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Models\Vehicle;
|
|
6
|
+
use Fleetbase\Solid\Models\SolidIdentity;
|
|
7
|
+
use Illuminate\Support\Facades\Log;
|
|
8
|
+
use Illuminate\Support\Str;
|
|
9
|
+
|
|
10
|
+
class VehicleSyncService
|
|
11
|
+
{
|
|
12
|
+
/**
|
|
13
|
+
* Get vehicles available for sync.
|
|
14
|
+
*/
|
|
15
|
+
public function getAvailableVehicles(): array
|
|
16
|
+
{
|
|
17
|
+
try {
|
|
18
|
+
$vehicles = Vehicle::with(['driver', 'vendor', 'category'])
|
|
19
|
+
->where('company_uuid', session('company'))
|
|
20
|
+
->get();
|
|
21
|
+
|
|
22
|
+
return $vehicles->map(function ($vehicle) {
|
|
23
|
+
return [
|
|
24
|
+
'id' => $vehicle->uuid,
|
|
25
|
+
'display_name' => $this->getVehicleDisplayName($vehicle),
|
|
26
|
+
'make' => $vehicle->make,
|
|
27
|
+
'model' => $vehicle->model,
|
|
28
|
+
'year' => $vehicle->year,
|
|
29
|
+
'plate_number' => $vehicle->plate_number,
|
|
30
|
+
'vin' => $vehicle->vin,
|
|
31
|
+
'status' => $vehicle->status,
|
|
32
|
+
'driver_name' => $vehicle->driver?->name,
|
|
33
|
+
'vendor_name' => $vehicle->vendor?->name,
|
|
34
|
+
'last_seen' => $vehicle->updated_at?->diffForHumans(),
|
|
35
|
+
'sync_status' => 'not_synced', // TODO: Track actual sync status
|
|
36
|
+
];
|
|
37
|
+
})->toArray();
|
|
38
|
+
} catch (\Throwable $e) {
|
|
39
|
+
Log::error('[GET AVAILABLE VEHICLES ERROR]', [
|
|
40
|
+
'error' => $e->getMessage(),
|
|
41
|
+
]);
|
|
42
|
+
throw $e;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Sync selected vehicles to a pod.
|
|
48
|
+
*/
|
|
49
|
+
public function syncVehiclesToPod(SolidIdentity $identity, string $podUrl, array $vehicleIds): array
|
|
50
|
+
{
|
|
51
|
+
$syncedCount = 0;
|
|
52
|
+
$failedCount = 0;
|
|
53
|
+
$details = [];
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
$vehicles = Vehicle::with(['driver', 'vendor', 'category'])
|
|
57
|
+
->whereIn('uuid', $vehicleIds)
|
|
58
|
+
->where('company_uuid', session('company'))
|
|
59
|
+
->get();
|
|
60
|
+
|
|
61
|
+
foreach ($vehicles as $vehicle) {
|
|
62
|
+
try {
|
|
63
|
+
$this->syncSingleVehicle($identity, $podUrl, $vehicle);
|
|
64
|
+
$syncedCount++;
|
|
65
|
+
$details[] = [
|
|
66
|
+
'vehicle_id' => $vehicle->uuid,
|
|
67
|
+
'status' => 'success',
|
|
68
|
+
'message' => 'Synced successfully',
|
|
69
|
+
];
|
|
70
|
+
} catch (\Throwable $e) {
|
|
71
|
+
$failedCount++;
|
|
72
|
+
$details[] = [
|
|
73
|
+
'vehicle_id' => $vehicle->uuid,
|
|
74
|
+
'status' => 'failed',
|
|
75
|
+
'message' => $e->getMessage(),
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
Log::error('[VEHICLE SYNC FAILED]', [
|
|
79
|
+
'vehicle_id' => $vehicle->uuid,
|
|
80
|
+
'error' => $e->getMessage(),
|
|
81
|
+
]);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
Log::info('[VEHICLE SYNC COMPLETED]', [
|
|
86
|
+
'pod_url' => $podUrl,
|
|
87
|
+
'synced_count' => $syncedCount,
|
|
88
|
+
'failed_count' => $failedCount,
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
return [
|
|
92
|
+
'synced_count' => $syncedCount,
|
|
93
|
+
'failed_count' => $failedCount,
|
|
94
|
+
'details' => $details,
|
|
95
|
+
];
|
|
96
|
+
} catch (\Throwable $e) {
|
|
97
|
+
Log::error('[SYNC VEHICLES TO POD ERROR]', [
|
|
98
|
+
'pod_url' => $podUrl,
|
|
99
|
+
'vehicle_ids' => $vehicleIds,
|
|
100
|
+
'error' => $e->getMessage(),
|
|
101
|
+
]);
|
|
102
|
+
throw $e;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Sync a single vehicle to the pod.
|
|
108
|
+
*/
|
|
109
|
+
private function syncSingleVehicle(SolidIdentity $identity, string $podUrl, Vehicle $vehicle): void
|
|
110
|
+
{
|
|
111
|
+
// Generate RDF/Turtle content for the vehicle
|
|
112
|
+
$rdfContent = $this->generateVehicleRDF($vehicle);
|
|
113
|
+
|
|
114
|
+
// Create filename
|
|
115
|
+
$filename = $this->generateVehicleFilename($vehicle);
|
|
116
|
+
$resourceUrl = rtrim($podUrl, '/') . '/' . $filename;
|
|
117
|
+
|
|
118
|
+
// Store the vehicle data in the pod
|
|
119
|
+
$response = $identity->request('put', $resourceUrl, $rdfContent, [
|
|
120
|
+
'headers' => [
|
|
121
|
+
'Content-Type' => 'text/turtle',
|
|
122
|
+
],
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
if (!$response->successful()) {
|
|
126
|
+
throw new \Exception('Failed to store vehicle data: ' . $response->body());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
Log::info('[VEHICLE SYNCED]', [
|
|
130
|
+
'vehicle_id' => $vehicle->uuid,
|
|
131
|
+
'resource_url' => $resourceUrl,
|
|
132
|
+
'status' => $response->status(),
|
|
133
|
+
]);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Generate RDF/Turtle content for a vehicle.
|
|
138
|
+
*/
|
|
139
|
+
private function generateVehicleRDF(Vehicle $vehicle): string
|
|
140
|
+
{
|
|
141
|
+
$turtle = "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n";
|
|
142
|
+
$turtle .= "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n";
|
|
143
|
+
$turtle .= "@prefix dc: <http://purl.org/dc/terms/> .\n";
|
|
144
|
+
$turtle .= "@prefix foaf: <http://xmlns.com/foaf/0.1/> .\n";
|
|
145
|
+
$turtle .= "@prefix vehicle: <http://fleetbase.io/ontology/vehicle#> .\n";
|
|
146
|
+
$turtle .= "@prefix fleet: <http://fleetbase.io/ontology/fleet#> .\n\n";
|
|
147
|
+
|
|
148
|
+
$vehicleUri = "<#vehicle-{$vehicle->uuid}>";
|
|
149
|
+
|
|
150
|
+
$turtle .= "$vehicleUri a vehicle:Vehicle ;\n";
|
|
151
|
+
$turtle .= " dc:identifier \"{$vehicle->uuid}\" ;\n";
|
|
152
|
+
|
|
153
|
+
if ($vehicle->make) {
|
|
154
|
+
$turtle .= " vehicle:make \"{$vehicle->make}\" ;\n";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if ($vehicle->model) {
|
|
158
|
+
$turtle .= " vehicle:model \"{$vehicle->model}\" ;\n";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if ($vehicle->year) {
|
|
162
|
+
$turtle .= " vehicle:year \"{$vehicle->year}\" ;\n";
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if ($vehicle->trim) {
|
|
166
|
+
$turtle .= " vehicle:trim \"{$vehicle->trim}\" ;\n";
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if ($vehicle->type) {
|
|
170
|
+
$turtle .= " vehicle:type \"{$vehicle->type}\" ;\n";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if ($vehicle->plate_number) {
|
|
174
|
+
$turtle .= " vehicle:plateNumber \"{$vehicle->plate_number}\" ;\n";
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if ($vehicle->vin) {
|
|
178
|
+
$turtle .= " vehicle:vin \"{$vehicle->vin}\" ;\n";
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if ($vehicle->status) {
|
|
182
|
+
$turtle .= " fleet:status \"{$vehicle->status}\" ;\n";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if ($vehicle->online !== null) {
|
|
186
|
+
$onlineStatus = $vehicle->online ? 'true' : 'false';
|
|
187
|
+
$turtle .= " fleet:online \"$onlineStatus\"^^<http://www.w3.org/2001/XMLSchema#boolean> ;\n";
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Add location if available
|
|
191
|
+
if ($vehicle->location) {
|
|
192
|
+
$turtle .= " fleet:location [\n";
|
|
193
|
+
$turtle .= " a fleet:Location ;\n";
|
|
194
|
+
|
|
195
|
+
if (isset($vehicle->location['coordinates'])) {
|
|
196
|
+
$coords = $vehicle->location['coordinates'];
|
|
197
|
+
if (isset($coords[0]) && isset($coords[1])) {
|
|
198
|
+
$turtle .= " fleet:longitude \"{$coords[0]}\"^^<http://www.w3.org/2001/XMLSchema#decimal> ;\n";
|
|
199
|
+
$turtle .= " fleet:latitude \"{$coords[1]}\"^^<http://www.w3.org/2001/XMLSchema#decimal> ;\n";
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
$turtle .= " ] ;\n";
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Add driver relationship
|
|
207
|
+
if ($vehicle->driver) {
|
|
208
|
+
$turtle .= " fleet:assignedDriver [\n";
|
|
209
|
+
$turtle .= " a foaf:Person ;\n";
|
|
210
|
+
$turtle .= " foaf:name \"{$vehicle->driver->name}\" ;\n";
|
|
211
|
+
$turtle .= " dc:identifier \"{$vehicle->driver->uuid}\" ;\n";
|
|
212
|
+
$turtle .= " ] ;\n";
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Add vendor relationship
|
|
216
|
+
if ($vehicle->vendor) {
|
|
217
|
+
$turtle .= " fleet:vendor [\n";
|
|
218
|
+
$turtle .= " a fleet:Vendor ;\n";
|
|
219
|
+
$turtle .= " foaf:name \"{$vehicle->vendor->name}\" ;\n";
|
|
220
|
+
$turtle .= " dc:identifier \"{$vehicle->vendor->uuid}\" ;\n";
|
|
221
|
+
$turtle .= " ] ;\n";
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Add metadata
|
|
225
|
+
$turtle .= " dc:created \"{$vehicle->created_at->toISOString()}\" ;\n";
|
|
226
|
+
$turtle .= " dc:modified \"{$vehicle->updated_at->toISOString()}\" ;\n";
|
|
227
|
+
$turtle .= ' fleet:syncedAt "' . now()->toISOString() . "\" ;\n";
|
|
228
|
+
$turtle .= " fleet:syncedFrom \"fleetbase\" .\n";
|
|
229
|
+
|
|
230
|
+
return $turtle;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Generate filename for vehicle resource.
|
|
235
|
+
*/
|
|
236
|
+
private function generateVehicleFilename(Vehicle $vehicle): string
|
|
237
|
+
{
|
|
238
|
+
$identifier = $vehicle->plate_number ?: $vehicle->vin ?: $vehicle->uuid;
|
|
239
|
+
$slug = Str::slug($identifier);
|
|
240
|
+
|
|
241
|
+
return "vehicle-{$slug}.ttl";
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get vehicle display name.
|
|
246
|
+
*/
|
|
247
|
+
private function getVehicleDisplayName(Vehicle $vehicle): string
|
|
248
|
+
{
|
|
249
|
+
$parts = array_filter([
|
|
250
|
+
$vehicle->year,
|
|
251
|
+
$vehicle->make,
|
|
252
|
+
$vehicle->model,
|
|
253
|
+
$vehicle->trim,
|
|
254
|
+
]);
|
|
255
|
+
|
|
256
|
+
$name = implode(' ', $parts);
|
|
257
|
+
|
|
258
|
+
if ($vehicle->plate_number) {
|
|
259
|
+
$name .= " ({$vehicle->plate_number})";
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return $name ?: "Vehicle {$vehicle->uuid}";
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get sync status for a pod.
|
|
267
|
+
*/
|
|
268
|
+
public function getSyncStatus(SolidIdentity $identity, string $podId): array
|
|
269
|
+
{
|
|
270
|
+
try {
|
|
271
|
+
// This would typically check a sync status table or cache
|
|
272
|
+
// For now, return a basic status
|
|
273
|
+
return [
|
|
274
|
+
'pod_id' => $podId,
|
|
275
|
+
'last_sync' => null,
|
|
276
|
+
'total_vehicles' => 0,
|
|
277
|
+
'synced_vehicles' => 0,
|
|
278
|
+
'failed_vehicles' => 0,
|
|
279
|
+
'status' => 'ready',
|
|
280
|
+
];
|
|
281
|
+
} catch (\Throwable $e) {
|
|
282
|
+
Log::error('[GET SYNC STATUS ERROR]', [
|
|
283
|
+
'pod_id' => $podId,
|
|
284
|
+
'error' => $e->getMessage(),
|
|
285
|
+
]);
|
|
286
|
+
throw $e;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
@@ -44,4 +44,14 @@ class Utils extends FleetbaseUtils
|
|
|
44
44
|
|
|
45
45
|
return null;
|
|
46
46
|
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get the Solid server URL from configuration.
|
|
50
|
+
*
|
|
51
|
+
* @return string
|
|
52
|
+
*/
|
|
53
|
+
public static function getSolidServerUrl(): string
|
|
54
|
+
{
|
|
55
|
+
return config('solid.server.url', 'http://localhost:3000');
|
|
56
|
+
}
|
|
47
57
|
}
|
package/server/src/routes.php
CHANGED
|
@@ -28,11 +28,35 @@ Route::prefix(config('solid.api.routing.prefix', 'solid'))->namespace('Fleetbase
|
|
|
28
28
|
$router->group(
|
|
29
29
|
['prefix' => 'v1'],
|
|
30
30
|
function ($router) {
|
|
31
|
-
$router->get('pods', 'SolidController@getPods');
|
|
32
31
|
$router->get('authenticate/{identifier}', 'SolidController@authenticate');
|
|
33
32
|
$router->group(['middleware' => ['fleetbase.protected']], function ($router) {
|
|
33
|
+
// Authentication status and management
|
|
34
34
|
$router->get('account', 'SolidController@getAccountIndex');
|
|
35
35
|
$router->get('request-authentication', 'SolidController@requestAuthentication');
|
|
36
|
+
$router->get('authentication-status', 'SolidController@getAuthenticationStatus');
|
|
37
|
+
$router->post('logout', 'SolidController@logout');
|
|
38
|
+
|
|
39
|
+
// Account and profile
|
|
40
|
+
$router->get('account', 'SolidController@getAccountIndex');
|
|
41
|
+
$router->get('profile', 'SolidController@getProfileData');
|
|
42
|
+
|
|
43
|
+
// Data management routes (single-pod architecture)
|
|
44
|
+
$router->get('data', 'DataController@index');
|
|
45
|
+
$router->get('data/folder/{slug}', 'DataController@showFolder');
|
|
46
|
+
$router->post('data/folder', 'DataController@createFolder');
|
|
47
|
+
$router->delete('data/{type}/{slug}', 'DataController@deleteItem');
|
|
48
|
+
$router->post('data/import', 'DataController@importResources');
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
// Resource sync endpoints
|
|
53
|
+
$router->get('sync-status', 'SolidController@getSyncStatus');
|
|
54
|
+
$router->post('sync-vehicles', 'SolidController@syncVehicles');
|
|
55
|
+
$router->post('sync-drivers', 'SolidController@syncDrivers');
|
|
56
|
+
$router->post('sync-orders', 'SolidController@syncOrders');
|
|
57
|
+
$router->post('sync-all', 'SolidController@syncAll');
|
|
58
|
+
|
|
59
|
+
// Server configuration
|
|
36
60
|
$router->get('server-config', 'SolidController@getServerConfig');
|
|
37
61
|
$router->post('server-config', 'SolidController@saveServerConfig');
|
|
38
62
|
});
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
<Modal::Default @modalIsOpened={{@modalIsOpened}} @options={{@options}} @confirm={{@onConfirm}} @decline={{@onDecline}}>
|
|
2
|
-
<div class="modal-body-container">
|
|
3
|
-
<InputGroup @name="Pod Name" @value={{@options.pod.name}} @helpText="Input a name for your new Pod" />
|
|
4
|
-
</div>
|
|
5
|
-
</Modal::Default>
|