@fleetbase/fleetops-engine 0.6.68 → 0.6.70
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 +44 -9
- package/addon/components/admin/telematics-settings.hbs +143 -0
- package/addon/components/admin/telematics-settings.js +145 -0
- package/addon/components/contact/form.hbs +13 -2
- package/addon/components/customer/details.hbs +17 -17
- package/addon/components/customer/form.hbs +13 -2
- package/addon/components/customer/form.js +0 -56
- package/addon/components/driver/details.hbs +13 -19
- package/addon/components/driver/form.hbs +20 -35
- package/addon/components/driver/form.js +0 -58
- package/addon/components/fuel-integration/hub.hbs +1 -1
- package/addon/components/layout/fleet-ops-sidebar.js +37 -17
- package/addon/components/map/leaflet-live-map.hbs +4 -242
- package/addon/components/map/leaflet-live-map.js +2 -1
- package/addon/components/map/marker-card/driver.hbs +57 -0
- package/addon/components/map/marker-card/driver.js +3 -0
- package/addon/components/map/marker-card/vehicle.hbs +78 -0
- package/addon/components/map/marker-card/vehicle.js +3 -0
- package/addon/components/modals/driver-form.hbs +2 -2
- package/addon/components/modals/reset-profile-credentials.hbs +23 -0
- package/addon/components/modals/reset-profile-credentials.js +70 -0
- package/addon/components/profile-identity-hint.hbs +13 -0
- package/addon/components/profile-identity-hint.js +49 -0
- package/addon/components/telematic/hub.hbs +1 -1
- package/addon/components/widget/live-fleet.hbs +8 -40
- package/addon/controllers/analytics/reports/index.js +5 -0
- package/addon/controllers/connectivity/devices/index.js +4 -0
- package/addon/controllers/connectivity/fuel-providers/index.js +6 -4
- package/addon/controllers/connectivity/sensors/index.js +4 -0
- package/addon/controllers/connectivity/telematics/index.js +4 -0
- package/addon/controllers/maintenance/equipment/index.js +11 -4
- package/addon/controllers/maintenance/inspection-forms/index.js +2 -2
- package/addon/controllers/maintenance/inspection-submissions/index.js +8 -2
- package/addon/controllers/maintenance/maintenances/index.js +4 -0
- package/addon/controllers/maintenance/parts/index.js +11 -4
- package/addon/controllers/maintenance/schedules/index.js +17 -4
- package/addon/controllers/maintenance/work-orders/index.js +11 -4
- package/addon/controllers/management/contacts/index.js +4 -0
- package/addon/controllers/management/drivers/index.js +8 -0
- package/addon/controllers/management/fleets/index.js +4 -0
- package/addon/controllers/management/fuel-reports/index.js +4 -0
- package/addon/controllers/management/fuel-transactions/index.js +9 -7
- package/addon/controllers/management/issues/index.js +4 -0
- package/addon/controllers/management/places/index.js +4 -0
- package/addon/controllers/management/vehicles/index.js +4 -0
- package/addon/controllers/management/vendors/index.js +4 -0
- package/addon/controllers/operations/orders/index/details.js +30 -1
- package/addon/controllers/operations/orders/index.js +7 -0
- package/addon/controllers/operations/service-rates/index.js +3 -0
- package/addon/controllers/settings/index.js +13 -0
- package/addon/controllers/settings/telematics.js +113 -0
- package/addon/extension.js +42 -8
- package/addon/routes/analytics/index.js +14 -1
- package/addon/routes/analytics/reports/index/details.js +1 -1
- package/addon/routes/analytics/reports/index/edit.js +1 -1
- package/addon/routes/analytics/reports/index/new.js +14 -1
- package/addon/routes/analytics/reports.js +14 -1
- package/addon/routes/connectivity/devices/index/new.js +14 -1
- package/addon/routes/connectivity/devices.js +14 -1
- package/addon/routes/connectivity/events.js +14 -1
- package/addon/routes/connectivity/fuel-providers/details/transactions.js +12 -0
- package/addon/routes/connectivity/fuel-providers/details.js +9 -0
- package/addon/routes/connectivity/fuel-providers/edit.js +9 -0
- package/addon/routes/connectivity/fuel-providers/new.js +14 -1
- package/addon/routes/connectivity/fuel-providers.js +14 -1
- package/addon/routes/connectivity/sensors/index/new.js +14 -1
- package/addon/routes/connectivity/sensors.js +14 -1
- package/addon/routes/connectivity/telematics/new.js +14 -1
- package/addon/routes/connectivity/telematics.js +14 -1
- package/addon/routes/connectivity/tracking.js +14 -1
- package/addon/routes/maintenance/equipment/index/details.js +2 -2
- package/addon/routes/maintenance/equipment/index/edit.js +2 -2
- package/addon/routes/maintenance/equipment/index/new.js +14 -1
- package/addon/routes/maintenance/equipment.js +14 -1
- package/addon/routes/maintenance/index.js +14 -1
- package/addon/routes/maintenance/inspection-forms/index/details.js +10 -1
- package/addon/routes/maintenance/inspection-forms/index/edit.js +10 -1
- package/addon/routes/maintenance/inspection-forms/index/new.js +14 -1
- package/addon/routes/maintenance/inspection-forms.js +14 -1
- package/addon/routes/maintenance/inspection-submissions/index/details.js +10 -1
- package/addon/routes/maintenance/inspection-submissions/index/edit.js +10 -1
- package/addon/routes/maintenance/inspection-submissions/index/new.js +14 -1
- package/addon/routes/maintenance/inspection-submissions.js +14 -1
- package/addon/routes/maintenance/maintenances/index/details.js +2 -2
- package/addon/routes/maintenance/maintenances/index/edit.js +2 -2
- package/addon/routes/maintenance/maintenances/index/new.js +14 -1
- package/addon/routes/maintenance/maintenances.js +14 -1
- package/addon/routes/maintenance/parts/index/details.js +2 -2
- package/addon/routes/maintenance/parts/index/edit.js +2 -2
- package/addon/routes/maintenance/parts/index/new.js +14 -1
- package/addon/routes/maintenance/parts.js +14 -1
- package/addon/routes/maintenance/schedules/index/details.js +2 -2
- package/addon/routes/maintenance/schedules/index/edit.js +2 -2
- package/addon/routes/maintenance/schedules/index/new.js +15 -1
- package/addon/routes/maintenance/schedules.js +15 -1
- package/addon/routes/maintenance/work-orders/index/details.js +2 -2
- package/addon/routes/maintenance/work-orders/index/edit.js +2 -2
- package/addon/routes/maintenance/work-orders/index/new.js +14 -1
- package/addon/routes/maintenance/work-orders.js +14 -1
- package/addon/routes/management/fuel-transactions/index/details.js +9 -0
- package/addon/routes/management/fuel-transactions.js +14 -1
- package/addon/routes/management/index.js +13 -0
- package/addon/routes/management/trailers.js +16 -0
- package/addon/routes/management/vendors/integrated/details.js +14 -1
- package/addon/routes/management/vendors/integrated/edit.js +14 -1
- package/addon/routes/management/vendors/integrated/new.js +14 -1
- package/addon/routes/management/vendors/integrated.js +14 -1
- package/addon/routes/operations/orders.js +40 -1
- package/addon/routes/operations/routes/index/details.js +14 -1
- package/addon/routes/operations/routes/index/new.js +13 -0
- package/addon/routes/operations/routes.js +14 -1
- package/addon/routes/operations/service-rates/index/new.js +11 -0
- package/addon/routes/operations/service-rates.js +14 -1
- package/addon/routes/settings/avatars.js +14 -1
- package/addon/routes/settings/custom-fields.js +14 -1
- package/addon/routes/settings/map.js +14 -1
- package/addon/routes/settings/notifications.js +11 -0
- package/addon/routes/settings/orchestrator.js +1 -1
- package/addon/routes/settings/payments/index.js +1 -1
- package/addon/routes/settings/routing.js +14 -1
- package/addon/routes/settings/scheduling.js +14 -1
- package/addon/routes/settings/telematics.js +21 -0
- package/addon/routes/virtual.js +11 -0
- package/addon/routes.js +1 -0
- package/addon/services/contact-actions.js +50 -89
- package/addon/services/driver-actions.js +98 -0
- package/addon/templates/settings/telematics.hbs +70 -0
- package/addon/utils/profile-login-actions.js +172 -0
- package/app/components/admin/telematics-settings.js +1 -0
- package/app/components/{modals/reset-customer-credentials.js → map/marker-card/driver.js} +1 -1
- package/app/components/map/marker-card/vehicle.js +1 -0
- package/app/components/modals/reset-profile-credentials.js +1 -0
- package/app/components/profile-identity-hint.js +1 -0
- package/app/controllers/settings/telematics.js +1 -0
- package/app/routes/settings/telematics.js +1 -0
- package/app/templates/settings/telematics.js +1 -0
- package/composer.json +2 -2
- package/docs/AFAQY.md +1 -1
- package/docs/TELEMATICS_QUEUES.md +1 -1
- package/docs/TELEMETRY_ARCHITECTURE.md +72 -1
- package/extension.json +1 -1
- package/package.json +3 -3
- package/scripts/pest-bootstrap.php +12 -0
- package/server/config/telemetry.php +12 -0
- package/server/migrations/2026_09_21_000001_convert_profile_only_users_to_managed_accounts.php +132 -0
- package/server/migrations/2026_09_23_000001_add_retention_index_to_device_events.php +40 -0
- package/server/migrations/2026_09_23_000002_add_retention_index_to_positions.php +40 -0
- package/server/migrations/2026_09_23_000003_add_retention_index_to_telematic_sync_runs.php +40 -0
- package/server/resources/views/mail/driver-credentials.blade.php +21 -0
- package/server/src/Auth/Schemas/FleetOps.php +104 -5
- package/server/src/Console/Commands/DrainTelematicInbox.php +3 -13
- package/server/src/Console/Commands/FixCustomerCompanies.php +1 -1
- package/server/src/Console/Commands/FixDriverCompanies.php +1 -1
- package/server/src/Console/Commands/ProcessOperationalAlerts.php +3 -3
- package/server/src/Console/Commands/PruneTelematicsData.php +318 -0
- package/server/src/Exceptions/ProfileIdentityConflictException.php +34 -0
- package/server/src/Http/Controllers/Api/v1/CustomerController.php +14 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +56 -39
- package/server/src/Http/Controllers/Api/v1/OrchestrationController.php +9 -0
- package/server/src/Http/Controllers/FleetOpsController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +16 -17
- package/server/src/Http/Controllers/Internal/v1/CustomerController.php +54 -4
- package/server/src/Http/Controllers/Internal/v1/DeviceController.php +13 -0
- package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/DriverController.php +204 -162
- package/server/src/Http/Controllers/Internal/v1/FleetOpsLookupController.php +47 -0
- package/server/src/Http/Controllers/Internal/v1/FuelProviderConnectionController.php +13 -0
- package/server/src/Http/Controllers/Internal/v1/FuelProviderTransactionController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/FuelReportController.php +9 -3
- package/server/src/Http/Controllers/Internal/v1/InspectionFormController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +16 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/ManifestController.php +15 -0
- package/server/src/Http/Controllers/Internal/v1/MetricsController.php +11 -0
- package/server/src/Http/Controllers/Internal/v1/NavigatorController.php +42 -1
- package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +12 -0
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +178 -27
- package/server/src/Http/Controllers/Internal/v1/PaymentController.php +13 -0
- package/server/src/Http/Controllers/Internal/v1/RadarController.php +19 -0
- package/server/src/Http/Controllers/Internal/v1/ServiceAreaController.php +9 -3
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +465 -14
- package/server/src/Http/Controllers/Internal/v1/TelematicController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/TrailerController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +21 -0
- package/server/src/Http/Controllers/Internal/v1/VendorController.php +17 -0
- package/server/src/Http/Controllers/Internal/v1/WorkOrderController.php +11 -0
- package/server/src/Http/Requests/CreateDriverRequest.php +6 -2
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +9 -18
- package/server/src/Http/Resources/v1/Contact.php +3 -0
- package/server/src/Http/Resources/v1/Driver.php +3 -0
- package/server/src/Jobs/DispatchTelematicsRetentionJobs.php +70 -0
- package/server/src/Jobs/PruneTelematicsDataJob.php +74 -0
- package/server/src/Listeners/NotifyDriverOnShiftChange.php +3 -3
- package/server/src/Mail/CustomerCredentialsMail.php +1 -1
- package/server/src/Mail/DriverCredentialsMail.php +55 -0
- package/server/src/Models/Contact.php +55 -90
- package/server/src/Models/Sensor.php +3 -5
- package/server/src/Observers/ContactObserver.php +2 -22
- package/server/src/Observers/DriverObserver.php +5 -6
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +9 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +18 -0
- package/server/src/Support/Ai/Capabilities/AbstractFleetOpsAICapability.php +62 -5
- package/server/src/Support/Ai/Capabilities/AssetStatusCapability.php +24 -5
- package/server/src/Support/Ai/Capabilities/CreateOrderPreviewCapability.php +6 -2
- package/server/src/Support/Ai/Capabilities/OrderInsightsCapability.php +40 -7
- package/server/src/Support/Ai/Capabilities/SearchResourcesCapability.php +86 -24
- package/server/src/Support/Ai/FleetOpsAiConsoleCommands.php +122 -0
- package/server/src/Support/Ai/Tools/CreateOrderTool.php +86 -0
- package/server/src/Support/Ai/Tools/ImportOrdersTool.php +53 -0
- package/server/src/Support/Ai/Tools/OptimizeOrderRouteTool.php +70 -0
- package/server/src/Support/Ai/Tools/SearchResourcesTool.php +73 -0
- package/server/src/Support/Analytics/LiveFleet.php +18 -2
- package/server/src/Support/Authorization.php +103 -0
- package/server/src/Support/Database/TableIndexes.php +66 -0
- package/server/src/Support/OrderTracker.php +2 -2
- package/server/src/Support/ProfileAccountManager.php +405 -0
- package/server/src/Support/Reporting/FleetOpsReportSchema.php +476 -116
- package/server/src/Support/Telematics/Retention/RetentionPolicy.php +300 -0
- package/server/src/Support/Telematics/Retention/TelemetryActivity.php +40 -0
- package/server/src/Support/Telematics/TelematicService.php +12 -2
- package/server/src/Support/Telematics/Telemetry/Ingestor.php +4 -2
- package/server/src/Tracking/TrackingIntelligenceService.php +1 -1
- package/server/src/Tracking/TrackingOptions.php +2 -2
- package/server/src/Traits/AuthorizesMethods.php +37 -0
- package/server/src/routes.php +14 -1
- package/server/tests/AiOperationalQueryCapabilityTest.php +1 -0
- package/server/tests/AnalyticsRoutesTest.php +29 -2
- package/server/tests/ApiCustomerControllerContractsTest.php +21 -0
- package/server/tests/ApiDriverControllerContractsTest.php +102 -20
- package/server/tests/CommandContractsTest.php +4 -4
- package/server/tests/ControllerHelperContractsTest.php +3 -11
- package/server/tests/Feature/Http/AfaqyRealtimeIngestionTest.php +21 -1
- package/server/tests/Feature/Http/Api/DriverControllerAuthFlowsTest.php +36 -0
- package/server/tests/Feature/Http/Api/DriverControllerSessionHelpersTest.php +19 -6
- package/server/tests/Feature/Http/Api/ProfileOnlyUserConversionMigrationTest.php +201 -0
- package/server/tests/Feature/Http/Api/TelematicsRetentionIndexMigrationTest.php +115 -0
- package/server/tests/Feature/Http/Internal/ContactControllerEndpointsTest.php +6 -4
- package/server/tests/Feature/Http/Internal/CustomerControllerCredentialsTest.php +6 -2
- package/server/tests/Feature/Http/Internal/DriverControllerContractsTest.php +23 -0
- package/server/tests/Feature/Http/Internal/DriverControllerCreateRecordTest.php +42 -17
- package/server/tests/Feature/Http/Internal/DriverControllerExistingUserAdoptionTest.php +168 -277
- package/server/tests/Feature/Http/Internal/DriverControllerLoginManagementTest.php +716 -0
- package/server/tests/Feature/Http/Internal/OrderControllerContractsTest.php +10 -3
- package/server/tests/Feature/Http/Internal/OrderControllerImportFromFilesTest.php +18 -0
- package/server/tests/Feature/Http/Internal/OrderControllerTenantScopingTest.php +411 -0
- package/server/tests/Feature/Http/Internal/OrderControllerUpstreamNotFoundTest.php +8 -20
- package/server/tests/Feature/Http/Internal/SettingControllerHelpersTest.php +71 -0
- package/server/tests/Feature/Http/Internal/TelematicsStorageDeadlinesTest.php +119 -0
- package/server/tests/InternalCustomerControllerContractsTest.php +32 -1
- package/server/tests/InternalImportExportControllerContractsTest.php +5 -8
- package/server/tests/ModelAccessorContractsTest.php +82 -20
- package/server/tests/NavigatorControllerContractsTest.php +36 -4
- package/server/tests/NotificationAndMailContractsTest.php +31 -0
- package/server/tests/ObserverContractsTest.php +28 -3
- package/server/tests/OrchestratorConsumableApiTest.php +42 -0
- package/server/tests/ProviderContractsTest.php +10 -0
- package/server/tests/ReportSchemaContractsTest.php +120 -6
- package/server/tests/RequestContractsTest.php +19 -11
- package/server/tests/SettingControllerContractsTest.php +429 -18
- package/server/tests/SmallControllerContractsTest.php +8 -5
- package/server/tests/Support/PruneTelematicsDataProbe.php +55 -0
- package/server/tests/SupportJobAndAiCoverageTest.php +3 -3
- package/server/tests/TelematicControllerContractsTest.php +22 -4
- package/server/tests/Unit/Console/Commands/ProcessOperationalAlertsCommandTest.php +2 -2
- package/server/tests/Unit/Console/PruneTelematicsDataCommandTest.php +353 -0
- package/server/tests/Unit/Jobs/DispatchTelematicsRetentionJobsTest.php +114 -0
- package/server/tests/Unit/Jobs/PruneTelematicsDataJobTest.php +60 -0
- package/server/tests/Unit/Models/ContactCustomerUserTest.php +90 -14
- package/server/tests/Unit/Models/ContactTest.php +48 -5
- package/server/tests/Unit/Models/SensorTest.php +2 -2
- package/server/tests/Unit/Models/SmallModelSurfacesTest.php +31 -32
- package/server/tests/Unit/Observers/ObserverListenerSeamsTest.php +7 -2
- package/server/tests/Unit/Orchestration/VroomEngineTransportAndSettingsTest.php +20 -0
- package/server/tests/Unit/Providers/FleetOpsAiRegistrationTest.php +15 -2
- package/server/tests/Unit/Support/Ai/AssetStatusCapabilityQueriesTest.php +42 -0
- package/server/tests/Unit/Support/Ai/Capabilities/OrderInsightsCapabilityTest.php +19 -1
- package/server/tests/Unit/Support/Ai/Capabilities/SearchResourcesCapabilityTest.php +85 -1
- package/server/tests/Unit/Support/Ai/FleetOpsAiConsoleCommandsTest.php +31 -0
- package/server/tests/Unit/Support/Ai/Tools/FleetOpsAiToolsTest.php +246 -0
- package/server/tests/Unit/Support/AiCapabilityPermissionsTest.php +3 -3
- package/server/tests/Unit/Support/Analytics/LiveFleetTest.php +6 -0
- package/server/tests/Unit/Support/AuthorizationTest.php +142 -0
- package/server/tests/Unit/Support/MaintainableAndOperationalAlertsTest.php +12 -1
- package/server/tests/Unit/Support/MoreQuerySeamsTest.php +11 -2
- package/server/tests/Unit/Support/ProfileAccountManagerTest.php +643 -0
- package/server/tests/Unit/Support/Telematics/Retention/RetentionPolicyTest.php +190 -0
- package/server/tests/Unit/Support/Telematics/Retention/TelemetryActivityTest.php +68 -0
- package/server/tests/Unit/Tracking/TrackingOptionsCompanySettingsTest.php +109 -0
- package/translations/en-us.yaml +85 -2
- package/translations/uk-ua.yaml +3475 -0
- package/addon/components/modals/reset-customer-credentials.hbs +0 -18
- package/addon/components/modals/reset-customer-credentials.js +0 -55
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Console\Commands;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Support\Telematics\Retention\RetentionPolicy;
|
|
6
|
+
use Illuminate\Console\Command;
|
|
7
|
+
use Illuminate\Support\Collection;
|
|
8
|
+
use Illuminate\Support\Facades\Cache;
|
|
9
|
+
use Illuminate\Support\Facades\DB;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Applies each company's telematics retention policy.
|
|
13
|
+
*
|
|
14
|
+
* Deletes are hard deletes issued in bounded batches so a large backlog never
|
|
15
|
+
* monopolizes the tables ingestion is writing to; a capped run picks up where
|
|
16
|
+
* it left off on the next schedule tick.
|
|
17
|
+
*/
|
|
18
|
+
class PruneTelematicsData extends Command
|
|
19
|
+
{
|
|
20
|
+
public const TABLES = ['device_events', 'positions', 'telematic_deliveries', 'telematic_sync_runs'];
|
|
21
|
+
|
|
22
|
+
public const LOCK = 'fleetops:prune-telematics-data';
|
|
23
|
+
|
|
24
|
+
protected $signature = 'fleetops:prune-telematics-data
|
|
25
|
+
{--company= : Only prune data for this company (uuid or public id)}
|
|
26
|
+
{--orphans-only : Only prune data without an existing company or connection}
|
|
27
|
+
{--table=* : Only prune these tables (device_events, positions, telematic_deliveries, telematic_sync_runs)}
|
|
28
|
+
{--batch-size=1000 : Rows per delete or update statement}
|
|
29
|
+
{--max-batches=50 : Maximum batches per table per company per run}
|
|
30
|
+
{--dry-run : Report matching rows without deleting or compacting}
|
|
31
|
+
{--no-lock : Skip process locking}';
|
|
32
|
+
|
|
33
|
+
protected $description = 'Prune telematics device events, positions, deliveries and sync runs according to each company\'s retention policy.';
|
|
34
|
+
|
|
35
|
+
protected int $batchSize = 1000;
|
|
36
|
+
protected int $maxBatches = 50;
|
|
37
|
+
protected bool $dryRun = false;
|
|
38
|
+
|
|
39
|
+
public function handle(): int
|
|
40
|
+
{
|
|
41
|
+
$tables = $this->requestedTables();
|
|
42
|
+
if ($tables === null) {
|
|
43
|
+
$this->error('Unknown table. Valid tables: ' . implode(', ', self::TABLES) . '.');
|
|
44
|
+
|
|
45
|
+
return self::FAILURE;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
$lock = null;
|
|
49
|
+
if (!$this->option('no-lock')) {
|
|
50
|
+
$lock = Cache::lock(self::LOCK, 840);
|
|
51
|
+
if (!$lock->get()) {
|
|
52
|
+
$this->warn('Another telematics prune run appears to be in progress.');
|
|
53
|
+
|
|
54
|
+
return self::SUCCESS;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// Queue workers may have cached settings before an administrator saved a new policy.
|
|
60
|
+
RetentionPolicy::flush();
|
|
61
|
+
$this->batchSize = max(100, min(5000, (int) $this->option('batch-size') ?: 1000));
|
|
62
|
+
$this->maxBatches = max(1, (int) $this->option('max-batches') ?: 50);
|
|
63
|
+
$this->dryRun = (bool) $this->option('dry-run');
|
|
64
|
+
|
|
65
|
+
$only = ((string) $this->option('company')) ?: null;
|
|
66
|
+
$orphansOnly = (bool) $this->option('orphans-only');
|
|
67
|
+
if ($only && $orphansOnly) {
|
|
68
|
+
$this->error('Choose either --company or --orphans-only.');
|
|
69
|
+
|
|
70
|
+
return self::FAILURE;
|
|
71
|
+
}
|
|
72
|
+
$companies = $orphansOnly ? new Collection() : $this->companies($only);
|
|
73
|
+
if ($only && $companies->isEmpty()) {
|
|
74
|
+
$this->error(sprintf('Company [%s] was not found.', $only));
|
|
75
|
+
|
|
76
|
+
return self::FAILURE;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
$totals = $this->emptyStats();
|
|
80
|
+
foreach ($companies as $company) {
|
|
81
|
+
$stats = $this->pruneCompany($company->uuid, $this->policyFor($company->uuid), $tables);
|
|
82
|
+
$totals = $this->mergeStats($totals, $stats);
|
|
83
|
+
$this->report($company->public_id ?: $company->uuid, $stats);
|
|
84
|
+
}
|
|
85
|
+
if (!$only) {
|
|
86
|
+
$stats = $this->pruneOrphans($this->policyFor(null), $tables);
|
|
87
|
+
$totals = $this->mergeStats($totals, $stats);
|
|
88
|
+
$this->report('orphaned', $stats);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
$this->info(sprintf(
|
|
92
|
+
'%s: deleted=%d compacted=%d batches=%d%s',
|
|
93
|
+
$this->dryRun ? 'Telematics prune dry run' : 'Telematics prune complete',
|
|
94
|
+
$totals['deleted'],
|
|
95
|
+
$totals['compacted'],
|
|
96
|
+
$totals['batches'],
|
|
97
|
+
$totals['capped'] ? ' (capped; more rows remain for the next run)' : ''
|
|
98
|
+
));
|
|
99
|
+
|
|
100
|
+
return self::SUCCESS;
|
|
101
|
+
} finally {
|
|
102
|
+
$lock?->release();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @return string[]|null null when an unknown table was requested
|
|
108
|
+
*/
|
|
109
|
+
protected function requestedTables(): ?array
|
|
110
|
+
{
|
|
111
|
+
$requested = array_values(array_filter((array) $this->option('table')));
|
|
112
|
+
if ($requested === []) {
|
|
113
|
+
return self::TABLES;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return array_diff($requested, self::TABLES) === [] ? array_values(array_unique($requested)) : null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
protected function companies(?string $only): Collection
|
|
120
|
+
{
|
|
121
|
+
$query = DB::table('companies')->select(['id', 'uuid', 'public_id'])->orderBy('id');
|
|
122
|
+
if ($only) {
|
|
123
|
+
$query->where(fn ($where) => $where->where('uuid', $only)->orWhere('public_id', $only));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return $query->get();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
protected function policyFor(?string $companyUuid): RetentionPolicy
|
|
130
|
+
{
|
|
131
|
+
return RetentionPolicy::forCompany($companyUuid);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
protected function pruneCompany(string $companyUuid, RetentionPolicy $policy, array $tables): array
|
|
135
|
+
{
|
|
136
|
+
$rows = fn ($query) => $query->where('company_uuid', $companyUuid);
|
|
137
|
+
|
|
138
|
+
$telematics = null;
|
|
139
|
+
if (array_intersect(['telematic_deliveries', 'telematic_sync_runs'], $tables) !== []) {
|
|
140
|
+
// Inbox tables carry no company; trashed connections still prune under their company's policy.
|
|
141
|
+
$telematics = DB::table('telematics')->where('company_uuid', $companyUuid)->pluck('uuid')->all();
|
|
142
|
+
}
|
|
143
|
+
$inbox = $telematics ? fn ($query) => $query->whereIn('telematic_uuid', $telematics) : null;
|
|
144
|
+
|
|
145
|
+
return $this->prune($rows, $inbox, $policy, $tables);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Rows whose company or connection no longer exists fall back to the system defaults.
|
|
150
|
+
*/
|
|
151
|
+
protected function pruneOrphans(RetentionPolicy $policy, array $tables): array
|
|
152
|
+
{
|
|
153
|
+
$rows = fn ($query) => $query->where(function ($where) {
|
|
154
|
+
$where->whereNull('company_uuid')->orWhereNotIn('company_uuid', DB::table('companies')->select('uuid'));
|
|
155
|
+
});
|
|
156
|
+
$inbox = fn ($query) => $query->whereNotExists(function ($connections) use ($query) {
|
|
157
|
+
$connections->selectRaw('1')
|
|
158
|
+
->from('telematics')
|
|
159
|
+
->join('companies', 'companies.uuid', '=', 'telematics.company_uuid')
|
|
160
|
+
->whereColumn('telematics.uuid', $query->from . '.telematic_uuid');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return $this->prune($rows, $inbox, $policy, $tables);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
protected function prune(\Closure $rows, ?\Closure $inbox, RetentionPolicy $policy, array $tables): array
|
|
167
|
+
{
|
|
168
|
+
$stats = $this->emptyStats();
|
|
169
|
+
if (in_array('device_events', $tables, true)) {
|
|
170
|
+
$stats['tables']['device_events'] = $this->pruneDeviceEvents($rows, $policy);
|
|
171
|
+
}
|
|
172
|
+
if (in_array('positions', $tables, true)) {
|
|
173
|
+
$stats['tables']['positions'] = $this->prunePositions($rows, $policy);
|
|
174
|
+
}
|
|
175
|
+
if ($inbox && in_array('telematic_deliveries', $tables, true)) {
|
|
176
|
+
$stats['tables']['telematic_deliveries'] = $this->pruneDeliveries($inbox, $policy);
|
|
177
|
+
}
|
|
178
|
+
if ($inbox && in_array('telematic_sync_runs', $tables, true)) {
|
|
179
|
+
$stats['tables']['telematic_sync_runs'] = $this->pruneSyncRuns($inbox, $policy);
|
|
180
|
+
}
|
|
181
|
+
foreach ($stats['tables'] as $table) {
|
|
182
|
+
$stats['deleted'] += $table['deleted'];
|
|
183
|
+
$stats['compacted'] += $table['compacted'];
|
|
184
|
+
$stats['batches'] += $table['batches'];
|
|
185
|
+
$stats['capped'] = $stats['capped'] || $table['capped'];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return $stats;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
protected function pruneDeviceEvents(\Closure $rows, RetentionPolicy $policy): array
|
|
192
|
+
{
|
|
193
|
+
$stats = $this->emptyTableStats();
|
|
194
|
+
$cutoff = $policy->cutoff('event_retention_days');
|
|
195
|
+
// Soft-deleted events only grow the table; purge them regardless of age.
|
|
196
|
+
$this->deleteInBatches('device_events', 'id', fn ($query) => $rows($query)->whereNotNull('deleted_at'), $stats);
|
|
197
|
+
if ($cutoff) {
|
|
198
|
+
$this->deleteInBatches('device_events', 'id', fn ($query) => $rows($query)->where('created_at', '<', $cutoff->toDateTimeString()), $stats);
|
|
199
|
+
}
|
|
200
|
+
if ($policy->compactsEvents() && ($compactAt = $policy->cutoff('event_compact_after_days'))) {
|
|
201
|
+
// Keep the event (type, severity, location, normalized data) but drop the raw provider blobs.
|
|
202
|
+
// Rows past the delete cutoff are left to the delete sweep rather than rewritten first.
|
|
203
|
+
$this->updateInBatches(
|
|
204
|
+
'device_events',
|
|
205
|
+
'id',
|
|
206
|
+
function ($query) use ($rows, $cutoff, $compactAt) {
|
|
207
|
+
$query = $rows($query)->where('created_at', '<', $compactAt->toDateTimeString())->where(fn ($where) => $where->whereNotNull('payload')->orWhereNotNull('meta'));
|
|
208
|
+
|
|
209
|
+
return $cutoff ? $query->where('created_at', '>=', $cutoff->toDateTimeString()) : $query;
|
|
210
|
+
},
|
|
211
|
+
['payload' => null, 'meta' => null],
|
|
212
|
+
$stats
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return $stats;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
protected function prunePositions(\Closure $rows, RetentionPolicy $policy): array
|
|
220
|
+
{
|
|
221
|
+
$stats = $this->emptyTableStats();
|
|
222
|
+
$this->deleteInBatches('positions', 'id', fn ($query) => $rows($query)->whereNotNull('deleted_at'), $stats);
|
|
223
|
+
if ($cutoff = $policy->cutoff('position_retention_days')) {
|
|
224
|
+
$this->deleteInBatches('positions', 'id', fn ($query) => $rows($query)->where('created_at', '<', $cutoff->toDateTimeString()), $stats);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return $stats;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
protected function pruneDeliveries(\Closure $inbox, RetentionPolicy $policy): array
|
|
231
|
+
{
|
|
232
|
+
$stats = $this->emptyTableStats();
|
|
233
|
+
foreach (['processed' => 'processed_retention_hours', 'quarantined' => 'quarantine_retention_days'] as $status => $key) {
|
|
234
|
+
if ($cutoff = $policy->cutoff($key)) {
|
|
235
|
+
$this->deleteInBatches('telematic_deliveries', 'uuid', fn ($query) => $inbox($query)->where('status', $status)->where('updated_at', '<', $cutoff->toDateTimeString()), $stats);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return $stats;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
protected function pruneSyncRuns(\Closure $inbox, RetentionPolicy $policy): array
|
|
243
|
+
{
|
|
244
|
+
$stats = $this->emptyTableStats();
|
|
245
|
+
if ($cutoff = $policy->cutoff('sync_run_retention_days')) {
|
|
246
|
+
// In-flight runs are recovered by the drain command, never expired here.
|
|
247
|
+
$this->deleteInBatches('telematic_sync_runs', 'uuid', fn ($query) => $inbox($query)->whereNotIn('status', ['fetching', 'ingesting'])->where('updated_at', '<', $cutoff->toDateTimeString()), $stats);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return $stats;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
protected function deleteInBatches(string $table, string $key, \Closure $scope, array &$stats): void
|
|
254
|
+
{
|
|
255
|
+
if ($this->dryRun) {
|
|
256
|
+
$stats['deleted'] += $scope(DB::table($table))->count();
|
|
257
|
+
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
for ($batch = 0; $batch < $this->maxBatches; $batch++) {
|
|
261
|
+
$ids = $scope(DB::table($table))->orderBy($key)->limit($this->batchSize)->pluck($key);
|
|
262
|
+
if ($ids->isEmpty()) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
$stats['batches']++;
|
|
266
|
+
$stats['deleted'] += DB::table($table)->whereIn($key, $ids->all())->delete();
|
|
267
|
+
}
|
|
268
|
+
$stats['capped'] = true;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
protected function updateInBatches(string $table, string $key, \Closure $scope, array $values, array &$stats): void
|
|
272
|
+
{
|
|
273
|
+
if ($this->dryRun) {
|
|
274
|
+
$stats['compacted'] += $scope(DB::table($table))->count();
|
|
275
|
+
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
for ($batch = 0; $batch < $this->maxBatches; $batch++) {
|
|
279
|
+
$ids = $scope(DB::table($table))->orderBy($key)->limit($this->batchSize)->pluck($key);
|
|
280
|
+
if ($ids->isEmpty()) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
$stats['batches']++;
|
|
284
|
+
$stats['compacted'] += DB::table($table)->whereIn($key, $ids->all())->update($values);
|
|
285
|
+
}
|
|
286
|
+
$stats['capped'] = true;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
protected function report(string $label, array $stats): void
|
|
290
|
+
{
|
|
291
|
+
foreach ($stats['tables'] as $table => $counts) {
|
|
292
|
+
if (!$counts['deleted'] && !$counts['compacted'] && !$counts['capped']) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
$this->info(sprintf('%s %s: deleted=%d compacted=%d batches=%d%s', $label, $table, $counts['deleted'], $counts['compacted'], $counts['batches'], $counts['capped'] ? ' capped' : ''));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
protected function emptyTableStats(): array
|
|
300
|
+
{
|
|
301
|
+
return ['deleted' => 0, 'compacted' => 0, 'batches' => 0, 'capped' => false];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
protected function emptyStats(): array
|
|
305
|
+
{
|
|
306
|
+
return ['deleted' => 0, 'compacted' => 0, 'batches' => 0, 'capped' => false, 'tables' => []];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
protected function mergeStats(array $totals, array $stats): array
|
|
310
|
+
{
|
|
311
|
+
$totals['deleted'] += $stats['deleted'];
|
|
312
|
+
$totals['compacted'] += $stats['compacted'];
|
|
313
|
+
$totals['batches'] += $stats['batches'];
|
|
314
|
+
$totals['capped'] = $totals['capped'] || $stats['capped'];
|
|
315
|
+
|
|
316
|
+
return $totals;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Exceptions;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Thrown when a driver, contact or customer profile is given an email or phone
|
|
7
|
+
* that belongs to an account it cannot be linked to.
|
|
8
|
+
*/
|
|
9
|
+
class ProfileIdentityConflictException extends \Exception
|
|
10
|
+
{
|
|
11
|
+
/**
|
|
12
|
+
* The profile field the conflict was found on (`email` or `phone`).
|
|
13
|
+
*/
|
|
14
|
+
protected string $field;
|
|
15
|
+
|
|
16
|
+
public function __construct(string $message, string $field = 'email')
|
|
17
|
+
{
|
|
18
|
+
parent::__construct($message);
|
|
19
|
+
$this->field = $field;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public function getField(): string
|
|
23
|
+
{
|
|
24
|
+
return $this->field;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The conflict keyed by field, in the shape of a validation error bag.
|
|
29
|
+
*/
|
|
30
|
+
public function getErrors(): array
|
|
31
|
+
{
|
|
32
|
+
return [$this->field => [$this->getMessage()]];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -44,6 +44,8 @@ class CustomerController extends Controller
|
|
|
44
44
|
{
|
|
45
45
|
use \Fleetbase\FleetOps\Http\Controllers\Concerns\ResolvesReviewAccountBypass;
|
|
46
46
|
|
|
47
|
+
public const DEACTIVATED_LOGIN_MESSAGE = 'This customer login has been deactivated.';
|
|
48
|
+
|
|
47
49
|
/* ============================================================
|
|
48
50
|
| Public auth flows (API credential only, no Customer-Token)
|
|
49
51
|
* ============================================================ */
|
|
@@ -345,6 +347,10 @@ class CustomerController extends Controller
|
|
|
345
347
|
return response()->apiError('Authentication failed using credentials provided.', 401);
|
|
346
348
|
}
|
|
347
349
|
|
|
350
|
+
if ($user->status === 'inactive') {
|
|
351
|
+
return response()->apiError(static::DEACTIVATED_LOGIN_MESSAGE, 403);
|
|
352
|
+
}
|
|
353
|
+
|
|
348
354
|
$sessionCompany = $this->sessionCompany();
|
|
349
355
|
if (!$sessionCompany) {
|
|
350
356
|
return response()->apiError('No company resolved from API credential.', 500);
|
|
@@ -381,6 +387,10 @@ class CustomerController extends Controller
|
|
|
381
387
|
return response()->apiError('No customer with this phone number found.');
|
|
382
388
|
}
|
|
383
389
|
|
|
390
|
+
if ($user->status === 'inactive') {
|
|
391
|
+
return response()->apiError(static::DEACTIVATED_LOGIN_MESSAGE, 403);
|
|
392
|
+
}
|
|
393
|
+
|
|
384
394
|
try {
|
|
385
395
|
$this->generateSmsVerification($user, 'fleetops_customer_login', [
|
|
386
396
|
'messageCallback' => fn ($verification) => 'Your ' . config('app.name') . ' verification code is ' . $verification->code,
|
|
@@ -423,6 +433,10 @@ class CustomerController extends Controller
|
|
|
423
433
|
return response()->apiError('Unable to verify code.');
|
|
424
434
|
}
|
|
425
435
|
|
|
436
|
+
if ($user->status === 'inactive') {
|
|
437
|
+
return response()->apiError(static::DEACTIVATED_LOGIN_MESSAGE, 403);
|
|
438
|
+
}
|
|
439
|
+
|
|
426
440
|
$verificationCode = $this->verificationCodeExists([
|
|
427
441
|
'subject_uuid' => $user->uuid,
|
|
428
442
|
'code' => $code,
|
|
@@ -6,6 +6,7 @@ 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\ProfileIdentityConflictException;
|
|
9
10
|
use Fleetbase\FleetOps\Exceptions\PublicRelationNotFoundException;
|
|
10
11
|
use Fleetbase\FleetOps\Http\Controllers\Api\v1\Concerns\ResolvesFleetOpsApiResources;
|
|
11
12
|
use Fleetbase\FleetOps\Http\Controllers\Api\v1\Concerns\ResolvesPublicExpansions;
|
|
@@ -22,6 +23,7 @@ use Fleetbase\FleetOps\Models\Vehicle;
|
|
|
22
23
|
use Fleetbase\FleetOps\Models\Vendor;
|
|
23
24
|
use Fleetbase\FleetOps\Support\GeofenceIntersectionService;
|
|
24
25
|
use Fleetbase\FleetOps\Support\OSRM;
|
|
26
|
+
use Fleetbase\FleetOps\Support\ProfileAccountManager;
|
|
25
27
|
use Fleetbase\FleetOps\Support\Utils;
|
|
26
28
|
use Fleetbase\Http\Controllers\Controller;
|
|
27
29
|
use Fleetbase\Http\Requests\SwitchOrganizationRequest;
|
|
@@ -47,6 +49,11 @@ class DriverController extends Controller
|
|
|
47
49
|
use ResolvesFleetOpsApiResources;
|
|
48
50
|
use ResolvesPublicExpansions;
|
|
49
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Returned when a driver whose login was deactivated from the console tries to sign in.
|
|
54
|
+
*/
|
|
55
|
+
public const DEACTIVATED_LOGIN_MESSAGE = 'This driver login has been deactivated.';
|
|
56
|
+
|
|
50
57
|
/**
|
|
51
58
|
* Public expansion name => Eloquent relation name.
|
|
52
59
|
*
|
|
@@ -97,20 +104,14 @@ class DriverController extends Controller
|
|
|
97
104
|
// Apply user infos
|
|
98
105
|
$userDetails = $this->applyUserInfoFromRequest($request, $userDetails);
|
|
99
106
|
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
// Set user type
|
|
110
|
-
$user->setUserType('driver');
|
|
111
|
-
|
|
112
|
-
// assign driver role
|
|
113
|
-
$user->assignSingleRole('Driver');
|
|
107
|
+
// Resolve the driver's login account: a team member of the company with
|
|
108
|
+
// the email/phone is linked, otherwise a driver account is created and
|
|
109
|
+
// added to the company without an organization invite.
|
|
110
|
+
try {
|
|
111
|
+
$user = $this->resolveDriverAccount($company->uuid, $userDetails);
|
|
112
|
+
} catch (ProfileIdentityConflictException $exception) {
|
|
113
|
+
return $this->jsonResponse(['error' => $exception->getMessage()], 422);
|
|
114
|
+
}
|
|
114
115
|
|
|
115
116
|
// set user id
|
|
116
117
|
$input['user_uuid'] = $user->uuid;
|
|
@@ -193,10 +194,11 @@ class DriverController extends Controller
|
|
|
193
194
|
// it. The driver's own record has no timezone column; the user's does.
|
|
194
195
|
$userDetails = $request->only(['name', 'email', 'phone', 'timezone']);
|
|
195
196
|
|
|
196
|
-
// update driver
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
// update the driver's login account through its proxy fields
|
|
198
|
+
try {
|
|
199
|
+
ProfileAccountManager::syncProxyFields($driver->getUser(), $userDetails);
|
|
200
|
+
} catch (ProfileIdentityConflictException $exception) {
|
|
201
|
+
return $this->jsonResponse(['error' => $exception->getMessage()], 422);
|
|
200
202
|
}
|
|
201
203
|
|
|
202
204
|
// latitude / longitude
|
|
@@ -519,10 +521,14 @@ class DriverController extends Controller
|
|
|
519
521
|
)->whereHas('driver')->first();
|
|
520
522
|
|
|
521
523
|
// Check password to authenticate driver
|
|
522
|
-
if (!Hash::check($password, $user->password)) {
|
|
524
|
+
if (!$user || !is_string($user->password) || !Hash::check((string) $password, $user->password)) {
|
|
523
525
|
return response()->apiError('Authentication failed using password provided.', 401);
|
|
524
526
|
}
|
|
525
527
|
|
|
528
|
+
if (static::isLoginDeactivated($user)) {
|
|
529
|
+
return response()->apiError(static::DEACTIVATED_LOGIN_MESSAGE, 403);
|
|
530
|
+
}
|
|
531
|
+
|
|
526
532
|
// Get the user's company for this driver profile
|
|
527
533
|
$company = static::getDriverCompanyFromUser($user);
|
|
528
534
|
|
|
@@ -561,6 +567,10 @@ class DriverController extends Controller
|
|
|
561
567
|
return response()->apiError('No driver with this phone # found.');
|
|
562
568
|
}
|
|
563
569
|
|
|
570
|
+
if (static::isLoginDeactivated($user)) {
|
|
571
|
+
return response()->apiError(static::DEACTIVATED_LOGIN_MESSAGE, 403);
|
|
572
|
+
}
|
|
573
|
+
|
|
564
574
|
// Get the user's company for this driver profile
|
|
565
575
|
$company = static::getDriverCompanyFromUser($user);
|
|
566
576
|
|
|
@@ -627,6 +637,10 @@ class DriverController extends Controller
|
|
|
627
637
|
return response()->apiError('Unable to verify code.');
|
|
628
638
|
}
|
|
629
639
|
|
|
640
|
+
if (static::isLoginDeactivated($user)) {
|
|
641
|
+
return response()->apiError(static::DEACTIVATED_LOGIN_MESSAGE, 403);
|
|
642
|
+
}
|
|
643
|
+
|
|
630
644
|
// find and verify code
|
|
631
645
|
$verificationCode = VerificationCode::where(['subject_uuid' => $user->uuid, 'code' => $code, 'for' => $for])->exists();
|
|
632
646
|
if (!$verificationCode && !static::verificationBypassMatches($identity, $code)) {
|
|
@@ -975,26 +989,29 @@ class DriverController extends Controller
|
|
|
975
989
|
return User::applyUserInfoFromRequest($request, $userDetails);
|
|
976
990
|
}
|
|
977
991
|
|
|
978
|
-
|
|
992
|
+
/**
|
|
993
|
+
* Resolve the login account for a driver created through the API.
|
|
994
|
+
*
|
|
995
|
+
* @throws ProfileIdentityConflictException
|
|
996
|
+
*/
|
|
997
|
+
protected function resolveDriverAccount(string $companyUuid, array $userDetails): User
|
|
979
998
|
{
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
$user = User::create($userDetails);
|
|
991
|
-
|
|
992
|
-
if (is_string($password) && strlen($password)) {
|
|
993
|
-
$user->password = $password;
|
|
994
|
-
$user->save();
|
|
995
|
-
}
|
|
999
|
+
return ProfileAccountManager::resolveForProfile(
|
|
1000
|
+
$companyUuid,
|
|
1001
|
+
'driver',
|
|
1002
|
+
$userDetails['name'] ?? null,
|
|
1003
|
+
$userDetails['email'] ?? null,
|
|
1004
|
+
$userDetails['phone'] ?? null,
|
|
1005
|
+
array_merge(Arr::except($userDetails, ['name', 'email', 'phone']), ['status' => 'active'])
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
996
1008
|
|
|
997
|
-
|
|
1009
|
+
/**
|
|
1010
|
+
* Whether the driver's login was deactivated from the console.
|
|
1011
|
+
*/
|
|
1012
|
+
public static function isLoginDeactivated(User $user): bool
|
|
1013
|
+
{
|
|
1014
|
+
return $user->status === 'inactive';
|
|
998
1015
|
}
|
|
999
1016
|
|
|
1000
1017
|
protected function getUuid(array|string $table, array $where, array $options = []): mixed
|
|
@@ -1313,7 +1330,7 @@ class DriverController extends Controller
|
|
|
1313
1330
|
}
|
|
1314
1331
|
|
|
1315
1332
|
$user = static::findDriverUserByIdentity($identity);
|
|
1316
|
-
if (!$user) {
|
|
1333
|
+
if (!$user || static::isLoginDeactivated($user)) {
|
|
1317
1334
|
return response()->json(['status' => 'ok']);
|
|
1318
1335
|
}
|
|
1319
1336
|
|
|
@@ -1344,7 +1361,7 @@ class DriverController extends Controller
|
|
|
1344
1361
|
}
|
|
1345
1362
|
|
|
1346
1363
|
$user = static::findDriverUserByIdentity($identity);
|
|
1347
|
-
if (!$user) {
|
|
1364
|
+
if (!$user || static::isLoginDeactivated($user)) {
|
|
1348
1365
|
return response()->apiError('Invalid or expired reset code.', 422);
|
|
1349
1366
|
}
|
|
1350
1367
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Http\Controllers\Api\v1;
|
|
4
4
|
|
|
5
5
|
use Fleetbase\FleetOps\Http\Controllers\Internal\v1\OrchestrationController as InternalOrchestrationController;
|
|
6
|
+
use Fleetbase\FleetOps\Orchestration\OrchestrationEngineRegistry;
|
|
6
7
|
use Illuminate\Http\JsonResponse;
|
|
7
8
|
use Illuminate\Http\Request;
|
|
8
9
|
|
|
@@ -14,6 +15,14 @@ use Illuminate\Http\Request;
|
|
|
14
15
|
*/
|
|
15
16
|
class OrchestrationController extends InternalOrchestrationController
|
|
16
17
|
{
|
|
18
|
+
public function __construct(OrchestrationEngineRegistry $registry)
|
|
19
|
+
{
|
|
20
|
+
// The fleetbase.api route group authenticates API credentials and establishes
|
|
21
|
+
// their company scope. Console IAM middleware belongs to the internal
|
|
22
|
+
// workbench, not to these organization-credential endpoints.
|
|
23
|
+
$this->registry = $registry;
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
public function run(Request $request): JsonResponse
|
|
18
27
|
{
|
|
19
28
|
return $this->publicResponse(parent::run($request));
|
|
@@ -2,12 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Http\Controllers;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\FleetOps\Traits\AuthorizesMethods;
|
|
5
6
|
use Fleetbase\Http\Controllers\FleetbaseController;
|
|
7
|
+
use Illuminate\Database\Eloquent\Model;
|
|
6
8
|
|
|
7
9
|
class FleetOpsController extends FleetbaseController
|
|
8
10
|
{
|
|
11
|
+
use AuthorizesMethods;
|
|
12
|
+
|
|
9
13
|
/**
|
|
10
14
|
* The package namespace used to resolve from.
|
|
11
15
|
*/
|
|
12
16
|
public string $namespace = '\\Fleetbase\\FleetOps';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Explicit permissions for custom actions, keyed by controller method, e.g.
|
|
20
|
+
* `['bulkDispatch' => 'dispatch order']`. Those methods also carry
|
|
21
|
+
* `#[SkipAuthorizationCheck]` so AuthorizationGuard does not demand the
|
|
22
|
+
* permission it would otherwise infer from the HTTP verb.
|
|
23
|
+
*
|
|
24
|
+
* @var array<string, string|string[]>
|
|
25
|
+
*/
|
|
26
|
+
protected array $methodPermissions = [];
|
|
27
|
+
|
|
28
|
+
public function __construct(?Model $model = null, ?string $resource = null)
|
|
29
|
+
{
|
|
30
|
+
parent::__construct($model, $resource);
|
|
31
|
+
|
|
32
|
+
$this->authorizeMethods($this->methodPermissions);
|
|
33
|
+
}
|
|
13
34
|
}
|
|
@@ -14,6 +14,7 @@ use Fleetbase\FleetOps\Support\Analytics\OperationsPulse;
|
|
|
14
14
|
use Fleetbase\FleetOps\Support\Analytics\OrdersByStatus;
|
|
15
15
|
use Fleetbase\FleetOps\Support\Analytics\RevenueTrend;
|
|
16
16
|
use Fleetbase\FleetOps\Support\Analytics\TopDrivers;
|
|
17
|
+
use Fleetbase\FleetOps\Support\Authorization;
|
|
17
18
|
use Fleetbase\Http\Controllers\Controller;
|
|
18
19
|
use Illuminate\Http\Request;
|
|
19
20
|
|
|
@@ -28,6 +29,16 @@ use Illuminate\Http\Request;
|
|
|
28
29
|
*/
|
|
29
30
|
class AnalyticsController extends Controller
|
|
30
31
|
{
|
|
32
|
+
public function __construct()
|
|
33
|
+
{
|
|
34
|
+
// Not a resource controller, so AuthorizationGuard cannot resolve a permission for it.
|
|
35
|
+
$this->middleware(function ($request, $next) {
|
|
36
|
+
Authorization::authorize('view analytics');
|
|
37
|
+
|
|
38
|
+
return $next($request);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
31
42
|
public function operationsPulse(Request $request)
|
|
32
43
|
{
|
|
33
44
|
return $this->run($request, OperationsPulse::class);
|