Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions server/src/Casts/MultiPolygon.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use Fleetbase\FleetOps\Support\Utils;
use Fleetbase\LaravelMysqlSpatial\Eloquent\SpatialExpression;
use Fleetbase\LaravelMysqlSpatial\Types\GeometryInterface;
use Fleetbase\LaravelMysqlSpatial\Types\MultiPolygon as SpatialMultiPolygon;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class MultiPolygon implements CastsAttributes
Expand Down Expand Up @@ -37,12 +36,6 @@ public function set($model, $key, $value, $attributes)
return new SpatialExpression($value);
}

if ($value instanceof SpatialMultiPolygon) {
$model->geometries[$key] = $value;

return $value;
}

if (Utils::isGeoJson($value)) {
$value = Utils::createGeometryObjectFromGeoJson($value);
$model->geometries[$key] = $value;
Expand Down
11 changes: 11 additions & 0 deletions server/src/Casts/Point.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,22 @@ public function set($model, $key, $value, $attributes)
return $point;
}

// Unreachable today: SpatialExpression extends Expression, so the guard
// above returns first. Deliberately kept rather than deleted, because
// that earlier guard returns WITHOUT recording $model->geometries[$key],
// and this block documents that a spatial expression is meant to be
// recorded. Note the recording would currently be a no-op anyway —
// SpatialTrait::performInsert restores the attribute from geometries[],
// which would assign the same expression back. Restoring it to a usable
// Point would mean recording the geometry behind the expression, which
// is a behaviour change and out of scope here.
// @codeCoverageIgnoreStart
if ($value instanceof SpatialExpression) {
$model->geometries[$key] = $value;

return $value;
}
// @codeCoverageIgnoreEnd

return static::createEmptySpatialExpression();
}
Expand Down
9 changes: 1 addition & 8 deletions server/src/Casts/Polygon.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use Fleetbase\FleetOps\Support\Utils;
use Fleetbase\LaravelMysqlSpatial\Eloquent\SpatialExpression;
use Fleetbase\LaravelMysqlSpatial\Types\GeometryInterface;
use Fleetbase\LaravelMysqlSpatial\Types\Polygon as SpatialPolygon;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class Polygon implements CastsAttributes
Expand Down Expand Up @@ -34,13 +33,7 @@ public function set($model, $key, $value, $attributes)
if ($value instanceof GeometryInterface) {
$model->geometries[$key] = $value;

return $value;
}

if ($value instanceof SpatialPolygon) {
$model->geometries[$key] = $value;

return $value;
return new SpatialExpression($value);
}

if (Utils::isGeoJson($value)) {
Expand Down
10 changes: 2 additions & 8 deletions server/src/Http/Controllers/Api/v1/DriverController.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,8 @@ public function create(CreateDriverRequest $request)
// create user account for driver
$user = $this->createUser($userDetails);

// Assign company
if ($company) {
$user->assignCompany($company);
} else {
$user->deleteQuietly();

return $this->apiError('Unable to assign driver to company.');
}
// Assign company — already guaranteed non-null by the guard above
$user->assignCompany($company);

// Set user type
$user->setUserType('driver');
Expand Down
23 changes: 17 additions & 6 deletions server/src/Http/Controllers/Api/v1/OrderController.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public function create(CreateOrderRequest $request)

// Set order config to input
$input['order_config_uuid'] = $orderConfig->uuid;
$input['type'] = $orderConfig->key;
$input['type'] = $orderConfig->key ?? 'transport';

// make sure company is set
$input['company_uuid'] = $this->sessionCompany();
Expand Down Expand Up @@ -283,11 +283,6 @@ public function create(CreateOrderRequest $request)
}
}

// if no type is set its default to transport
if (!isset($input['type'])) {
$input['type'] = 'transport';
}

// if no status is set its default to `created`
if (!isset($input['status'])) {
$input['status'] = 'created';
Expand Down Expand Up @@ -1081,9 +1076,19 @@ public function updateActivity($id, Request $request)
}

// if no order found
//
// Currently unreachable: findOrder() above is typed `: Order` and throws
// rather than returning null. The catch it sits behind is itself dead
// because of an upstream defect — Fleetbase\Models\Model::findByIdOrFail()
// in core-api calls a getModelNotFoundException() method that does not
// exist on Eloquent's builder, so it raises BadMethodCallException
// instead of ModelNotFoundException and a missing order surfaces as a 500
// rather than this 404. Remove this annotation once core-api is patched.
// @codeCoverageIgnoreStart
if (!$order) {
return response()->apiError('Order resource not found.', 404);
}
// @codeCoverageIgnoreEnd

// if order is still status of `created` trigger started flag
if ($order->status === 'created') {
Expand Down Expand Up @@ -1614,9 +1619,15 @@ function ($value) {
// Normalize into one array
$incoming = array_merge($rawInputs, $base64Inputs);

// Defensive only: the validate() above already requires `photos` to be a
// non-empty array whose every element is an upload or a decodable base64
// string, and each of those survives the filter, so this cannot be empty.
// Kept in case those rules are relaxed.
// @codeCoverageIgnoreStart
if (empty($incoming)) {
return $this->apiError('No photo data to capture.');
}
// @codeCoverageIgnoreEnd

// Load Order
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ public function reverse(Request $request)
$query = $request->or(['coordinates', 'query']);
$single = $request->boolean('single');

/** @var \Fleetbase\LaravelMysqlSpatial\Types\Point $coordinates */
$coordinates = Utils::getPointFromCoordinates($query);
// Resolve strictly: getPointFromCoordinates() is typed `: Point` and
// falls back to Point(0, 0) for unusable input, which would silently
// reverse-geocode Null Island instead of reporting the bad request
/** @var \Fleetbase\LaravelMysqlSpatial\Types\Point|null $coordinates */
$coordinates = Utils::getPointFromCoordinatesStrict($query);

// if not a valid point error
if (!$coordinates instanceof \Fleetbase\LaravelMysqlSpatial\Types\Point) {
Expand Down
10 changes: 2 additions & 8 deletions server/src/Http/Controllers/Internal/v1/MetricsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,11 @@ protected function resolvePeriod(Request $request): array
}
}

// $request->date() yields a Carbon (a DateTime) or null, and both
// fallbacks are DateTime, so these are always DateTime instances
$start = $request->date('start') ?? Carbon::now()->subDays(30)->toDateTime();
$end = $request->date('end') ?? Carbon::now()->toDateTime();

if (!$start instanceof \DateTime) {
$start = Carbon::parse($start)->toDateTime();
}

if (!$end instanceof \DateTime) {
$end = Carbon::parse($end)->toDateTime();
}

return [$start, $end];
}
}
7 changes: 3 additions & 4 deletions server/src/Http/Resources/v1/Maintenance.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,10 @@ protected function transformMorphResource($model): ?array
return null;
}

// Find::httpResourceForModel() always resolves a class, falling back to
// FleetbaseResource, so there is no un-resolvable case to handle here
$resourceClass = \Fleetbase\Support\Find::httpResourceForModel($model);
if ($resourceClass) {
return (new $resourceClass($model))->resolve();
}

return (new \Illuminate\Http\Resources\Json\JsonResource($model))->resolve();
return (new $resourceClass($model))->resolve();
}
}
7 changes: 3 additions & 4 deletions server/src/Http/Resources/v1/MaintenanceSchedule.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,10 @@ protected function transformMorphResource($model): ?array
return null;
}

// Find::httpResourceForModel() always resolves a class, falling back to
// FleetbaseResource, so there is no un-resolvable case to handle here
$resourceClass = \Fleetbase\Support\Find::httpResourceForModel($model);
if ($resourceClass) {
return (new $resourceClass($model))->resolve();
}

return (new \Illuminate\Http\Resources\Json\JsonResource($model))->resolve();
return (new $resourceClass($model))->resolve();
}
}
10 changes: 3 additions & 7 deletions server/src/Http/Resources/v1/Order.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,11 @@ protected function transformMorphResource($model)
return null;
}

// Use Find to get the appropriate resource class for this model
// Find::httpResourceForModel() always resolves a class, falling back to
// FleetbaseResource, so there is no un-resolvable case to handle here
$resourceClass = \Fleetbase\Support\Find::httpResourceForModel($model);

if ($resourceClass) {
return (new $resourceClass($model))->resolve();
}

// Fallback to generic resource
return (new \Illuminate\Http\Resources\Json\JsonResource($model))->resolve();
return (new $resourceClass($model))->resolve();
}

/**
Expand Down
10 changes: 4 additions & 6 deletions server/src/Http/Resources/v1/PurchaseRate.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,10 @@ public function serviceQuote()
return $this->resource->serviceQuote ? new ServiceQuote($this->resource->serviceQuote) : null;
}

if (method_exists($this, 'loadMissing')) {
$this->loadMissing('serviceQuote');

return $this->serviceQuote ? new ServiceQuote($this->serviceQuote) : null;
}

// A `loadMissing` fallback used to live here, guarded by
// method_exists($this, 'loadMissing'). No class in this resource's
// hierarchy declares that method — it only resolves through __call,
// which method_exists cannot see — so the branch never ran.
return null;
}
}
10 changes: 4 additions & 6 deletions server/src/Http/Resources/v1/TrackingStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,10 @@ public function trackingNumber()
return $this->resource->trackingNumber ? new TrackingNumber($this->resource->trackingNumber) : null;
}

if (method_exists($this, 'loadMissing')) {
$this->loadMissing('trackingNumber');

return $this->trackingNumber ? new TrackingNumber($this->trackingNumber) : null;
}

// A `loadMissing` fallback used to live here, guarded by
// method_exists($this, 'loadMissing'). No class in this resource's
// hierarchy declares that method — it only resolves through __call,
// which method_exists cannot see — so the branch never ran.
return null;
}
}
7 changes: 3 additions & 4 deletions server/src/Http/Resources/v1/WorkOrder.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,10 @@ protected function transformMorphResource($model): ?array
return null;
}

// Find::httpResourceForModel() always resolves a class, falling back to
// FleetbaseResource, so there is no un-resolvable case to handle here
$resourceClass = \Fleetbase\Support\Find::httpResourceForModel($model);
if ($resourceClass) {
return (new $resourceClass($model))->resolve();
}

return (new \Illuminate\Http\Resources\Json\JsonResource($model))->resolve();
return (new $resourceClass($model))->resolve();
}
}
4 changes: 0 additions & 4 deletions server/src/Integrations/Lalamove/Lalamove.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,6 @@ public static function instance(?string $apiKey = null, ?string $apiSecret = nul

public static function __callStatic($name, $arguments)
{
if ($name === 'instance') {
return static::instance(...$arguments);
}

$sandbox = false;

if (Str::contains($name, 'FromSandbox')) {
Expand Down
12 changes: 4 additions & 8 deletions server/src/Models/Payload.php
Original file line number Diff line number Diff line change
Expand Up @@ -911,14 +911,10 @@ public function setPlace($property, Place $place, array $options = [])
$save = data_get($options, 'save', false);
$callback = data_get($options, 'callback', false);

if ($instance) {
if (Str::isUuid($instance)) {
$this->setAttribute($attr, $instance);
} elseif ($instance instanceof Model) {
$this->setAttribute($attr, $instance->uuid);
} else {
$this->setAttribute($attr, $instance);
}
// createFromMixed() returns a Place or null, so a resolved instance is
// always a model — the uuid-string and passthrough cases cannot occur
if ($instance instanceof Model) {
$this->setAttribute($attr, $instance->uuid);
}

// Get the ID property
Expand Down
21 changes: 9 additions & 12 deletions server/src/Models/Place.php
Original file line number Diff line number Diff line change
Expand Up @@ -713,11 +713,6 @@ public static function createFromMixed($place, $attributes = [], $saveInstance =
}
// If $place is an array
elseif (is_array($place)) {
// If $place is an array of coordinates, create a new Place object
if (Utils::isCoordinatesStrict($place)) {
return static::createFromCoordinates($place, $attributes, $saveInstance);
}

// Get uuid if set
$uuid = data_get($place, 'uuid');

Expand Down Expand Up @@ -792,12 +787,11 @@ public static function insertFromMixed($place)
}
}

// handle address if set
if (!empty($place['address'])) {
return static::insertFromGeocodingLookup($place['address']);
}

return static::insertFromGeocodingLookup($place);
} elseif ($place instanceof \Geocoder\Provider\GoogleMaps\Model\GoogleAddress) {
// Must be tested before the array/object arm below, which would
// otherwise swallow it and flatten it to an array
return static::insertFromGoogleAddress($place);
} elseif (is_array($place) || is_object($place)) {
// if place already exists just return uuid
if (static::isValidPlaceUuid(data_get($place, 'uuid'))) {
Expand All @@ -815,14 +809,17 @@ public static function insertFromMixed($place)

$values = is_array($place) ? $place : (array) $place;

// handle address if set
if (!empty($values['address'])) {
return static::insertFromGeocodingLookup($values['address']);
}

if ($existingPlace = static::findExistingSharedPlace($values)) {
return $existingPlace->uuid;
}

// create a new place
return static::insertGetUuid((array) $values);
} elseif ($place instanceof \Geocoder\Provider\GoogleMaps\Model\GoogleAddress) {
return static::insertFromGoogleAddress($place);
}
}

Expand Down
6 changes: 2 additions & 4 deletions server/src/Models/ServiceRate.php
Original file line number Diff line number Diff line change
Expand Up @@ -1367,12 +1367,10 @@ protected function getLngLatFromPlace(?Place $place): ?array
return null;
}

// getLocationAsPoint() is typed `: SpatialPoint`, which always exposes
// getLat()/getLng(), so no further guarding is possible here
$point = $place->getLocationAsPoint();

if (!$point || !method_exists($point, 'getLat') || !method_exists($point, 'getLng')) {
return null;
}

return ['lat' => (float) $point->getLat(), 'lng' => (float) $point->getLng()];
}

Expand Down
10 changes: 8 additions & 2 deletions server/src/Support/Utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -1162,9 +1162,13 @@ public static function createPolygonFromCountry(string $country): ?\Fleetbase\La

$feature = collect($globe->features)->first(
function ($feature) use ($country) {
// Every feature in the bundled resources/data/globe.json carries
// both codes, so this only guards against future data changes.
// @codeCoverageIgnoreStart
if (!isset($feature->properties->ISO_A3) || !isset($feature->properties->ISO_A2)) {
return false;
}
// @codeCoverageIgnoreEnd

return strtolower($feature->properties->ISO_A3) === $country || strtolower($feature->properties->ISO_A2) === $country;
}
Expand Down Expand Up @@ -1199,8 +1203,10 @@ public static function coordsToCircle($latitude, $longitude, $meters)
$radius = ($meters * 1000) / 6378137;
// create circle coordinates
$coords = collect();
// loop through the array and write path linestrings
for ($i = 0; $i <= 360; $i += 3) {
// loop through the array and write path linestrings — stop short of 360
// so the ring is closed explicitly below rather than by re-computing the
// 0 degree vertex, which produced a duplicated point
for ($i = 0; $i < 360; $i += 3) {
$radial = deg2rad($i);
$lat_rad = asin(sin($latitude) * cos($radius) + cos($latitude) * sin($radius) * cos($radial));
$dlon_rad = atan2(sin($radial) * sin($radius) * cos($latitude), cos($radius) - sin($latitude) * sin($lat_rad));
Expand Down
Loading