@fleetbase/fleetops-engine 0.6.71 → 0.6.72
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 +15 -18
- package/addon/components/admin/tracking-page-settings.hbs +56 -0
- package/addon/components/admin/tracking-page-settings.js +67 -0
- package/addon/components/device/form.hbs +2 -2
- package/addon/components/driver/details.hbs +1 -1
- package/addon/components/driver/form.hbs +1 -1
- package/addon/components/equipment/form.hbs +1 -1
- package/addon/components/layout/fleet-ops-sidebar.js +11 -3
- package/addon/components/maintenance/form.hbs +3 -3
- package/addon/components/map/google-live-map.js +2 -2
- package/addon/components/map/leaflet-live-map.js +45 -32
- package/addon/components/orchestrator/phase-builder.js +2 -1
- package/addon/components/orchestrator-workbench.hbs +7 -1
- package/addon/components/orchestrator-workbench.js +16 -2
- package/addon/components/order/form/route.hbs +1 -1
- package/addon/components/order/form/route.js +13 -1
- package/addon/components/service-rate/details.hbs +1 -1
- package/addon/components/service-rate/form.hbs +1 -1
- package/addon/components/tracking-page/full-view.hbs +276 -0
- package/addon/components/tracking-page/full-view.js +474 -0
- package/addon/components/tracking-page/locale-selector.js +30 -0
- package/addon/components/tracking-page/map.hbs +44 -0
- package/addon/components/tracking-page/map.js +328 -0
- package/addon/components/tracking-page/order-list.hbs +35 -0
- package/addon/components/tracking-page/public-view.hbs +60 -0
- package/addon/components/tracking-page/settings-preview.hbs +21 -0
- package/addon/components/tracking-page/settings-preview.js +23 -0
- package/addon/components/tracking-page/verify-panel.hbs +109 -0
- package/addon/components/tracking-page.hbs +142 -0
- package/addon/components/tracking-page.js +564 -0
- package/addon/components/trailer/form.hbs +2 -2
- package/addon/components/vehicle/form.hbs +3 -3
- package/addon/components/warranty/form.hbs +2 -2
- package/addon/components/widget/live-fleet.js +54 -6
- package/addon/components/widget/proof-of-delivery.hbs +49 -0
- package/addon/components/widget/proof-of-delivery.js +62 -0
- package/addon/components/work-order/form.hbs +3 -3
- package/addon/controllers/operations/orders/index/details.js +19 -3
- package/addon/controllers/operations/scheduler/index.js +45 -14
- package/addon/controllers/settings/index.js +6 -0
- package/addon/controllers/settings/map.js +75 -0
- package/addon/controllers/settings/orchestrator.js +8 -6
- package/addon/controllers/settings/tracking-page.js +212 -0
- package/addon/extension.js +65 -1
- package/addon/helpers/parcel-box-image.js +16 -0
- package/addon/routes/operations/orders/index/new.js +5 -4
- package/addon/routes/operations/scheduler/index.js +1 -1
- package/addon/routes/settings/tracking-page.js +31 -0
- package/addon/routes.js +1 -0
- package/addon/services/location.js +69 -21
- package/addon/services/map-settings.js +28 -0
- package/addon/services/order-allocation.js +1 -1
- package/addon/styles/fleetops-engine.css +1391 -0
- package/addon/templates/settings/map.hbs +46 -0
- package/addon/templates/settings/tracking-page.hbs +318 -0
- package/addon/utils/identity-cell-resource.js +2 -56
- package/addon/utils/map-default-view.js +132 -0
- package/addon/utils/placeholder-images.js +4 -69
- package/addon/utils/resource-descriptors/assets.js +2 -57
- package/addon/utils/resource-descriptors/helpers.js +23 -122
- package/addon/utils/resource-descriptors/operations.js +2 -61
- package/addon/utils/resource-descriptors/people.js +3 -142
- package/addon/utils/resource-descriptors/shared.js +18 -0
- package/addon/utils/tracking-live-source.js +123 -0
- package/addon/utils/tracking-page-theme.js +123 -0
- package/app/components/admin/tracking-page-settings.js +1 -0
- package/app/components/tracking-page/full-view.js +1 -0
- package/app/components/tracking-page/locale-selector.js +1 -0
- package/app/components/tracking-page/map.js +1 -0
- package/app/components/tracking-page/order-list.js +1 -0
- package/app/components/tracking-page/public-view.js +1 -0
- package/app/components/tracking-page/settings-preview.js +1 -0
- package/app/components/tracking-page/verify-panel.js +1 -0
- package/app/components/tracking-page.js +1 -0
- package/app/components/widget/proof-of-delivery.js +1 -0
- package/app/controllers/settings/tracking-page.js +1 -0
- package/app/helpers/parcel-box-image.js +1 -0
- package/app/routes/settings/tracking-page.js +1 -0
- package/app/templates/settings/tracking-page.js +1 -0
- package/app/utils/map-default-view.js +1 -0
- package/app/utils/tracking-live-source.js +1 -0
- package/app/utils/tracking-page-theme.js +1 -0
- package/composer.json +2 -2
- package/extension.json +1 -1
- package/package.json +4 -4
- package/scripts/pest-bootstrap.php +4 -0
- package/server/config/fleetops.php +4 -0
- package/server/migrations/2026_10_08_000001_create_tracking_sessions_tables.php +51 -0
- package/server/migrations/2026_10_10_000001_add_trashed_index_to_device_events.php +40 -0
- package/server/migrations/2026_10_10_000002_add_trashed_index_to_positions.php +40 -0
- package/server/migrations/2026_10_12_000001_add_deleted_at_to_tracking_sessions_tables.php +32 -0
- package/server/resources/views/labels/default.php +2 -2
- package/server/resources/views/labels/entity-label.php +2 -2
- package/server/resources/views/labels/waypoint-label.php +2 -2
- package/server/src/Auth/Schemas/FleetOps.php +5 -0
- package/server/src/Console/Commands/ProcessOperationalAlerts.php +1 -0
- package/server/src/Console/Commands/PruneTelematicsData.php +101 -26
- package/server/src/Events/OrderChanged.php +257 -0
- package/server/src/Events/OrderDriverUnassigned.php +68 -0
- package/server/src/Exceptions/OrderEditLockedException.php +32 -0
- package/server/src/Expansions/CompanyExpansion.php +3 -2
- package/server/src/Expansions/UserExpansion.php +12 -10
- package/server/src/Http/Controllers/Api/v1/CustomerController.php +108 -18
- package/server/src/Http/Controllers/Api/v1/DriverController.php +95 -10
- package/server/src/Http/Controllers/Api/v1/EntityController.php +66 -0
- package/server/src/Http/Controllers/Api/v1/FleetController.php +10 -4
- package/server/src/Http/Controllers/Api/v1/OrderController.php +139 -36
- package/server/src/Http/Controllers/Api/v1/PayloadController.php +76 -8
- package/server/src/Http/Controllers/Api/v1/PlaceController.php +3 -3
- package/server/src/Http/Controllers/Api/v1/TrackingNumberController.php +6 -32
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +5 -0
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +3 -0
- package/server/src/Http/Controllers/Internal/v1/DriverController.php +10 -0
- package/server/src/Http/Controllers/Internal/v1/FleetController.php +16 -2
- package/server/src/Http/Controllers/Internal/v1/InspectionSubmissionController.php +13 -8
- package/server/src/Http/Controllers/Internal/v1/IssueController.php +2 -0
- package/server/src/Http/Controllers/Internal/v1/MaintenanceController.php +4 -2
- package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +93 -11
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +84 -24
- package/server/src/Http/Controllers/Internal/v1/PositionController.php +22 -0
- package/server/src/Http/Controllers/Internal/v1/ProofController.php +55 -4
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +303 -10
- package/server/src/Http/Controllers/Internal/v1/TrailerController.php +1 -0
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +1 -0
- package/server/src/Http/Controllers/Public/TrackingController.php +518 -0
- package/server/src/Http/Filter/ContactFilter.php +1 -1
- package/server/src/Http/Filter/InspectionFormFilter.php +23 -0
- package/server/src/Http/Filter/InspectionSubmissionFilter.php +23 -0
- package/server/src/Http/Filter/MaintenanceFilter.php +23 -0
- package/server/src/Http/Filter/OrderFilter.php +39 -4
- package/server/src/Http/Filter/ProofFilter.php +18 -0
- package/server/src/Http/Filter/RouteFilter.php +18 -0
- package/server/src/Http/Filter/TelematicFilter.php +23 -0
- package/server/src/Http/Filter/WarrantyFilter.php +23 -0
- package/server/src/Http/Requests/CancelOrderRequest.php +2 -1
- package/server/src/Http/Resources/v1/Concerns/ResolvesPublicRelationFields.php +14 -1
- package/server/src/Http/Resources/v1/Index/Customer.php +18 -0
- package/server/src/Http/Resources/v1/Index/Order.php +11 -2
- package/server/src/Http/Resources/v1/PublicOrderTracking.php +153 -0
- package/server/src/Http/Resources/v1/TrackingNumber.php +8 -34
- package/server/src/Http/Resources/v1/Zone.php +31 -23
- package/server/src/Jobs/FinalizeApiOrderCreation.php +3 -1
- package/server/src/Jobs/ProcessAllocationJob.php +16 -6
- package/server/src/Jobs/PublishTrackingUpdate.php +50 -0
- package/server/src/Listeners/HandleGeofenceEntered.php +31 -15
- package/server/src/Listeners/HandleOrderCanceled.php +9 -1
- package/server/src/Listeners/HandleUserRemovedFromCompany.php +2 -1
- package/server/src/Listeners/PublishTrackingUpdates.php +27 -0
- package/server/src/Mail/CustomerCredentialsMail.php +4 -2
- package/server/src/Models/AssetConnection.php +3 -0
- package/server/src/Models/Contact.php +3 -0
- package/server/src/Models/Device.php +28 -0
- package/server/src/Models/DeviceEvent.php +7 -0
- package/server/src/Models/Driver.php +20 -7
- package/server/src/Models/Entity.php +12 -4
- package/server/src/Models/Fleet.php +11 -32
- package/server/src/Models/FleetDriver.php +3 -0
- package/server/src/Models/FleetVehicle.php +3 -0
- package/server/src/Models/FuelReport.php +5 -13
- package/server/src/Models/Issue.php +4 -16
- package/server/src/Models/Order.php +200 -9
- package/server/src/Models/Payload.php +227 -35
- package/server/src/Models/Place.php +7 -1
- package/server/src/Models/ServiceArea.php +7 -0
- package/server/src/Models/ServiceRate.php +7 -17
- package/server/src/Models/TrackingNumber.php +101 -2
- package/server/src/Models/TrackingSession.php +33 -0
- package/server/src/Models/TrackingSessionGrant.php +22 -0
- package/server/src/Models/TrackingStatus.php +6 -0
- package/server/src/Models/Vehicle.php +21 -2
- package/server/src/Models/Vendor.php +7 -0
- package/server/src/Models/Waypoint.php +13 -4
- package/server/src/Notifications/OrderAssigned.php +2 -1
- package/server/src/Notifications/OrderCanceled.php +7 -1
- package/server/src/Notifications/OrderChanged.php +87 -0
- package/server/src/Notifications/OrderCompleted.php +2 -1
- package/server/src/Notifications/OrderDispatchFailed.php +2 -1
- package/server/src/Notifications/OrderDispatched.php +2 -1
- package/server/src/Notifications/OrderFailed.php +2 -1
- package/server/src/Notifications/OrderUnassigned.php +66 -0
- package/server/src/Notifications/WaypointCompleted.php +2 -1
- package/server/src/Observers/CompanyUserObserver.php +8 -7
- package/server/src/Observers/DriverObserver.php +6 -1
- package/server/src/Observers/OrderObserver.php +141 -1
- package/server/src/Observers/TrackingNumberObserver.php +18 -6
- package/server/src/Observers/UserObserver.php +2 -1
- package/server/src/Observers/VehicleObserver.php +8 -3
- package/server/src/Orchestration/Engines/RouteSequencingEngine.php +21 -3
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +38 -8
- package/server/src/Orchestration/OrchestrationEngineRegistry.php +61 -1
- package/server/src/Orchestration/Support/OrchestratorSettings.php +61 -0
- package/server/src/Orchestration/Support/RouteMetrics.php +165 -0
- package/server/src/Providers/EventServiceProvider.php +11 -0
- package/server/src/Providers/FleetOpsServiceProvider.php +13 -0
- package/server/src/Support/ApiCacheThrottle.php +76 -0
- package/server/src/Support/LiveOrderQuery.php +3 -1
- package/server/src/Support/OrderEdit.php +365 -0
- package/server/src/Support/OrderEditPolicy.php +292 -0
- package/server/src/Support/ProfileAccountManager.php +54 -1
- package/server/src/Support/ResolvesOrderServiceStops.php +5 -0
- package/server/src/Support/SocketChannels.php +346 -0
- package/server/src/Support/Telematics/TelematicService.php +7 -2
- package/server/src/Support/Telematics/Telemetry/Inbox.php +8 -1
- package/server/src/Support/TrackingChannel.php +55 -0
- package/server/src/Support/TrackingCode.php +242 -0
- package/server/src/Support/TrackingPage/TrackingAccess.php +356 -0
- package/server/src/Support/TrackingPage/TrackingPageConfig.php +264 -0
- package/server/src/Support/TrackingPage/TrackingRecipient.php +111 -0
- package/server/src/Support/TrackingPage/TrackingResolver.php +201 -0
- package/server/src/Support/TrackingPage/TrackingTarget.php +40 -0
- package/server/src/Support/TrackingPage/TrackingView.php +543 -0
- package/server/src/Support/TrackingPublisher.php +261 -0
- package/server/src/Support/TrackingScope.php +182 -0
- package/server/src/Support/TrackingUpdate.php +402 -0
- package/server/src/Support/Utils.php +15 -3
- package/server/src/Traits/HasTrackingNumber.php +2 -0
- package/server/src/routes.php +39 -7
- package/server/tests/ApiCustomerControllerContractsTest.php +54 -7
- package/server/tests/ApiDriverPasswordContractsTest.php +25 -21
- package/server/tests/ApiEntityControllerContractsTest.php +84 -0
- package/server/tests/ApiOrderControllerContractsTest.php +245 -0
- package/server/tests/ApiPayloadControllerContractsTest.php +121 -0
- package/server/tests/ApiPlaceControllerContractsTest.php +1 -1
- package/server/tests/ApiTrackingNumberControllerContractsTest.php +6 -9
- package/server/tests/CompactResourceSerializationTest.php +23 -59
- package/server/tests/ControllerFilterContractsTest.php +1 -1
- package/server/tests/CustomerContactIdentitySafetyTest.php +1 -1
- package/server/tests/EventContractsTest.php +26 -60
- package/server/tests/ExpansionContractsTest.php +15 -2
- package/server/tests/Feature/Http/Api/CustomerControllerHelperSeamsTest.php +29 -3
- package/server/tests/Feature/Http/Api/DriverControllerAuthFlowsTest.php +23 -0
- package/server/tests/Feature/Http/Api/OrderControllerNearbyFiltersTest.php +16 -16
- package/server/tests/Feature/Http/Api/OrderControllerQueryFiltersTest.php +41 -14
- package/server/tests/Feature/Http/Api/TrackingNumberControllerHelpersTest.php +57 -22
- package/server/tests/Feature/Http/Internal/DriverControllerCreateRecordTest.php +23 -9
- package/server/tests/Feature/Http/Internal/OrchestrationControllerContractsTest.php +171 -1
- package/server/tests/Feature/Http/Internal/OrderControllerContractsTest.php +110 -14
- package/server/tests/Feature/Http/Internal/OrderControllerHelpersTest.php +7 -5
- package/server/tests/Feature/Http/Internal/PositionControllerReplayChannelTest.php +135 -0
- package/server/tests/Feature/Http/Internal/ProofControllerContractsTest.php +3 -3
- package/server/tests/Feature/Http/Internal/ProofControllerRecentTest.php +170 -0
- package/server/tests/Feature/Http/Public/TrackingControllerTest.php +495 -0
- package/server/tests/IndexCustomerPhotoTest.php +151 -0
- package/server/tests/ModelAccessorContractsTest.php +4 -3
- package/server/tests/NotificationAndMailContractsTest.php +5 -8
- package/server/tests/ObserverContractsTest.php +72 -10
- package/server/tests/OrderActivityFlowRegressionTest.php +3 -2
- package/server/tests/OrderFilterExecutionTest.php +167 -2
- package/server/tests/ProcessAllocationJobContractsTest.php +20 -0
- package/server/tests/PublicOrderTrackingResourceTest.php +221 -0
- package/server/tests/QueueJobContractsTest.php +2 -1
- package/server/tests/RequestContractsTest.php +1 -1
- package/server/tests/RouteRegistrationExecutionTest.php +26 -0
- package/server/tests/RouteSequencingEngineTest.php +17 -0
- package/server/tests/SettingControllerContractsTest.php +110 -0
- package/server/tests/Support/TrackingPageDatabase.php +200 -0
- package/server/tests/TenantScopingTest.php +206 -0
- package/server/tests/TrackingPageSettingsTest.php +343 -0
- package/server/tests/Unit/Console/PruneTelematicsDataCommandTest.php +51 -0
- package/server/tests/Unit/Http/Filter/ContactFilterAddressTest.php +61 -0
- package/server/tests/Unit/Http/Resources/IndexOrderResourceTest.php +30 -0
- package/server/tests/Unit/Jobs/FinalizeApiOrderCreationDispatchGuardTest.php +266 -0
- package/server/tests/Unit/Listeners/HandleGeofenceEnteredTest.php +182 -22
- package/server/tests/Unit/Listeners/HandleUserRemovedFromCompanyTest.php +5 -0
- package/server/tests/Unit/Models/DriverAssignVehicleTest.php +81 -0
- package/server/tests/Unit/Models/FleetTest.php +4 -3
- package/server/tests/Unit/Models/OrderCancelReasonTest.php +143 -0
- package/server/tests/Unit/Models/OrderPayloadConfigAndDistanceTest.php +60 -0
- package/server/tests/Unit/Models/OrderPayloadRouteTest.php +4 -2
- package/server/tests/Unit/Models/PayloadTest.php +18 -4
- package/server/tests/Unit/Models/ServiceRatePersistenceAndServicabilityTest.php +2 -2
- package/server/tests/Unit/Models/TrackingNumberTest.php +67 -6
- package/server/tests/Unit/Models/VehicleTest.php +16 -2
- package/server/tests/Unit/Models/WaypointTest.php +4 -3
- package/server/tests/Unit/Notifications/OrderChangeNotificationsTest.php +59 -0
- package/server/tests/Unit/Notifications/TerminalOrderNotificationsTest.php +3 -3
- package/server/tests/Unit/Observers/DriverDeletionCleanupTest.php +158 -0
- package/server/tests/Unit/Observers/FleetOpsCompanyUserAndServiceAreaObserversTest.php +20 -0
- package/server/tests/Unit/Observers/ObserverListenerSeamsTest.php +3 -3
- package/server/tests/Unit/Observers/OrderAssignmentObserverTest.php +100 -0
- package/server/tests/Unit/Observers/OrderDeleteCascadeTest.php +213 -0
- package/server/tests/Unit/Observers/VehicleObserverDeleteTest.php +67 -0
- package/server/tests/Unit/Orchestration/EngineSelectionAndRouteMetricsTest.php +336 -0
- package/server/tests/Unit/Orchestration/Engines/VroomOrchestrationEngineLifecycleTest.php +8 -1
- package/server/tests/Unit/Support/ApiCacheDependenciesTest.php +347 -0
- package/server/tests/Unit/Support/OrderEditPolicyTest.php +156 -0
- package/server/tests/Unit/Support/OrderOperationsTest.php +514 -0
- package/server/tests/Unit/Support/OsrmProofAndIssueFilterTest.php +4 -3
- package/server/tests/Unit/Support/ProfileAccountManagerTest.php +141 -0
- package/server/tests/Unit/Support/QuerySeamsTest.php +5 -1
- package/server/tests/Unit/Support/SocketChannelsTest.php +396 -0
- package/server/tests/Unit/Support/TrackingChannelTest.php +232 -0
- package/server/tests/Unit/Support/TrackingCodeTest.php +186 -0
- package/server/tests/Unit/Support/TrackingPage/TrackingAccessTest.php +200 -0
- package/server/tests/Unit/Support/TrackingPage/TrackingRecipientTest.php +51 -0
- package/server/tests/Unit/Support/TrackingPage/TrackingResolverTest.php +181 -0
- package/server/tests/Unit/Support/TrackingPage/TrackingViewTest.php +479 -0
- package/server/tests/Unit/Support/TrackingPublisherTest.php +425 -0
- package/server/tests/Unit/Support/TrackingUpdateTest.php +363 -0
- package/server/tests/Unit/Support/UtilsAdditionalTest.php +9 -0
- package/server/tests/Unit/Support/UtilsGeoMatrixAndVendorTest.php +25 -0
- package/server/tests/VehicleResourceContractsTest.php +46 -0
- package/server/tests/VroomOrchestrationEngineTest.php +34 -1
- package/server/tests/ZoneGeofenceSettingsResourceTest.php +121 -0
- package/translations/ar-ae.yml +274 -0
- package/translations/bg-bg.yaml +274 -0
- package/translations/en-us.yaml +287 -0
- package/translations/es-pa.yaml +274 -0
- package/translations/fr-fr.yaml +274 -0
- package/translations/mn-mn.yaml +274 -0
- package/translations/pt-br.yaml +274 -0
- package/translations/ru-ru.yaml +276 -2
- package/translations/uk-ua.yaml +275 -1
- package/translations/vi-vn.yaml +274 -0
- package/addon/components/cell/contact-identity.hbs +0 -1
- package/addon/components/cell/customer-identity.hbs +0 -1
- package/addon/components/cell/driver-identity.hbs +0 -1
- package/addon/components/cell/fleet-identity.hbs +0 -1
- package/addon/components/cell/order-identity.hbs +0 -1
- package/addon/components/cell/place-identity.hbs +0 -1
- package/addon/components/cell/vehicle-identity.hbs +0 -1
- package/addon/components/cell/vendor-identity.hbs +0 -1
- package/addon/components/contact/pill.hbs +0 -24
- package/addon/components/contact/summary.hbs +0 -1
- package/addon/components/customer/pill.hbs +0 -24
- package/addon/components/customer/summary.hbs +0 -1
- package/addon/components/driver/pill.hbs +0 -24
- package/addon/components/driver/summary.hbs +0 -1
- package/addon/components/fleet/pill.hbs +0 -24
- package/addon/components/fleet/summary.hbs +0 -1
- package/addon/components/order/pill.hbs +0 -24
- package/addon/components/order/summary.hbs +0 -1
- package/addon/components/place/pill.hbs +0 -24
- package/addon/components/place/summary.hbs +0 -1
- package/addon/components/select-option/contact.hbs +0 -12
- package/addon/components/select-option/customer.hbs +0 -12
- package/addon/components/select-option/driver.hbs +0 -12
- package/addon/components/select-option/fleet.hbs +0 -12
- package/addon/components/select-option/order.hbs +0 -12
- package/addon/components/select-option/place.hbs +0 -12
- package/addon/components/select-option/vehicle.hbs +0 -12
- package/addon/components/select-option/vendor.hbs +0 -12
- package/addon/components/vehicle/pill.hbs +0 -24
- package/addon/components/vehicle/summary.hbs +0 -1
- package/addon/components/vendor/pill.hbs +0 -24
- package/addon/components/vendor/summary.hbs +0 -1
- package/app/components/cell/contact-identity.js +0 -1
- package/app/components/cell/customer-identity.js +0 -1
- package/app/components/cell/driver-identity.js +0 -1
- package/app/components/cell/fleet-identity.js +0 -1
- package/app/components/cell/order-identity.js +0 -1
- package/app/components/cell/place-identity.js +0 -1
- package/app/components/cell/vehicle-identity.js +0 -1
- package/app/components/cell/vendor-identity.js +0 -1
- package/app/components/contact/pill.js +0 -1
- package/app/components/contact/summary.js +0 -1
- package/app/components/customer/pill.js +0 -1
- package/app/components/customer/summary.js +0 -1
- package/app/components/driver/pill.js +0 -1
- package/app/components/driver/summary.js +0 -1
- package/app/components/fleet/pill.js +0 -1
- package/app/components/fleet/summary.js +0 -1
- package/app/components/order/pill.js +0 -1
- package/app/components/order/summary.js +0 -1
- package/app/components/place/pill.js +0 -1
- package/app/components/place/summary.js +0 -1
- package/app/components/select-option/contact.js +0 -1
- package/app/components/select-option/customer.js +0 -1
- package/app/components/select-option/driver.js +0 -1
- package/app/components/select-option/fleet.js +0 -1
- package/app/components/select-option/order.js +0 -1
- package/app/components/select-option/place.js +0 -1
- package/app/components/select-option/vehicle.js +0 -1
- package/app/components/select-option/vendor.js +0 -1
- package/app/components/vehicle/pill.js +0 -1
- package/app/components/vehicle/summary.js +0 -1
- package/app/components/vendor/pill.js +0 -1
- package/app/components/vendor/summary.js +0 -1
|
@@ -5,6 +5,8 @@ namespace Fleetbase\FleetOps\Expansions;
|
|
|
5
5
|
use Fleetbase\Build\Expansion;
|
|
6
6
|
use Fleetbase\FleetOps\Models\Contact;
|
|
7
7
|
use Fleetbase\FleetOps\Models\Driver;
|
|
8
|
+
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
9
|
+
use Illuminate\Database\Eloquent\Relations\HasOne;
|
|
8
10
|
|
|
9
11
|
class UserExpansion implements Expansion
|
|
10
12
|
{
|
|
@@ -19,55 +21,55 @@ class UserExpansion implements Expansion
|
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
|
-
* @return
|
|
24
|
+
* @return HasOne
|
|
23
25
|
*/
|
|
24
26
|
public static function driver()
|
|
25
27
|
{
|
|
26
|
-
return function () {
|
|
28
|
+
return function (): HasOne {
|
|
27
29
|
/** @var \Illuminate\Database\Eloquent\Model $this */
|
|
28
30
|
return $this->hasOne(Driver::class)->without('user');
|
|
29
31
|
};
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
|
-
* @return
|
|
35
|
+
* @return HasOne
|
|
34
36
|
*/
|
|
35
37
|
public static function currentDriverSession()
|
|
36
38
|
{
|
|
37
|
-
return function () {
|
|
39
|
+
return function (): HasOne {
|
|
38
40
|
/** @var \Illuminate\Database\Eloquent\Model $this */
|
|
39
41
|
return $this->driver()->where('company_uuid', session('company'));
|
|
40
42
|
};
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
/**
|
|
44
|
-
* @return
|
|
46
|
+
* @return HasMany
|
|
45
47
|
*/
|
|
46
48
|
public static function driverProfiles()
|
|
47
49
|
{
|
|
48
|
-
return function () {
|
|
50
|
+
return function (): HasMany {
|
|
49
51
|
/** @var \Illuminate\Database\Eloquent\Model $this */
|
|
50
52
|
return $this->hasMany(Driver::class);
|
|
51
53
|
};
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
/**
|
|
55
|
-
* @return
|
|
57
|
+
* @return HasOne
|
|
56
58
|
*/
|
|
57
59
|
public static function customer()
|
|
58
60
|
{
|
|
59
|
-
return function () {
|
|
61
|
+
return function (): HasOne {
|
|
60
62
|
/** @var \Illuminate\Database\Eloquent\Model $this */
|
|
61
63
|
return $this->hasOne(Contact::class, 'user_uuid', 'uuid')->where('type', 'customer')->without('user');
|
|
62
64
|
};
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
/**
|
|
66
|
-
* @return
|
|
68
|
+
* @return HasOne
|
|
67
69
|
*/
|
|
68
70
|
public static function contact()
|
|
69
71
|
{
|
|
70
|
-
return function () {
|
|
72
|
+
return function (): HasOne {
|
|
71
73
|
/** @var \Illuminate\Database\Eloquent\Model $this */
|
|
72
74
|
return $this->hasOne(Contact::class, 'user_uuid', 'uuid')->without('user');
|
|
73
75
|
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Http\Controllers\Api\v1;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\Exceptions\SmsBlockedException;
|
|
5
6
|
use Fleetbase\FleetOps\Exceptions\UserAlreadyExistsException;
|
|
6
7
|
use Fleetbase\FleetOps\Http\Requests\CreateCustomerOrderRequest;
|
|
7
8
|
use Fleetbase\FleetOps\Http\Requests\CreateCustomerRequest;
|
|
@@ -28,6 +29,7 @@ use Illuminate\Http\JsonResponse;
|
|
|
28
29
|
use Illuminate\Http\Request;
|
|
29
30
|
use Illuminate\Support\Facades\Hash;
|
|
30
31
|
use Illuminate\Support\Str;
|
|
32
|
+
use Symfony\Component\HttpFoundation\Response;
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
35
|
* Customer-facing Fleet-Ops API.
|
|
@@ -46,6 +48,11 @@ class CustomerController extends Controller
|
|
|
46
48
|
|
|
47
49
|
public const DEACTIVATED_LOGIN_MESSAGE = 'This customer login has been deactivated.';
|
|
48
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Wrong guesses a single code tolerates before it locks itself (core's check()).
|
|
53
|
+
*/
|
|
54
|
+
public const CODE_CHECK_ATTEMPTS = 5;
|
|
55
|
+
|
|
49
56
|
/* ============================================================
|
|
50
57
|
| Public auth flows (API credential only, no Customer-Token)
|
|
51
58
|
* ============================================================ */
|
|
@@ -80,23 +87,21 @@ class CustomerController extends Controller
|
|
|
80
87
|
$providedName = trim((string) $request->input('name', ''));
|
|
81
88
|
$providedPhone = $request->filled('phone') ? static::phone($request->input('phone')) : null;
|
|
82
89
|
|
|
83
|
-
// The
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
90
|
+
// The code's subject is the person signing up: an existing user (a prior
|
|
91
|
+
// incomplete signup, or an account in another organization) as it stands, else
|
|
92
|
+
// an unsaved User carrying the name and contact details the message needs.
|
|
93
|
+
// Nothing is persisted until create() verifies the code — a users row per
|
|
94
|
+
// unverified identity was a way to fill the table from a leaked credential.
|
|
88
95
|
$subject = $this->findActiveUserByIdentity($identity, $isEmail ? 'email' : 'phone');
|
|
89
96
|
|
|
90
97
|
if (!$subject) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
$subject = $this->createUser([
|
|
98
|
+
$subject = new User();
|
|
99
|
+
$subject->forceFill([
|
|
94
100
|
'company_uuid' => $sessionCompany,
|
|
95
101
|
'name' => $providedName !== '' ? $providedName : $identity,
|
|
96
102
|
'email' => $isEmail ? $identity : null,
|
|
97
103
|
'phone' => $isEmail ? $providedPhone : $identity,
|
|
98
104
|
]);
|
|
99
|
-
$subject->setUserType('customer');
|
|
100
105
|
} elseif ($providedName !== '' && (!$subject->name || $subject->name === $subject->email)) {
|
|
101
106
|
// Existing stub user from a prior incomplete signup — refresh the
|
|
102
107
|
// greeting name if the client supplied one.
|
|
@@ -118,10 +123,13 @@ class CustomerController extends Controller
|
|
|
118
123
|
]);
|
|
119
124
|
} else {
|
|
120
125
|
$this->generateSmsVerification($subject, 'fleetops_create_customer', [
|
|
126
|
+
'hashed' => true,
|
|
121
127
|
'messageCallback' => fn ($verification) => 'Your ' . config('app.name') . ' verification code is ' . $verification->code,
|
|
122
128
|
'meta' => $meta,
|
|
123
129
|
]);
|
|
124
130
|
}
|
|
131
|
+
} catch (SmsBlockedException $e) {
|
|
132
|
+
return static::smsBlockedResponse($e);
|
|
125
133
|
} catch (\Twilio\Exceptions\RestException $e) {
|
|
126
134
|
return response()->apiError($e->getMessage());
|
|
127
135
|
} catch (\Exception $e) {
|
|
@@ -391,13 +399,21 @@ class CustomerController extends Controller
|
|
|
391
399
|
return response()->apiError(static::DEACTIVATED_LOGIN_MESSAGE, 403);
|
|
392
400
|
}
|
|
393
401
|
|
|
402
|
+
// A guard refusal (paused, over budget, unusable number) falls back to email like
|
|
403
|
+
// a provider failure does; with no email to fall back to, the refusal itself is the
|
|
404
|
+
// answer, with its status, not the generic error.
|
|
405
|
+
$smsFailure = null;
|
|
406
|
+
|
|
394
407
|
try {
|
|
395
408
|
$this->generateSmsVerification($user, 'fleetops_customer_login', [
|
|
409
|
+
'hashed' => true,
|
|
396
410
|
'messageCallback' => fn ($verification) => 'Your ' . config('app.name') . ' verification code is ' . $verification->code,
|
|
397
411
|
]);
|
|
398
412
|
|
|
399
413
|
return response()->json(['status' => 'ok', 'method' => 'sms']);
|
|
400
414
|
} catch (\Throwable $e) {
|
|
415
|
+
$smsFailure = $e;
|
|
416
|
+
|
|
401
417
|
if ($user->email) {
|
|
402
418
|
try {
|
|
403
419
|
$this->generateEmailVerification($user, 'fleetops_customer_login', [
|
|
@@ -407,12 +423,12 @@ class CustomerController extends Controller
|
|
|
407
423
|
|
|
408
424
|
return response()->json(['status' => 'ok', 'method' => 'email']);
|
|
409
425
|
} catch (\Throwable $inner) {
|
|
410
|
-
return
|
|
426
|
+
return static::sendFailureResponse($smsFailure, 'Unable to send verification code.');
|
|
411
427
|
}
|
|
412
428
|
}
|
|
413
429
|
}
|
|
414
430
|
|
|
415
|
-
return
|
|
431
|
+
return static::sendFailureResponse($smsFailure, 'Unable to send verification code.');
|
|
416
432
|
}
|
|
417
433
|
|
|
418
434
|
/**
|
|
@@ -494,10 +510,13 @@ class CustomerController extends Controller
|
|
|
494
510
|
]);
|
|
495
511
|
} else {
|
|
496
512
|
$this->generateSmsVerification($user, 'fleetops_customer_password_reset', [
|
|
513
|
+
'hashed' => true,
|
|
497
514
|
'messageCallback' => fn ($v) => 'Your ' . config('app.name') . ' password reset code is ' . $v->code,
|
|
498
515
|
'meta' => $meta,
|
|
499
516
|
]);
|
|
500
517
|
}
|
|
518
|
+
} catch (SmsBlockedException $e) {
|
|
519
|
+
return static::smsBlockedResponse($e);
|
|
501
520
|
} catch (\Throwable $e) {
|
|
502
521
|
return response()->apiError(app()->hasDebugModeEnabled() ? $e->getMessage() : 'Unable to send reset code.');
|
|
503
522
|
}
|
|
@@ -542,12 +561,9 @@ class CustomerController extends Controller
|
|
|
542
561
|
// setPasswordAttribute mutator hashes plaintext; don't pre-hash here.
|
|
543
562
|
$user->password = $password;
|
|
544
563
|
$user->save();
|
|
545
|
-
// Invalidate all existing sessions for this user after a password reset.
|
|
564
|
+
// Invalidate all existing sessions for this user after a password reset. The
|
|
565
|
+
// code itself was used up when it verified (findVerificationCode).
|
|
546
566
|
$this->deleteUserTokens($user);
|
|
547
|
-
// Null on the testing-bypass path — there is no row to consume.
|
|
548
|
-
if ($verificationCode) {
|
|
549
|
-
$verificationCode->delete();
|
|
550
|
-
}
|
|
551
567
|
|
|
552
568
|
return response()->json(['status' => 'ok']);
|
|
553
569
|
}
|
|
@@ -948,6 +964,36 @@ class CustomerController extends Controller
|
|
|
948
964
|
return VerificationCode::generateSmsVerificationFor($user, $for, $options);
|
|
949
965
|
}
|
|
950
966
|
|
|
967
|
+
/**
|
|
968
|
+
* Answer a refusal from the SMS guard with the status it carries: 422 for a number that
|
|
969
|
+
* can never be texted, 429 with Retry-After for a cooldown or budget, 423 while texting
|
|
970
|
+
* is paused. The sends here sit inside catch-alls so a provider outage becomes an email
|
|
971
|
+
* fallback or a generic error; a refusal must not be flattened into that generic error,
|
|
972
|
+
* or an app can neither explain it to the person nor back off.
|
|
973
|
+
*/
|
|
974
|
+
protected static function smsBlockedResponse(SmsBlockedException $e)
|
|
975
|
+
{
|
|
976
|
+
$response = response()->apiError($e->getMessage(), $e->status());
|
|
977
|
+
if ($response instanceof Response) {
|
|
978
|
+
$response->headers->add($e->headers());
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
return $response;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* The response when a code could not be sent: the guard's refusal when that is why,
|
|
986
|
+
* otherwise the flow's own generic message.
|
|
987
|
+
*/
|
|
988
|
+
protected static function sendFailureResponse(?\Throwable $failure, string $message)
|
|
989
|
+
{
|
|
990
|
+
if ($failure instanceof SmsBlockedException) {
|
|
991
|
+
return static::smsBlockedResponse($failure);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
return response()->apiError($message);
|
|
995
|
+
}
|
|
996
|
+
|
|
951
997
|
/**
|
|
952
998
|
* Whether the supplied code matches the configured testing bypass code.
|
|
953
999
|
*
|
|
@@ -978,14 +1024,58 @@ class CustomerController extends Controller
|
|
|
978
1024
|
);
|
|
979
1025
|
}
|
|
980
1026
|
|
|
1027
|
+
/**
|
|
1028
|
+
* Whether the submitted code verifies against a live code for the purpose and
|
|
1029
|
+
* subject or identity in `$attributes`, using the code up when it does.
|
|
1030
|
+
*/
|
|
981
1031
|
protected function verificationCodeExists(array $attributes): bool
|
|
982
1032
|
{
|
|
983
|
-
return
|
|
1033
|
+
return $this->findVerificationCode($attributes) !== null;
|
|
984
1034
|
}
|
|
985
1035
|
|
|
1036
|
+
/**
|
|
1037
|
+
* The live code the submitted code verified against, used up so it cannot verify
|
|
1038
|
+
* again, or null. `$attributes` carries `code`, `for` and either `subject_uuid` or
|
|
1039
|
+
* `meta->identity`. Live codes are checked newest first; a wrong guess counts against
|
|
1040
|
+
* each until it locks (core's check()). Plaintext rows written before codes were hashed
|
|
1041
|
+
* verify the same way.
|
|
1042
|
+
*/
|
|
986
1043
|
protected function findVerificationCode(array $attributes): mixed
|
|
987
1044
|
{
|
|
988
|
-
|
|
1045
|
+
$code = (string) ($attributes['code'] ?? '');
|
|
1046
|
+
|
|
1047
|
+
foreach ($this->liveVerificationCodes($attributes) as $verificationCode) {
|
|
1048
|
+
if ($verificationCode->check($code, static::CODE_CHECK_ATTEMPTS) === VerificationCode::CHECK_VALID) {
|
|
1049
|
+
$verificationCode->consume();
|
|
1050
|
+
|
|
1051
|
+
return $verificationCode;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
return null;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
/**
|
|
1059
|
+
* Unexpired, unused, unlocked codes for the purpose and subject or identity in
|
|
1060
|
+
* `$attributes`, newest first.
|
|
1061
|
+
*
|
|
1062
|
+
* @return \Illuminate\Support\Collection<int, VerificationCode>
|
|
1063
|
+
*/
|
|
1064
|
+
protected function liveVerificationCodes(array $attributes)
|
|
1065
|
+
{
|
|
1066
|
+
$query = VerificationCode::query()
|
|
1067
|
+
->where('for', (string) ($attributes['for'] ?? ''))
|
|
1068
|
+
->where(function ($query) {
|
|
1069
|
+
$query->whereNull('status')->orWhereNotIn('status', [VerificationCode::STATUS_USED, VerificationCode::STATUS_LOCKED]);
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
foreach (['subject_uuid', 'meta->identity'] as $column) {
|
|
1073
|
+
if (array_key_exists($column, $attributes)) {
|
|
1074
|
+
$query->where($column, $attributes[$column]);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
return $query->orderByDesc('created_at')->get();
|
|
989
1079
|
}
|
|
990
1080
|
|
|
991
1081
|
protected function findFileByPublicId(string $publicId): mixed
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Http\Controllers\Api\v1;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\Exceptions\SmsBlockedException;
|
|
5
6
|
use Fleetbase\FleetOps\Events\DriverLocationChanged;
|
|
6
7
|
use Fleetbase\FleetOps\Events\GeofenceEntered;
|
|
7
8
|
use Fleetbase\FleetOps\Events\GeofenceExited;
|
|
@@ -21,9 +22,11 @@ use Fleetbase\FleetOps\Models\Driver;
|
|
|
21
22
|
use Fleetbase\FleetOps\Models\Order;
|
|
22
23
|
use Fleetbase\FleetOps\Models\Vehicle;
|
|
23
24
|
use Fleetbase\FleetOps\Models\Vendor;
|
|
25
|
+
use Fleetbase\FleetOps\Support\ApiCacheThrottle;
|
|
24
26
|
use Fleetbase\FleetOps\Support\GeofenceIntersectionService;
|
|
25
27
|
use Fleetbase\FleetOps\Support\OSRM;
|
|
26
28
|
use Fleetbase\FleetOps\Support\ProfileAccountManager;
|
|
29
|
+
use Fleetbase\FleetOps\Support\TrackingPublisher;
|
|
27
30
|
use Fleetbase\FleetOps\Support\Utils;
|
|
28
31
|
use Fleetbase\Http\Controllers\Controller;
|
|
29
32
|
use Fleetbase\Http\Requests\SwitchOrganizationRequest;
|
|
@@ -42,6 +45,7 @@ use Illuminate\Support\Carbon;
|
|
|
42
45
|
use Illuminate\Support\Facades\DB;
|
|
43
46
|
use Illuminate\Support\Facades\Hash;
|
|
44
47
|
use Illuminate\Support\Str;
|
|
48
|
+
use Symfony\Component\HttpFoundation\Response;
|
|
45
49
|
|
|
46
50
|
class DriverController extends Controller
|
|
47
51
|
{
|
|
@@ -54,6 +58,11 @@ class DriverController extends Controller
|
|
|
54
58
|
*/
|
|
55
59
|
public const DEACTIVATED_LOGIN_MESSAGE = 'This driver login has been deactivated.';
|
|
56
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Wrong guesses a single code tolerates before it locks itself (core's check()).
|
|
63
|
+
*/
|
|
64
|
+
public const CODE_CHECK_ATTEMPTS = 5;
|
|
65
|
+
|
|
57
66
|
/**
|
|
58
67
|
* Public expansion name => Eloquent relation name.
|
|
59
68
|
*
|
|
@@ -373,7 +382,14 @@ class DriverController extends Controller
|
|
|
373
382
|
}
|
|
374
383
|
}
|
|
375
384
|
|
|
385
|
+
// Location writes are quiet and frequent: invalidate cached lists at most once a window
|
|
386
|
+
ApiCacheThrottle::invalidate($driver, $driver->company_uuid);
|
|
387
|
+
if ($vehicle) {
|
|
388
|
+
ApiCacheThrottle::invalidate($vehicle, $vehicle->company_uuid);
|
|
389
|
+
}
|
|
390
|
+
|
|
376
391
|
broadcast(new DriverLocationChanged($driver));
|
|
392
|
+
app(TrackingPublisher::class)->driverMoved($driver);
|
|
377
393
|
|
|
378
394
|
// ----------------------------------------------------------------
|
|
379
395
|
// Geofence intersection detection
|
|
@@ -431,11 +447,13 @@ class DriverController extends Controller
|
|
|
431
447
|
|
|
432
448
|
// Perform a single update call
|
|
433
449
|
$driver->updateQuietly(['online' => $onlineValue]);
|
|
450
|
+
$driver->invalidateApiCache();
|
|
434
451
|
|
|
435
452
|
// Update vehicle online too
|
|
436
453
|
$driver->loadMissing('vehicle');
|
|
437
454
|
if ($vehicle = $driver->vehicle) {
|
|
438
455
|
$vehicle->updateQuietly(['online' => $onlineValue]);
|
|
456
|
+
$vehicle->invalidateApiCache();
|
|
439
457
|
}
|
|
440
458
|
|
|
441
459
|
// Return the updated resource
|
|
@@ -574,9 +592,15 @@ class DriverController extends Controller
|
|
|
574
592
|
// Get the user's company for this driver profile
|
|
575
593
|
$company = static::getDriverCompanyFromUser($user);
|
|
576
594
|
|
|
595
|
+
// A guard refusal (paused, over budget, unusable number) falls back to email like
|
|
596
|
+
// a provider failure does; with no email to fall back to, the refusal itself is the
|
|
597
|
+
// answer, with its status, not the generic error.
|
|
598
|
+
$smsFailure = null;
|
|
599
|
+
|
|
577
600
|
// generate verification token
|
|
578
601
|
try {
|
|
579
602
|
VerificationCode::generateSmsVerificationFor($user, 'driver_login', [
|
|
603
|
+
'hashed' => true,
|
|
580
604
|
'company_uuid' => $company->uuid,
|
|
581
605
|
'messageCallback' => function ($verification) use ($company) {
|
|
582
606
|
return 'Your ' . data_get($company, 'name', config('app.name')) . ' verification code is ' . $verification->code;
|
|
@@ -585,6 +609,8 @@ class DriverController extends Controller
|
|
|
585
609
|
|
|
586
610
|
return response()->json(['status' => 'OK', 'method' => 'sms']);
|
|
587
611
|
} catch (\Throwable $e) {
|
|
612
|
+
$smsFailure = $e;
|
|
613
|
+
|
|
588
614
|
if (app()->bound('sentry')) {
|
|
589
615
|
app('sentry')->captureException($e);
|
|
590
616
|
}
|
|
@@ -608,7 +634,7 @@ class DriverController extends Controller
|
|
|
608
634
|
}
|
|
609
635
|
}
|
|
610
636
|
|
|
611
|
-
return
|
|
637
|
+
return static::sendFailureResponse($smsFailure, 'Unable to send SMS Verification code.');
|
|
612
638
|
}
|
|
613
639
|
|
|
614
640
|
/**
|
|
@@ -641,8 +667,8 @@ class DriverController extends Controller
|
|
|
641
667
|
return response()->apiError(static::DEACTIVATED_LOGIN_MESSAGE, 403);
|
|
642
668
|
}
|
|
643
669
|
|
|
644
|
-
// find and verify code
|
|
645
|
-
$verificationCode =
|
|
670
|
+
// find and verify code: the user's newest live code for this purpose, used up
|
|
671
|
+
$verificationCode = static::spendCode($user, $for, (string) $code);
|
|
646
672
|
if (!$verificationCode && !static::verificationBypassMatches($identity, $code)) {
|
|
647
673
|
return response()->apiError('Invalid verification code!');
|
|
648
674
|
}
|
|
@@ -1336,6 +1362,8 @@ class DriverController extends Controller
|
|
|
1336
1362
|
|
|
1337
1363
|
try {
|
|
1338
1364
|
static::sendResetCode($user, $identity);
|
|
1365
|
+
} catch (SmsBlockedException $e) {
|
|
1366
|
+
return static::smsBlockedResponse($e);
|
|
1339
1367
|
} catch (\Throwable $e) {
|
|
1340
1368
|
return response()->apiError(static::debugEnabled() ? $e->getMessage() : 'Unable to send reset code.');
|
|
1341
1369
|
}
|
|
@@ -1376,7 +1404,7 @@ class DriverController extends Controller
|
|
|
1376
1404
|
$user->password = $password;
|
|
1377
1405
|
$user->save();
|
|
1378
1406
|
|
|
1379
|
-
|
|
1407
|
+
// The code was used up when it verified (findResetCode); every session ends.
|
|
1380
1408
|
$user->tokens()->delete();
|
|
1381
1409
|
|
|
1382
1410
|
return response()->json(['status' => 'ok']);
|
|
@@ -1411,14 +1439,40 @@ class DriverController extends Controller
|
|
|
1411
1439
|
return Hash::check($plain, $user->password);
|
|
1412
1440
|
}
|
|
1413
1441
|
|
|
1414
|
-
/**
|
|
1442
|
+
/**
|
|
1443
|
+
* The live reset code the supplied one verified against, used up so it cannot
|
|
1444
|
+
* verify again, or null.
|
|
1445
|
+
*/
|
|
1415
1446
|
protected static function findResetCode(User $user, string $code)
|
|
1416
1447
|
{
|
|
1417
|
-
return
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1448
|
+
return static::spendCode($user, 'driver_password_reset', $code);
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* Verify a code against the user's live codes for a purpose and use it up. Codes are
|
|
1453
|
+
* checked newest first; a wrong guess counts against each until it locks (core's
|
|
1454
|
+
* check()). Plaintext rows written before codes were hashed verify the same way.
|
|
1455
|
+
*/
|
|
1456
|
+
protected static function spendCode(User $user, string $for, string $code): ?VerificationCode
|
|
1457
|
+
{
|
|
1458
|
+
$live = VerificationCode::query()
|
|
1459
|
+
->where('for', $for)
|
|
1460
|
+
->where('subject_uuid', $user->uuid)
|
|
1461
|
+
->where(function ($query) {
|
|
1462
|
+
$query->whereNull('status')->orWhereNotIn('status', [VerificationCode::STATUS_USED, VerificationCode::STATUS_LOCKED]);
|
|
1463
|
+
})
|
|
1464
|
+
->orderByDesc('created_at')
|
|
1465
|
+
->get();
|
|
1466
|
+
|
|
1467
|
+
foreach ($live as $verificationCode) {
|
|
1468
|
+
if ($verificationCode->check($code, static::CODE_CHECK_ATTEMPTS) === VerificationCode::CHECK_VALID) {
|
|
1469
|
+
$verificationCode->consume();
|
|
1470
|
+
|
|
1471
|
+
return $verificationCode;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
return null;
|
|
1422
1476
|
}
|
|
1423
1477
|
|
|
1424
1478
|
/** Sends the reset code by whichever channel the identity names. */
|
|
@@ -1433,11 +1487,42 @@ class DriverController extends Controller
|
|
|
1433
1487
|
]);
|
|
1434
1488
|
} else {
|
|
1435
1489
|
VerificationCode::generateSmsVerificationFor($user, 'driver_password_reset', [
|
|
1490
|
+
'hashed' => true,
|
|
1436
1491
|
'messageCallback' => fn ($v) => 'Your ' . config('app.name') . ' password reset code is ' . $v->code,
|
|
1437
1492
|
]);
|
|
1438
1493
|
}
|
|
1439
1494
|
}
|
|
1440
1495
|
|
|
1496
|
+
/**
|
|
1497
|
+
* Answer a refusal from the SMS guard with the status it carries: 422 for a number that
|
|
1498
|
+
* can never be texted, 429 with Retry-After for a cooldown or budget, 423 while texting
|
|
1499
|
+
* is paused. The sends here sit inside catch-alls so a provider outage becomes an email
|
|
1500
|
+
* fallback or a generic error; a refusal must not be flattened into that generic error,
|
|
1501
|
+
* or an app can neither explain it to the person nor back off.
|
|
1502
|
+
*/
|
|
1503
|
+
protected static function smsBlockedResponse(SmsBlockedException $e)
|
|
1504
|
+
{
|
|
1505
|
+
$response = response()->apiError($e->getMessage(), $e->status());
|
|
1506
|
+
if ($response instanceof Response) {
|
|
1507
|
+
$response->headers->add($e->headers());
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
return $response;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
/**
|
|
1514
|
+
* The response when a code could not be sent: the guard's refusal when that is why,
|
|
1515
|
+
* otherwise the flow's own generic message.
|
|
1516
|
+
*/
|
|
1517
|
+
protected static function sendFailureResponse(?\Throwable $failure, string $message)
|
|
1518
|
+
{
|
|
1519
|
+
if ($failure instanceof SmsBlockedException) {
|
|
1520
|
+
return static::smsBlockedResponse($failure);
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
return response()->apiError($message);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1441
1526
|
/** Resolve a driver's user account from an email address or a phone number. */
|
|
1442
1527
|
protected static function findDriverUserByIdentity(string $identity): ?User
|
|
1443
1528
|
{
|
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Http\Controllers\Api\v1;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\FleetOps\Exceptions\OrderEditLockedException;
|
|
5
6
|
use Fleetbase\FleetOps\Http\Requests\CreateEntityRequest;
|
|
6
7
|
use Fleetbase\FleetOps\Http\Requests\UpdateEntityRequest;
|
|
7
8
|
use Fleetbase\FleetOps\Http\Resources\v1\DeletedResource;
|
|
8
9
|
use Fleetbase\FleetOps\Http\Resources\v1\Entity as EntityResource;
|
|
9
10
|
use Fleetbase\FleetOps\Models\Entity;
|
|
11
|
+
use Fleetbase\FleetOps\Models\Order;
|
|
10
12
|
use Fleetbase\FleetOps\Models\Payload;
|
|
11
13
|
use Fleetbase\FleetOps\Models\Place;
|
|
14
|
+
use Fleetbase\FleetOps\Support\OrderEdit;
|
|
15
|
+
use Fleetbase\FleetOps\Support\OrderEditPolicy;
|
|
12
16
|
use Fleetbase\FleetOps\Support\Utils;
|
|
13
17
|
use Fleetbase\Http\Controllers\Controller;
|
|
14
18
|
use Illuminate\Http\Request;
|
|
@@ -79,8 +83,15 @@ class EntityController extends Controller
|
|
|
79
83
|
// make sure company is set
|
|
80
84
|
$input['company_uuid'] = session('company');
|
|
81
85
|
|
|
86
|
+
// adding an item to an order is refused once the order's status locks its items
|
|
87
|
+
$edit = $this->orderEditForPayload($input['payload_uuid'] ?? null);
|
|
88
|
+
if ($locked = $this->guardItemsEdit($edit, true)) {
|
|
89
|
+
return $locked;
|
|
90
|
+
}
|
|
91
|
+
|
|
82
92
|
// create the entity
|
|
83
93
|
$entity = $this->createEntity($input);
|
|
94
|
+
$this->commitItemsEdit($edit);
|
|
84
95
|
|
|
85
96
|
// response the driver resource
|
|
86
97
|
return $this->entityResource($entity);
|
|
@@ -159,8 +170,15 @@ class EntityController extends Controller
|
|
|
159
170
|
}
|
|
160
171
|
}
|
|
161
172
|
|
|
173
|
+
// changing an order's item is refused once the order's status locks its items
|
|
174
|
+
$edit = $this->orderEditForPayload($entity->payload_uuid);
|
|
175
|
+
if ($locked = $this->guardItemsEdit($edit, true)) {
|
|
176
|
+
return $locked;
|
|
177
|
+
}
|
|
178
|
+
|
|
162
179
|
// update the entity
|
|
163
180
|
$entity->update($input);
|
|
181
|
+
$this->commitItemsEdit($edit);
|
|
164
182
|
|
|
165
183
|
// response the entity resource
|
|
166
184
|
return $this->entityResource($entity);
|
|
@@ -220,13 +238,61 @@ class EntityController extends Controller
|
|
|
220
238
|
);
|
|
221
239
|
}
|
|
222
240
|
|
|
241
|
+
// removing an order's item is refused once the order's status locks its items
|
|
242
|
+
$edit = $this->orderEditForPayload($entity->payload_uuid);
|
|
243
|
+
if ($locked = $this->guardItemsEdit($edit, false)) {
|
|
244
|
+
return $locked;
|
|
245
|
+
}
|
|
246
|
+
|
|
223
247
|
// delete the entity
|
|
224
248
|
$entity->delete();
|
|
249
|
+
$this->commitItemsEdit($edit);
|
|
225
250
|
|
|
226
251
|
// response the entity resource
|
|
227
252
|
return $this->deletedEntityResource($entity);
|
|
228
253
|
}
|
|
229
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Begin an edit of the order the payload belongs to, if it belongs to one.
|
|
257
|
+
*/
|
|
258
|
+
protected function orderEditForPayload(?string $payloadUuid): ?OrderEdit
|
|
259
|
+
{
|
|
260
|
+
if (!$payloadUuid) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
$order = Order::where('payload_uuid', $payloadUuid)->first();
|
|
266
|
+
} catch (\Throwable) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return $order ? OrderEdit::begin($order) : null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* A 422 response when the order's status locks the item change, otherwise null.
|
|
275
|
+
*/
|
|
276
|
+
protected function guardItemsEdit(?OrderEdit $edit, bool $adds)
|
|
277
|
+
{
|
|
278
|
+
if (!$edit) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
$edit->guard([OrderEditPolicy::FIELD_ITEMS => ['adds' => $adds]]);
|
|
284
|
+
} catch (OrderEditLockedException $exception) {
|
|
285
|
+
return response()->apiError($exception->getMessage(), 422, $exception->toResponseData());
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
protected function commitItemsEdit(?OrderEdit $edit): void
|
|
292
|
+
{
|
|
293
|
+
$edit?->commit();
|
|
294
|
+
}
|
|
295
|
+
|
|
230
296
|
protected function entityInputFromRequest(Request $request): array
|
|
231
297
|
{
|
|
232
298
|
return $request->only([
|
|
@@ -434,10 +434,13 @@ class FleetController extends Controller
|
|
|
434
434
|
|
|
435
435
|
protected function removeVehicleFromFleet(Fleet $fleet, Vehicle $vehicle): void
|
|
436
436
|
{
|
|
437
|
-
|
|
437
|
+
// Deleted without model events; fleet_vehicles rows carry no company
|
|
438
|
+
if (FleetVehicle::where([
|
|
438
439
|
'fleet_uuid' => $fleet->uuid,
|
|
439
440
|
'vehicle_uuid' => $vehicle->uuid,
|
|
440
|
-
])->delete()
|
|
441
|
+
])->delete()) {
|
|
442
|
+
FleetVehicle::invalidateApiCacheManually();
|
|
443
|
+
}
|
|
441
444
|
}
|
|
442
445
|
|
|
443
446
|
protected function assignDriverToFleet(Fleet $fleet, Driver $driver): void
|
|
@@ -450,10 +453,13 @@ class FleetController extends Controller
|
|
|
450
453
|
|
|
451
454
|
protected function removeDriverFromFleet(Fleet $fleet, Driver $driver): void
|
|
452
455
|
{
|
|
453
|
-
|
|
456
|
+
// Deleted without model events; fleet_drivers rows carry no company
|
|
457
|
+
if (FleetDriver::where([
|
|
454
458
|
'fleet_uuid' => $fleet->uuid,
|
|
455
459
|
'driver_uuid' => $driver->uuid,
|
|
456
|
-
])->delete()
|
|
460
|
+
])->delete()) {
|
|
461
|
+
FleetDriver::invalidateApiCacheManually();
|
|
462
|
+
}
|
|
457
463
|
}
|
|
458
464
|
|
|
459
465
|
protected function vehicleMembershipPayload(Fleet $fleet, Vehicle $vehicle, bool $assigned): array
|