Skip to content
Merged
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
14 changes: 3 additions & 11 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
# v1.6.71 — Socket settings from the console; live and test-mode API caches kept apart; driver-assigned events on driver channels
# v1.6.72 — Searchable columns registered at runtime

## Added

- **Socket settings from the console.** System administrators manage realtime channel authentication without touching the environment: `GET`/`POST int/v1/settings/socket` read and save the switch, the mode (`off`, `log`, `enforce`), the token TTL, the allowed origins and the publisher origin. Each value falls back to the environment when unset, so nothing changes until an administrator saves something. A signed `GET int/v1/socket/config` lets the socket server (fleetbase/fleetbase v0.7.70) poll the effective config and apply it at runtime, no restart. `SOCKETCLUSTER_AUTH_KEY` stays in the environment and is never stored or returned; turning the switch on without a key is refused. Every save is written to the activity log. (#314)
- **Category `parent_uuid` on internal responses.** The internal Category resource now includes `parent_uuid`, so a console can build a category tree from a flat list (Storefront's nested product categories). (#316)

## Fixed

- **A controller's filter is used for every query it serves.** `Find::httpFilterForModel()` cached its answer per model class for the life of the process, ignoring the filter a controller sets on its model (`$filter`) and whether the request was internal. Under Octane, the first controller to list a model decided the filter for every other: Fleet-Ops' order filter was applied to Storefront's order lists, so a network's orders page listed every order in the company. The cache is now keyed by the model's own filter, and a filter found by convention is picked per request audience. `Find::httpRequestForModel()` is keyed by the model's own request class too. (#315)
- **Live and test-mode data no longer share API cache entries.** List, model and relationship cache keys now carry the database connection (`{api_query}:mysql:…` / `{api_query}:sandbox:…`). Before, a console request in test mode could be served the cached live response for the same query (and the reverse), because console sessions carry no API credential to tell the two apart. (#312)
- **Driver-assigned lifecycle events reach `driver.{public_id}` and `driver.{uuid}`.** Order events for records with an assigned driver were only published on `driverAssigned.*`, which clients following the `{type}.{id}` convention never subscribe to. `driverAssigned.*` is still published and is now deprecated. (#311)
- **Searchable columns registered at runtime.** `Searchable::addSearchableColumns([...])` lets an extension make its own data findable through a core or Fleet-Ops model's search without editing the model: `Order::addSearchableColumns(['companion.reference_number', 'customerContact.name'])`. Columns may use the existing `relation.column` dot notation, and the relation may be one the extension adds with Eloquent's `Model::resolveRelationUsing()`. Registered columns are merged into `getSearchableColumns()` (search) and `searcheableFields()` (filters), per model class. (#317)

## Upgrade Steps

- Socket settings: nothing changes until an administrator saves one; the environment values stay the defaults. A socket server older than fleetbase/fleetbase v0.7.70 keeps reading its own environment.
- Otherwise none. Existing cache entries keep their old keys and expire within their TTL (5 minutes for lists, 1 hour for models); `deploy.sh` already runs `php artisan cache:clear`.
- None. Nothing changes until an extension registers columns.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fleetbase/core-api",
"version": "1.6.71",
"version": "1.6.72",
"description": "Core Framework and Resources for Fleetbase API",
"keywords": [
"fleetbase",
Expand Down
6 changes: 4 additions & 2 deletions src/Traits/HasApiModelBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,10 @@ public function getSingularName(): string
*/
public function searcheableFields()
{
if ($this->searchableColumns) {
return $this->searchableColumns;
// Columns registered at runtime (Searchable::addSearchableColumns) count as searchable too.
$runtime = method_exists($this, 'runtimeSearchableColumns') ? static::runtimeSearchableColumns() : [];
if ($this->searchableColumns || $runtime) {
return array_values(array_unique(array_merge((array) $this->searchableColumns, $runtime)));
}

return array_merge(
Expand Down
32 changes: 31 additions & 1 deletion src/Traits/Searchable.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,34 @@ trait Searchable
{
public static $searchable = true;

/**
* Searchable columns added at runtime, per model class. Extensions use this to make their own
* data findable through a core or Fleet-Ops model's search (e.g. a companion record's reference
* number on orders) without editing the model. Dot notation names a relation column, as in
* `$searchableColumns`; the relation may be one added with `Model::resolveRelationUsing()`.
*
* @var array<class-string, string[]>
*/
protected static array $runtimeSearchableColumns = [];

/**
* Registers extra searchable columns for this model class.
*
* @param string[] $columns column names, or `relation.column`
*/
public static function addSearchableColumns(array $columns): void
{
$existing = static::$runtimeSearchableColumns[static::class] ?? [];

static::$runtimeSearchableColumns[static::class] = array_values(array_unique(array_merge($existing, array_values(array_filter($columns, 'is_string')))));
}

/** Columns registered through addSearchableColumns() for this model class. */
public static function runtimeSearchableColumns(): array
{
return static::$runtimeSearchableColumns[static::class] ?? [];
}

/**
* Searches a column where LIKE.
*
Expand Down Expand Up @@ -107,6 +135,8 @@ public function scopeSearch($query, $search, ?callable $additionalQuery = null)
*/
public function getSearchableColumns()
{
return property_exists($this, 'searchableColumns') ? $this->searchableColumns : [];
$columns = property_exists($this, 'searchableColumns') ? (array) $this->searchableColumns : [];

return array_values(array_unique(array_merge($columns, static::runtimeSearchableColumns())));
}
}
34 changes: 34 additions & 0 deletions tests/Unit/Traits/SearchableRuntimeColumnsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

use Fleetbase\Traits\Searchable;
use Illuminate\Database\Eloquent\Model as EloquentModel;

class SearchableRuntimeRecord extends EloquentModel
{
use Searchable;

protected $table = 'searchable_runtime_records';
protected $searchableColumns = ['name', 'owner.name'];
}

class SearchableRuntimeBareRecord extends EloquentModel
{
use Searchable;

protected $table = 'searchable_runtime_bare_records';
}

it('merges columns registered at runtime into the model searchable columns, per class', function () {
SearchableRuntimeRecord::addSearchableColumns(['loan.plate_number', 'name', 42, null, 'loan.plate_number']);
SearchableRuntimeRecord::addSearchableColumns(['loan.vin']);

expect(SearchableRuntimeRecord::runtimeSearchableColumns())->toBe(['loan.plate_number', 'name', 'loan.vin'])
->and((new SearchableRuntimeRecord())->getSearchableColumns())->toBe(['name', 'owner.name', 'loan.plate_number', 'loan.vin'])
->and(SearchableRuntimeBareRecord::runtimeSearchableColumns())->toBe([], 'registration is per model class')
->and((new SearchableRuntimeBareRecord())->getSearchableColumns())->toBe([]);

SearchableRuntimeBareRecord::addSearchableColumns(['code']);

expect((new SearchableRuntimeBareRecord())->getSearchableColumns())->toBe(['code'])
->and((new SearchableRuntimeRecord())->getSearchableColumns())->not->toContain('code');
});
Loading