@fleetbase/fleetops-engine 0.6.61 → 0.6.63
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/RELEASE.md +5 -14
- package/addon/controllers/management/drivers/index.js +4 -1
- package/addon/routes/management/fleets/index/details.js +1 -1
- package/addon/routes/management/fleets/index/edit.js +1 -1
- package/addon/routes/management/fleets/index.js +3 -1
- package/addon/services/map-settings.js +4 -1
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +1 -1
- package/server/migrations/2026_09_05_000001_add_unique_indexes_to_fleet_membership_pivots.php +129 -0
- package/server/src/Exceptions/PublicRelationNotFoundException.php +47 -0
- package/server/src/Http/Controllers/Api/v1/Concerns/ResolvesFleetOpsApiResources.php +42 -7
- package/server/src/Http/Controllers/Api/v1/Concerns/ResolvesPublicExpansions.php +187 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +93 -57
- package/server/src/Http/Controllers/Api/v1/EntityController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/FleetController.php +370 -33
- package/server/src/Http/Controllers/Api/v1/FuelReportController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/IssueController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/OrderController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/TrackingStatusController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +134 -35
- package/server/src/Http/Controllers/Api/v1/ZoneController.php +1 -1
- package/server/src/Http/Filter/Concerns/ResolvesPublicRelationUuids.php +54 -0
- package/server/src/Http/Filter/DriverFilter.php +71 -15
- package/server/src/Http/Filter/FleetFilter.php +32 -33
- package/server/src/Http/Filter/VehicleFilter.php +57 -19
- package/server/src/Http/Requests/Concerns/ScopesPublicRelationRules.php +48 -0
- package/server/src/Http/Requests/CreateDriverRequest.php +100 -9
- package/server/src/Http/Requests/CreateFleetRequest.php +30 -2
- package/server/src/Http/Requests/CreateServiceRateRequest.php +7 -2
- package/server/src/Http/Requests/CreateVehicleRequest.php +150 -31
- package/server/src/Http/Resources/v1/Concerns/ResolvesPublicRelationFields.php +110 -0
- package/server/src/Http/Resources/v1/Driver.php +22 -2
- package/server/src/Http/Resources/v1/Fleet.php +38 -25
- package/server/src/Http/Resources/v1/Vehicle.php +46 -4
- package/server/src/Models/Driver.php +2 -2
- package/server/src/routes.php +8 -0
- package/server/tests/ApiDriverControllerContractsTest.php +284 -13
- package/server/tests/ApiEntityControllerContractsTest.php +1 -1
- package/server/tests/ApiEquipmentControllerContractsTest.php +2 -2
- package/server/tests/ApiFleetControllerContractsTest.php +354 -21
- package/server/tests/ApiFuelReportControllerContractsTest.php +1 -1
- package/server/tests/ApiIssueControllerContractsTest.php +1 -1
- package/server/tests/ApiOrderControllerContractsTest.php +1 -1
- package/server/tests/ApiPartControllerContractsTest.php +2 -2
- package/server/tests/ApiServiceAreaZoneControllerContractsTest.php +2 -2
- package/server/tests/ApiTrackingStatusControllerContractsTest.php +1 -1
- package/server/tests/ApiVehicleControllerContractsTest.php +175 -8
- package/server/tests/ControllerFilterContractsTest.php +132 -13
- package/server/tests/ControllerHelperContractsTest.php +6 -3
- package/server/tests/DriverFilterExecutionTest.php +130 -6
- package/server/tests/ExceptionContractsTest.php +15 -0
- package/server/tests/Feature/Http/Api/DeviceControllerContractsTest.php +1 -1
- package/server/tests/Feature/Http/Api/FleetMembershipMigrationTest.php +140 -0
- package/server/tests/Feature/Http/Api/FleetMembershipTest.php +333 -0
- package/server/tests/Feature/Http/Api/FleetPublicContractTest.php +439 -0
- package/server/tests/Feature/Http/Api/FuelTransactionControllerContractsTest.php +2 -2
- package/server/tests/Feature/Http/Api/NullRelationshipInputTest.php +191 -0
- package/server/tests/Feature/Http/Api/PublicIdentifierFilterTest.php +293 -0
- package/server/tests/Feature/Http/Api/SmallApiControllerHelpersTest.php +7 -1
- package/server/tests/Feature/Http/Api/VehicleControllerTrackingTest.php +3 -1
- package/server/tests/Feature/Http/Api/VehiclePublicContractTest.php +454 -0
- package/server/tests/Feature/Http/Api/WorkOrderControllerContractsTest.php +1 -1
- package/server/tests/FleetHierarchyResourceTest.php +56 -8
- package/server/tests/RequestContractsTest.php +100 -7
- package/server/tests/Unit/Http/Resources/FleetResourceTest.php +50 -6
- package/server/tests/VehicleControllerHelperContractsTest.php +1 -1
|
@@ -6,6 +6,9 @@ use Fleetbase\FleetOps\Events\DriverLocationChanged;
|
|
|
6
6
|
use Fleetbase\FleetOps\Events\GeofenceEntered;
|
|
7
7
|
use Fleetbase\FleetOps\Events\GeofenceExited;
|
|
8
8
|
use Fleetbase\FleetOps\Events\VehicleLocationChanged;
|
|
9
|
+
use Fleetbase\FleetOps\Exceptions\PublicRelationNotFoundException;
|
|
10
|
+
use Fleetbase\FleetOps\Http\Controllers\Api\v1\Concerns\ResolvesFleetOpsApiResources;
|
|
11
|
+
use Fleetbase\FleetOps\Http\Controllers\Api\v1\Concerns\ResolvesPublicExpansions;
|
|
9
12
|
use Fleetbase\FleetOps\Http\Requests\CreateDriverRequest;
|
|
10
13
|
use Fleetbase\FleetOps\Http\Requests\DriverSimulationRequest;
|
|
11
14
|
use Fleetbase\FleetOps\Http\Requests\UpdateDriverRequest;
|
|
@@ -16,6 +19,7 @@ use Fleetbase\FleetOps\Jobs\SimulateDrivingRoute;
|
|
|
16
19
|
use Fleetbase\FleetOps\Models\Driver;
|
|
17
20
|
use Fleetbase\FleetOps\Models\Order;
|
|
18
21
|
use Fleetbase\FleetOps\Models\Vehicle;
|
|
22
|
+
use Fleetbase\FleetOps\Models\Vendor;
|
|
19
23
|
use Fleetbase\FleetOps\Support\GeofenceIntersectionService;
|
|
20
24
|
use Fleetbase\FleetOps\Support\OSRM;
|
|
21
25
|
use Fleetbase\FleetOps\Support\Utils;
|
|
@@ -40,6 +44,23 @@ use Illuminate\Support\Str;
|
|
|
40
44
|
class DriverController extends Controller
|
|
41
45
|
{
|
|
42
46
|
use \Fleetbase\FleetOps\Http\Controllers\Concerns\ResolvesReviewAccountBypass;
|
|
47
|
+
use ResolvesFleetOpsApiResources;
|
|
48
|
+
use ResolvesPublicExpansions;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Public expansion name => Eloquent relation name.
|
|
52
|
+
*
|
|
53
|
+
* `user` and `company` are deliberately absent. Both are already published
|
|
54
|
+
* as public-id strings, and Navigator interpolates `driver.user` directly
|
|
55
|
+
* into a socket channel name — expanding either would retype a released
|
|
56
|
+
* field and break it silently.
|
|
57
|
+
*/
|
|
58
|
+
public const EXPANDABLE = [
|
|
59
|
+
'vehicle' => 'vehicle',
|
|
60
|
+
'vendor' => 'vendor',
|
|
61
|
+
'current_job' => 'currentJob',
|
|
62
|
+
'fleets' => 'fleets',
|
|
63
|
+
];
|
|
43
64
|
|
|
44
65
|
/**
|
|
45
66
|
* Creates a new Fleetbase Driver resource.
|
|
@@ -48,11 +69,7 @@ class DriverController extends Controller
|
|
|
48
69
|
*/
|
|
49
70
|
public function create(CreateDriverRequest $request)
|
|
50
71
|
{
|
|
51
|
-
|
|
52
|
-
$input = $request->except(['name', 'password', 'email', 'phone', 'location', 'altitude', 'heading', 'speed', 'meta']);
|
|
53
|
-
|
|
54
|
-
// Add default status
|
|
55
|
-
$input['status'] = $request->input('status', 'available');
|
|
72
|
+
$this->applyPublicExpansions($request, static::EXPANDABLE);
|
|
56
73
|
|
|
57
74
|
// get user details for driver
|
|
58
75
|
$userDetails = $request->only(['name', 'password', 'email', 'phone', 'timezone']);
|
|
@@ -65,6 +82,18 @@ class DriverController extends Controller
|
|
|
65
82
|
return $this->apiError('Company not found.');
|
|
66
83
|
}
|
|
67
84
|
|
|
85
|
+
// get request input. Relationship inputs resolve against the company the
|
|
86
|
+
// driver is being created in, which is not always the session company —
|
|
87
|
+
// a request may name one explicitly.
|
|
88
|
+
try {
|
|
89
|
+
$input = $this->driverInputFromRequest($request, $company->uuid);
|
|
90
|
+
} catch (PublicRelationNotFoundException $exception) {
|
|
91
|
+
return $this->jsonResponse(['error' => $exception->getMessage()], 404);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Add default status
|
|
95
|
+
$input['status'] = $request->input('status', 'available');
|
|
96
|
+
|
|
68
97
|
// Apply user infos
|
|
69
98
|
$userDetails = $this->applyUserInfoFromRequest($request, $userDetails);
|
|
70
99
|
|
|
@@ -87,30 +116,6 @@ class DriverController extends Controller
|
|
|
87
116
|
$input['user_uuid'] = $user->uuid;
|
|
88
117
|
$input['company_uuid'] = $company->uuid; // Ensure correct company_uuid is set
|
|
89
118
|
|
|
90
|
-
// vehicle assignment public_id -> uuid
|
|
91
|
-
if ($request->has('vehicle')) {
|
|
92
|
-
$input['vehicle_uuid'] = $this->getUuid('vehicles', [
|
|
93
|
-
'public_id' => $request->input('vehicle'),
|
|
94
|
-
'company_uuid' => $company->uuid, // Use $company->uuid instead of session
|
|
95
|
-
]);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// vendor assignment public_id -> uuid
|
|
99
|
-
if ($request->has('vendor')) {
|
|
100
|
-
$input['vendor_uuid'] = $this->getUuid('vendors', [
|
|
101
|
-
'public_id' => $request->input('vendor'),
|
|
102
|
-
'company_uuid' => $company->uuid, // Use $company->uuid instead of session
|
|
103
|
-
]);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// order|alias:job assignment public_id -> uuid
|
|
107
|
-
if ($request->has('job')) {
|
|
108
|
-
$input['current_job_uuid'] = $this->getUuid('orders', [
|
|
109
|
-
'public_id' => $request->input('job'),
|
|
110
|
-
'company_uuid' => $company->uuid, // Use $company->uuid instead of session
|
|
111
|
-
]);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
119
|
// set default online
|
|
115
120
|
if (!isset($input['online'])) {
|
|
116
121
|
$input['online'] = 0;
|
|
@@ -130,7 +135,11 @@ class DriverController extends Controller
|
|
|
130
135
|
$file = $this->resolveFile($request->input('photo'), $path);
|
|
131
136
|
|
|
132
137
|
if ($file) {
|
|
133
|
-
|
|
138
|
+
// `photo_uuid` is not a column on users — the avatar lives in
|
|
139
|
+
// `avatar_uuid`, and User guards mass assignment by fillable, so
|
|
140
|
+
// the old key was dropped without a word and every driver photo
|
|
141
|
+
// uploaded through the public API was discarded.
|
|
142
|
+
$user->update(['avatar_uuid' => $file->uuid]);
|
|
134
143
|
}
|
|
135
144
|
}
|
|
136
145
|
|
|
@@ -151,6 +160,8 @@ class DriverController extends Controller
|
|
|
151
160
|
*/
|
|
152
161
|
public function update($id, UpdateDriverRequest $request)
|
|
153
162
|
{
|
|
163
|
+
$this->applyPublicExpansions($request, static::EXPANDABLE);
|
|
164
|
+
|
|
154
165
|
// find for the driver
|
|
155
166
|
try {
|
|
156
167
|
$driver = $this->findDriver($id, ['user']);
|
|
@@ -164,7 +175,11 @@ class DriverController extends Controller
|
|
|
164
175
|
}
|
|
165
176
|
|
|
166
177
|
// get request input
|
|
167
|
-
|
|
178
|
+
try {
|
|
179
|
+
$input = $this->driverInputFromRequest($request);
|
|
180
|
+
} catch (PublicRelationNotFoundException $exception) {
|
|
181
|
+
return $this->jsonResponse(['error' => $exception->getMessage()], 404);
|
|
182
|
+
}
|
|
168
183
|
|
|
169
184
|
/*
|
|
170
185
|
* Deliberately no `password` here. Setting one through a general update
|
|
@@ -173,7 +188,10 @@ class DriverController extends Controller
|
|
|
173
188
|
* password. Changing a password is its own operation with its own
|
|
174
189
|
* proof: see changePassword(), forgotPassword() and resetPassword().
|
|
175
190
|
*/
|
|
176
|
-
|
|
191
|
+
// `timezone` is accepted and documented on update, but was never copied
|
|
192
|
+
// to the linked user — the request validated, answered 200, and dropped
|
|
193
|
+
// it. The driver's own record has no timezone column; the user's does.
|
|
194
|
+
$userDetails = $request->only(['name', 'email', 'phone', 'timezone']);
|
|
177
195
|
|
|
178
196
|
// update driver user details
|
|
179
197
|
$driverUser = $driver->getUser();
|
|
@@ -181,30 +199,6 @@ class DriverController extends Controller
|
|
|
181
199
|
$driverUser->update($userDetails);
|
|
182
200
|
}
|
|
183
201
|
|
|
184
|
-
// vehicle assignment public_id -> uuid
|
|
185
|
-
if ($request->has('vehicle')) {
|
|
186
|
-
$input['vehicle_uuid'] = $this->getUuid('vehicles', [
|
|
187
|
-
'public_id' => $request->input('vehicle'),
|
|
188
|
-
'company_uuid' => $this->sessionCompany(),
|
|
189
|
-
]);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// vendor assignment public_id -> uuid
|
|
193
|
-
if ($request->has('vendor')) {
|
|
194
|
-
$input['vendor_uuid'] = $this->getUuid('vendors', [
|
|
195
|
-
'public_id' => $request->input('vendor'),
|
|
196
|
-
'company_uuid' => $this->sessionCompany(),
|
|
197
|
-
]);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// order|alias:job assignment public_id -> uuid
|
|
201
|
-
if ($request->has('job')) {
|
|
202
|
-
$input['current_job_uuid'] = $this->getUuid('orders', [
|
|
203
|
-
'public_id' => $request->input('job'),
|
|
204
|
-
'company_uuid' => $this->sessionCompany(),
|
|
205
|
-
]);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
202
|
// latitude / longitude
|
|
209
203
|
if ($request->has(['latitude', 'longitude'])) {
|
|
210
204
|
$input['location'] = $this->pointFromCoordinates($request->only(['latitude', 'longitude']));
|
|
@@ -220,7 +214,7 @@ class DriverController extends Controller
|
|
|
220
214
|
$file = $this->resolveFile($request->input('photo'), $path);
|
|
221
215
|
|
|
222
216
|
if ($file) {
|
|
223
|
-
$driver->user->update(['
|
|
217
|
+
$driver->user->update(['avatar_uuid' => $file->uuid]);
|
|
224
218
|
}
|
|
225
219
|
}
|
|
226
220
|
|
|
@@ -238,6 +232,8 @@ class DriverController extends Controller
|
|
|
238
232
|
*/
|
|
239
233
|
public function query(Request $request)
|
|
240
234
|
{
|
|
235
|
+
$this->applyPublicExpansions($request, static::EXPANDABLE);
|
|
236
|
+
|
|
241
237
|
$results = $this->queryDrivers($request);
|
|
242
238
|
|
|
243
239
|
return $this->driverResourceCollection($results);
|
|
@@ -252,6 +248,11 @@ class DriverController extends Controller
|
|
|
252
248
|
*/
|
|
253
249
|
public function find($id)
|
|
254
250
|
{
|
|
251
|
+
// Retrieve carries no Request parameter, so the container's is used —
|
|
252
|
+
// otherwise an unsupported `with` on this endpoint would reach Eloquent
|
|
253
|
+
// unmapped.
|
|
254
|
+
$this->applyPublicExpansions(request(), static::EXPANDABLE);
|
|
255
|
+
|
|
255
256
|
// find for the driver
|
|
256
257
|
try {
|
|
257
258
|
$driver = $this->findDriver($id, ['user', 'vehicle', 'vendor', 'currentJob']);
|
|
@@ -919,6 +920,41 @@ class DriverController extends Controller
|
|
|
919
920
|
return response()->json($route);
|
|
920
921
|
}
|
|
921
922
|
|
|
923
|
+
/**
|
|
924
|
+
* The explicit public input allowlist for a driver.
|
|
925
|
+
*
|
|
926
|
+
* Replaces an `except()` blocklist: anything not named here — `user_uuid`,
|
|
927
|
+
* `company_uuid`, `auth_token`, `signup_token_used`, `public_id`, `uuid`,
|
|
928
|
+
* `_key`, the generated `slug`, and the raw `*_uuid` relation columns — used
|
|
929
|
+
* to reach `Driver::create()` intact simply because nobody had thought to
|
|
930
|
+
* exclude it. `location`, `heading`, `altitude`, `speed` and `meta` were on
|
|
931
|
+
* that blocklist and so were dropped on every write, which is why a driver's
|
|
932
|
+
* metadata never persisted through the public API.
|
|
933
|
+
*
|
|
934
|
+
* @throws PublicRelationNotFoundException
|
|
935
|
+
*/
|
|
936
|
+
protected function driverInputFromRequest(Request $request, ?string $companyUuid = null): array
|
|
937
|
+
{
|
|
938
|
+
$input = $request->only([
|
|
939
|
+
// Identity
|
|
940
|
+
'internal_id', 'drivers_license_number', 'license_expiry',
|
|
941
|
+
// Operational
|
|
942
|
+
'country', 'currency', 'city', 'online', 'current_status', 'status',
|
|
943
|
+
'location', 'heading', 'bearing', 'altitude', 'speed',
|
|
944
|
+
// Structured / orchestrator
|
|
945
|
+
'meta', 'skills', 'max_travel_time', 'max_distance',
|
|
946
|
+
'time_window_start', 'time_window_end',
|
|
947
|
+
]);
|
|
948
|
+
|
|
949
|
+
$this->applyPublicIdRelations($input, [
|
|
950
|
+
'vehicle' => ['vehicle_uuid', Vehicle::class],
|
|
951
|
+
'vendor' => ['vendor_uuid', Vendor::class],
|
|
952
|
+
'job' => ['current_job_uuid', Order::class],
|
|
953
|
+
], $request, $companyUuid);
|
|
954
|
+
|
|
955
|
+
return $input;
|
|
956
|
+
}
|
|
957
|
+
|
|
922
958
|
protected function companyFromRequest(Request $request): ?Company
|
|
923
959
|
{
|
|
924
960
|
return Auth::getCompanyFromRequest($request);
|
|
@@ -271,7 +271,7 @@ class EntityController extends Controller
|
|
|
271
271
|
return $value ? Utils::singularize($value) : null;
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
-
protected function findPayloadByPublicId(string $publicId): ?Payload
|
|
274
|
+
protected function findPayloadByPublicId(?string $publicId): ?Payload
|
|
275
275
|
{
|
|
276
276
|
return Payload::where('public_id', $publicId)->first();
|
|
277
277
|
}
|