@fleetbase/fleetops-engine 0.3.6 → 0.3.7

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.
@@ -1,5 +1,8 @@
1
1
  <ContentPanel @title="Navigator App" @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
2
- <InputGroup @name="URL">
3
- <ClickToCopy>{{this.url}}</ClickToCopy>
2
+ <InputGroup @name="Instance Linking URL">
3
+ <ClickToCopy class="px-4 py-2 border rounded-md bg-gray-100 shadow-sm dark:bg-gray-700 border-gray-200 dark:border-gray-800" @value={{this.url}}>{{this.url}}</ClickToCopy>
4
+ <Attach::Tooltip @class="clean" @animation="scale" @placement="top">
5
+ <InputInfo>Use this URL to link the official Navigator app to this {{t "app.name"}} instance. Opening this URL on a mobile device will open the Navigator app and update it's configuration and branding.</InputInfo>
6
+ </Attach::Tooltip>
4
7
  </InputGroup>
5
- </ContentPanel>
8
+ </ContentPanel>
@@ -25,13 +25,6 @@ export default class ManagementDriversIndexEditController extends BaseController
25
25
  */
26
26
  @tracked overlay;
27
27
 
28
- /**
29
- * The universe service.
30
- *
31
- * @memberof ManagementDriversIndexEditController
32
- */
33
- @tracked universe;
34
-
35
28
  /**
36
29
  * When exiting the overlay.
37
30
  *
@@ -115,23 +108,4 @@ export default class ManagementDriversIndexEditController extends BaseController
115
108
  ...options,
116
109
  });
117
110
  }
118
-
119
- /**
120
- * Transitions to a specified route within the '@fleetbase/fleetops-engine' engine.
121
- *
122
- * This action is a wrapper around the `transitionToEngineRoute` method of the `universe` service (or object),
123
- * specifically targeting the '@fleetbase/fleetops-engine'. It allows for easy transitioning to routes
124
- * within this engine, abstracting away the need to repeatedly specify the engine name.
125
- *
126
- * @param {string} route - The route within the '@fleetbase/fleetops-engine' to transition to.
127
- * @param {...any} args - Additional arguments to pass to the transitionToEngineRoute method.
128
- * @returns {Promise} A Promise that resolves with the result of the transitionToEngineRoute method.
129
- *
130
- * @example
131
- * // To transition to the 'management.fleets.index.new' route within the '@fleetbase/fleetops-engine'
132
- * this.transitionToRoute('management.fleets.index.new');
133
- */
134
- @action _transitionToRoute(route, ...args) {
135
- return this.universe.transitionToEngineRoute('@fleetbase/fleetops-engine', route, ...args);
136
- }
137
111
  }
package/addon/engine.js CHANGED
@@ -4,7 +4,7 @@ import Resolver from 'ember-resolver';
4
4
  import config from './config/environment';
5
5
  import services from '@fleetbase/ember-core/exports/services';
6
6
  import AdminVisibilityControlsComponent from './components/admin/visibility-controls';
7
- import NavigatorAppControlsComponent from './components/admin/navigator-app';
7
+ import NavigatorAppConfigComponent from './components/admin/navigator-app';
8
8
 
9
9
  const { modulePrefix } = config;
10
10
  const externalRoutes = ['console', 'extensions'];
@@ -30,9 +30,9 @@ export default class FleetOpsEngine extends Engine {
30
30
  component: AdminVisibilityControlsComponent,
31
31
  },
32
32
  {
33
- title: 'Navigator Apps',
34
- icon: 'key',
35
- component: NavigatorAppControlsComponent,
33
+ title: 'Navigator App',
34
+ icon: 'location-arrow',
35
+ component: NavigatorAppConfigComponent,
36
36
  },
37
37
  ],
38
38
  {
package/composer.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetbase/fleetops-api",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "keywords": [
6
6
  "fleetbase-extension",
@@ -22,7 +22,7 @@
22
22
  ],
23
23
  "require": {
24
24
  "php": "^7.4|^8.0",
25
- "fleetbase/core-api": "^1.3.2",
25
+ "fleetbase/core-api": "^1.3.4",
26
26
  "barryvdh/laravel-dompdf": "^2.0",
27
27
  "brick/geo": "0.7.2",
28
28
  "cknow/laravel-money": "^7.1",
package/extension.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "Fleet-Ops",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "repository": "https://github.com/fleetbase/fleetops",
6
6
  "license": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/fleetops-engine",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "fleetbase": {
6
6
  "route": "fleet-ops",
@@ -12,7 +12,10 @@ return [
12
12
  'prefix' => null,
13
13
  'internal_prefix' => 'int'
14
14
  ]
15
- ],
15
+ ],
16
+ 'navigator' => [
17
+ 'bypass_verification_code' => env('NAVIGATOR_BYPASS_VERIFICATION_CODE', '999000')
18
+ ],
16
19
  'connection' => [
17
20
  'db' => env('DB_CONNECTION', 'mysql')
18
21
  ]
@@ -353,10 +353,10 @@ class DriverController extends Controller
353
353
  return response()->error('Authentication failed using password provided.', 401);
354
354
  }
355
355
 
356
- // get the current company session
356
+ // Get the company session for the user
357
357
  $company = Flow::getCompanySessionForUser($user);
358
358
 
359
- // get driver record
359
+ // Get driver record
360
360
  $driver = Driver::firstOrCreate(
361
361
  [
362
362
  'user_uuid' => $user->uuid,
@@ -365,9 +365,10 @@ class DriverController extends Controller
365
365
  [
366
366
  'user_uuid' => $user->uuid,
367
367
  'company_uuid' => $company->uuid,
368
- 'name' => $attrs['name'] ?? $user->name,
369
- 'phone' => $attrs['phone'] ?? $user->phone,
370
- 'email' => $attrs['email'] ?? $user->email,
368
+ 'name' => data_get($attrs, 'name', $user->name),
369
+ 'phone' => data_get($attrs, 'phone', $user->phone),
370
+ 'email' => data_get($attrs, 'email', $user->email),
371
+ 'location' => new Point(0, 0),
371
372
  ]
372
373
  );
373
374
 
@@ -443,7 +444,7 @@ class DriverController extends Controller
443
444
  // find and verify code
444
445
  $verificationCode = VerificationCode::where(['subject_uuid' => $user->uuid, 'code' => $code, 'for' => $for])->exists();
445
446
 
446
- if (!$verificationCode && $code !== '999000') {
447
+ if (!$verificationCode && $code !== config('fleetops.navigator.bypass_verification_code')) {
447
448
  return response()->error('Invalid verification code!');
448
449
  }
449
450
 
@@ -4,14 +4,18 @@ namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
4
4
 
5
5
  use Fleetbase\Exceptions\FleetbaseRequestValidationException;
6
6
  use Fleetbase\FleetOps\Exports\DriverExport;
7
+ use Fleetbase\FleetOps\Http\Controllers\Api\v1\DriverController as ApiDriverController;
7
8
  use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
9
+ use Fleetbase\FleetOps\Http\Requests\Internal\AssignOrderRequest;
8
10
  use Fleetbase\FleetOps\Http\Requests\Internal\CreateDriverRequest;
9
11
  use Fleetbase\FleetOps\Http\Requests\Internal\UpdateDriverRequest;
10
12
  use Fleetbase\FleetOps\Models\Driver;
13
+ use Fleetbase\FleetOps\Models\Order;
11
14
  use Fleetbase\FleetOps\Support\Utils;
12
15
  use Fleetbase\Http\Requests\ExportRequest;
13
16
  use Fleetbase\Models\Invite;
14
17
  use Fleetbase\Models\User;
18
+ use Fleetbase\Models\VerificationCode;
15
19
  use Fleetbase\Notifications\UserInvited;
16
20
  use Illuminate\Http\Request;
17
21
  use Illuminate\Support\Facades\DB;
@@ -267,4 +271,148 @@ class DriverController extends FleetOpsController
267
271
 
268
272
  return Excel::download(new DriverExport(), $fileName);
269
273
  }
274
+
275
+ /**
276
+ * Assigns a driver to a specified order.
277
+ *
278
+ * @param Fleetbase\FleetOps\Http\Requests\Internal\AssignOrderRequest $request
279
+ *
280
+ * @return \Illuminate\Http\Response $response
281
+ */
282
+ public function assignOrder(AssignOrderRequest $request)
283
+ {
284
+ $driver = Driver::where('public_id', $request->driver)->first();
285
+ $order = Order::where('public_id', $request->order)->first();
286
+
287
+ if ($order->hasDriverAssigned) {
288
+ return response()->error('A driver is already assigned to this order.');
289
+ }
290
+
291
+ if ($order->isDriver($driver)) {
292
+ return response()->error('The driver is already assigned to this order.');
293
+ }
294
+
295
+ $order->assignDriver($driver);
296
+
297
+ return response()->json([
298
+ 'status' => 'ok',
299
+ 'message' => 'Driver assigned',
300
+ ]);
301
+ }
302
+
303
+ /**
304
+ * Update drivers geolocation data.
305
+ *
306
+ * @return \Illuminate\Http\Response
307
+ */
308
+ public function track(string $id, Request $request)
309
+ {
310
+ return app(ApiDriverController::class)->track($id, $request);
311
+ }
312
+
313
+ /**
314
+ * Query for Storefront Customer orders.
315
+ *
316
+ * @return \Fleetbase\Http\Resources\Storefront\Customer
317
+ */
318
+ public function registerDevice(Request $request)
319
+ {
320
+ return app(ApiDriverController::class)->registerDevice($request);
321
+ }
322
+
323
+ /**
324
+ * Authenticates customer using login credentials and returns with auth token.
325
+ *
326
+ * @return \Fleetbase\Http\Resources\Storefront\Customer
327
+ */
328
+ public function login(Request $request)
329
+ {
330
+ return app(ApiDriverController::class)->login($request);
331
+ }
332
+
333
+ /**
334
+ * Attempts authentication with phone number via SMS verification.
335
+ *
336
+ * @return \Illuminate\Http\Response
337
+ */
338
+ public function loginWithPhone()
339
+ {
340
+ $phone = static::phone();
341
+
342
+ // Check if user exists
343
+ $user = User::where('phone', $phone)->whereNull('deleted_at')->withoutGlobalScopes()->first();
344
+
345
+ if (!$user) {
346
+ return response()->error('No driver with this phone # found.');
347
+ }
348
+
349
+ // Generate verification token
350
+ VerificationCode::generateSmsVerificationFor($user, 'driver_login', function ($verification) {
351
+ return 'Your ' . config('app.name') . ' verification code is ' . $verification->code;
352
+ });
353
+
354
+ return response()->json(['status' => 'OK']);
355
+ }
356
+
357
+ /**
358
+ * Verifys SMS code and sends auth token with driver resource.
359
+ *
360
+ * @return \Fleetbase\Http\Resources\FleetOps\Driver
361
+ */
362
+ public function verifyCode(Request $request)
363
+ {
364
+ $identity = Utils::isEmail($request->identity) ? $request->identity : static::phone($request->identity);
365
+ $code = $request->input('code');
366
+ $for = $request->input('for', 'driver_login');
367
+ $attrs = $request->input(['name', 'phone', 'email']);
368
+
369
+ if ($for === 'create_driver') {
370
+ return app(ApiDriverController::class)->create($request);
371
+ }
372
+
373
+ // Check if user exists
374
+ $user = User::where('phone', $identity)->orWhere('email', $identity)->first();
375
+ if (!$user) {
376
+ return response()->error('Unable to verify code.');
377
+ }
378
+
379
+ // Find and verify code
380
+ $verificationCode = VerificationCode::where(['subject_uuid' => $user->uuid, 'code' => $code, 'for' => $for])->exists();
381
+ if (!$verificationCode && $code !== config('fleetops.navigator.bypass_verification_code')) {
382
+ return response()->error('Invalid verification code!');
383
+ }
384
+
385
+ // Get driver record
386
+ $driver = Driver::where('user_uuid', $user->uuid)->whereNull('deleted_at')->withoutGlobalScopes()->first();
387
+ if (!$driver) {
388
+ return response()->error('No driver/agent record found for login.');
389
+ }
390
+
391
+ // Generate auth token
392
+ try {
393
+ $token = $user->createToken($driver->uuid);
394
+ } catch (\Exception $e) {
395
+ return response()->error($e->getMessage());
396
+ }
397
+
398
+ $driver->token = $token->plainTextToken;
399
+
400
+ return new $this->resource($driver);
401
+ }
402
+
403
+ /**
404
+ * Patches phone number with international code.
405
+ */
406
+ public static function phone(string $phone = null): string
407
+ {
408
+ if ($phone === null) {
409
+ $phone = request()->input('phone');
410
+ }
411
+
412
+ if (!Str::startsWith($phone, '+')) {
413
+ $phone = '+' . $phone;
414
+ }
415
+
416
+ return $phone;
417
+ }
270
418
  }
@@ -3,8 +3,12 @@
3
3
  namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
4
4
 
5
5
  use Fleetbase\Http\Controllers\Controller;
6
+ use Fleetbase\Http\Resources\Organization;
6
7
  use Fleetbase\Models\ApiCredential;
8
+ use Fleetbase\Models\Company;
7
9
  use Fleetbase\Models\User;
10
+ use Illuminate\Http\Request;
11
+ use Illuminate\Support\Str;
8
12
 
9
13
  class NavigatorController extends Controller
10
14
  {
@@ -38,4 +42,38 @@ class NavigatorController extends Controller
38
42
  'linkUrl' => url('int/v1/fleet-ops/navigator/link-app'),
39
43
  ]);
40
44
  }
45
+
46
+ public function getCurrentOrganization(Request $request)
47
+ {
48
+ $token = $request->bearerToken();
49
+ $isSecretKey = Str::startsWith($token, '$');
50
+
51
+ // Depending on API key format set the connection to find credential on
52
+ $connection = Str::startsWith($token, 'flb_test_') ? 'sandbox' : 'mysql';
53
+
54
+ // Find the API Credential record
55
+ $findApKey = ApiCredential::on($connection)
56
+ ->where(function ($query) use ($isSecretKey, $token) {
57
+ if ($isSecretKey) {
58
+ $query->where('secret', $token);
59
+ } else {
60
+ $query->where('key', $token);
61
+ }
62
+ })
63
+ ->with(['company.owner'])
64
+ ->withoutGlobalScopes();
65
+
66
+ // Get the api credential model record
67
+ $apiCredential = $findApKey->first();
68
+
69
+ // Handle no api credential found
70
+ if (!$apiCredential) {
71
+ return response()->error('No API key found to fetch company details with.');
72
+ }
73
+
74
+ // Get the organization owning the API key
75
+ $organization = Company::where('uuid', $apiCredential->company_uuid)->first();
76
+
77
+ return new Organization($organization);
78
+ }
41
79
  }
@@ -175,6 +175,60 @@ Route::prefix(config('fleetops.api.routing.prefix', null))->namespace('Fleetbase
175
175
  });
176
176
  });
177
177
 
178
+ /*
179
+ |--------------------------------------------------------------------------
180
+ | Navigator App API
181
+ |--------------------------------------------------------------------------
182
+ |
183
+ | Internal routes specific for instance wide authentication/use for navigator app
184
+ */
185
+ Route::prefix('navigator/v1')
186
+ ->group(function ($router) {
187
+ // driver/auth routes
188
+ $router->group(['prefix' => 'drivers', 'namespace' => 'Internal\v1'], function () use ($router) {
189
+ $router->post('login-with-sms', 'DriverController@loginWithPhone');
190
+ $router->post('verify-code', 'DriverController@verifyCode');
191
+ $router->post('login', 'DriverController@login');
192
+ });
193
+
194
+ // auth:sanctum
195
+ $router->group(['middleware' => ['fleetbase.protected', \Fleetbase\FleetOps\Http\Middleware\TransformLocationMiddleware::class]], function () use ($router) {
196
+ $router->group(['prefix' => 'orders'], function () use ($router) {
197
+ $router->post('/', 'Api\v1\OrderController@create');
198
+ $router->get('/', 'Api\v1\OrderController@query');
199
+ $router->get('{id}', 'Api\v1\OrderController@find');
200
+ $router->get('{id}/distance-and-time', 'Api\v1\OrderController@getDistanceMatrix');
201
+ $router->post('{id}/dispatch', 'Api\v1\OrderController@dispatchOrder');
202
+ $router->post('{id}/start', 'Api\v1\OrderController@startOrder');
203
+ $router->delete('{id}/cancel', 'Api\v1\OrderController@cancelOrder');
204
+ $router->post('{id}/update-activity', 'Api\v1\OrderController@updateActivity');
205
+ $router->post('{id}/complete', 'Api\v1\OrderController@completeOrder');
206
+ $router->get('{id}/next-activity', 'Api\v1\OrderController@getNextActivity');
207
+ $router->post('{id}/set-destination/{placeId}', 'Api\v1\OrderController@setDestination');
208
+ $router->post('{id}/capture-signature/{subjectId?}', 'Api\v1\OrderController@captureSignature');
209
+ $router->post('{id}/capture-qr/{subjectId?}', 'Api\v1\OrderController@captureQrScan');
210
+ $router->put('{id}', 'Api\v1\OrderController@update');
211
+ $router->delete('{id}', 'Api\v1\OrderController@delete');
212
+ });
213
+
214
+ $router->group(['prefix' => 'drivers'], function () use ($router) {
215
+ $router->post('{id}/register-device', 'Api\v1\DriverController@registerDevice');
216
+ $router->post('{id}/track', 'Api\v1\DriverController@track');
217
+ $router->post('{id}/switch-organization', 'Api\v1\DriverController@switchOrganization');
218
+ $router->post('/', 'Api\v1\DriverController@create');
219
+ $router->get('/', 'Api\v1\DriverController@query');
220
+ $router->get('{id}', 'Api\v1\DriverController@find');
221
+ $router->get('{id}/organizations', 'Api\v1\DriverController@listOrganizations');
222
+ $router->put('{id}', 'Api\v1\DriverController@update');
223
+ $router->delete('{id}', 'Api\v1\DriverController@delete');
224
+ });
225
+
226
+ $router->group(['prefix' => 'organizations'], function ($router) {
227
+ $router->get('current', 'Internal\v1\NavigatorController@getCurrentOrganization');
228
+ });
229
+ });
230
+ });
231
+
178
232
  /*
179
233
  |--------------------------------------------------------------------------
180
234
  | Internal FleetOps API Routes