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
2 changes: 1 addition & 1 deletion .github/workflows/ember.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
tags:
- 'v*'
pull_request:
branches: [ main ]
branches: [ main, release/v*, dev-v* ]

env:
NODE_VERSION: 22.x
Expand Down
10 changes: 9 additions & 1 deletion .github/workflows/postman.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,17 @@ permissions:

jobs:
contract:
uses: fleetbase/fleetbase/.github/workflows/api-contract.yml@main
# Laravel 12 release only. Revert this job to what it was before the release once it
# ships. The published image is Laravel 10 and its
# extensions require core-api 1.x, so this branch can only resolve against the whole
# Laravel 12 train: the `l12` stack builds the image from fleetbase's Laravel 12 branch and
# links core-api and every extension from their release branches. Point `uses` and
# `stack-ref` at `main` once fleetbase/fleetbase's contract-stack change merges there.
uses: fleetbase/fleetbase/.github/workflows/api-contract.yml@ci/contract-stack
with:
collections: "Fleetbase Storefront API"
stack: l12
stack-ref: ci/contract-stack
build-from-source: false
# Without this the run tests the version of fleetbase/storefront-api baked into the
# published image, not the branch under review. The workflow checks this
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
tags:
- 'v*'
pull_request:
branches: [ main ]
branches: [ main, release/v*, dev-v* ]

jobs:
build:
Expand Down
16 changes: 6 additions & 10 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
> v0.4.31 ~ "Sign in with Apple tokens are revoked on deletion, and Launch App opens AppHelm"
> v0.5.0 ~ "Laravel 12 and core-api 2.0"

---
## Highlights

- **Sign in with Apple tokens are revoked when a customer deletes their account,** as App Review guideline 5.1.1(v) requires. Tokens are stored encrypted at sign-in, and a missing key or a refusal from Apple never blocks sign-in or deletion. See `docs/sign-in-with-apple.md`.
- **Launch App opens AppHelm,** Fleetbase's service for publishing a store as a web and mobile app. Fleetbase Cloud accounts sign in with the account they're already using, and self-hosted installs go to the page that explains the service.
- **Laravel 12.** The server package now runs on Laravel 12 with Carbon 3 and requires `fleetbase/core-api` ^2.0. It installs into the Laravel 12 Fleetbase API (fleetbase/fleetbase v0.8.0); Laravel 10 installs stay on the v0.4.31 line.
- **No security advisory exceptions.** `composer audit` reports no advisories on the Laravel 12 dependency set, so the Composer advisory workarounds are gone.

---
## Upgrading

- Run migrations: adds `apple_sign_in_tokens` on the storefront connection.
- Customers who signed in with Apple before a key was configured have no token stored yet. Theirs is stored the next time they sign in with Apple.

---
## Need help?
- [GitHub Discussions](https://github.com/fleetbase/fleetbase/discussions)
- [Discord](https://discord.gg/HnTqQ6zAVn)
- **Requires fleetbase/core-api v2.0.0** (Laravel 12, PHP 8.2 or later). Upgrade the API to fleetbase/fleetbase v0.8.0 first, then this extension.
- **Requires** `fleetbase/fleetops-api` ^0.7.0. Install those Laravel 12 releases first.
- Run `php artisan migrate` after upgrading.
38 changes: 14 additions & 24 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fleetbase/storefront-api",
"version": "0.4.31",
"version": "0.5.0",
"description": "Headless Commerce & Marketplace Extension for Fleetbase",
"keywords": [
"fleetbase-extension",
Expand All @@ -21,26 +21,26 @@
}
],
"require": {
"php": "^8.0",
"fleetbase/core-api": "^1.6.74",
"fleetbase/fleetops-api": "*",
"php": "^8.2",
"fleetbase/core-api": "^2.0",
"fleetbase/fleetops-api": "^0.7.0",
"geocoder-php/google-maps-places-provider": "^1.4",
"laravel-notification-channels/apn": "^5.0",
"laravel-notification-channels/fcm": "^4.1",
"laravel-notification-channels/twilio": "^3.3",
"milon/barcode": "^10.0",
"laravel-notification-channels/apn": "^5.5",
"laravel-notification-channels/fcm": "^5.1",
"laravel-notification-channels/twilio": "^4.1",
"milon/barcode": "^13.0",
"php-http/guzzle7-adapter": "^1.0",
"psr/http-factory-implementation": "*",
"stripe/stripe-php": "^17.0",
"toin0u/geocoder-laravel": "^4.4"
"toin0u/geocoder-laravel": "^13.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.34.1",
"illuminate/validation": "^10.0",
"nunomaduro/collision": "^7.0",
"pestphp/pest": "^2.33.2",
"phpstan/phpstan": "^1.10.38",
"symfony/var-dumper": "^5.4.29"
"illuminate/validation": "^12.69",
"nunomaduro/collision": "^8.0",
"pestphp/pest": "^3.0",
"phpstan/phpstan": "^2.0",
"symfony/var-dumper": "^7.0"
},
"autoload": {
"psr-4": {
Expand Down Expand Up @@ -75,16 +75,6 @@
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
},
"policy": {
"advisories": {
"ignore-id": {
"PKSA-zwc5-qtrz-zm1n": {
"on-audit": false,
"reason": "CVE-2026-48019 has no patched illuminate/mail 9.x or 10.x release, and fleetbase/core-api requires ^9.0|^10.0. This only stops the install block; composer audit still reports it. Remove when core-api moves to Laravel 12.60 or later."
}
}
}
}
},
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion extension.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Storefront",
"version": "0.4.31",
"version": "0.5.0",
"description": "Headless Commerce & Marketplace Extension for Fleetbase",
"repository": "https://github.com/fleetbase/storefront",
"license": "AGPL-3.0-or-later",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@fleetbase/storefront-engine",
"version": "0.4.31",
"version": "0.5.0",
"description": "Headless Commerce & Marketplace Extension for Fleetbase",
"fleetbase": {
"route": "storefront",
Expand Down
16 changes: 12 additions & 4 deletions scripts/pest-bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -361,10 +361,13 @@ function now($tz = null): Illuminate\Support\Carbon
});
}

if (class_exists('Illuminate\Http\Request') && !Illuminate\Http\Request::hasMacro('array')) {
Illuminate\Http\Request::macro('array', function (string $parameter, array $default = []): array {
return (array) $this->input($parameter, $default);
});
// `array` stays until released fleetops, which these tests load, moves to `inputArray`.
foreach (['inputArray', 'array'] as $macro) {
if (class_exists('Illuminate\Http\Request') && !Illuminate\Http\Request::hasMacro($macro) && !method_exists('Illuminate\Http\Request', $macro)) {
Illuminate\Http\Request::macro($macro, function (string $parameter, array $default = []): array {
return (array) $this->input($parameter, $default);
});
}
}

if (class_exists('Illuminate\Http\Request') && !Illuminate\Http\Request::hasMacro('or')) {
Expand Down Expand Up @@ -422,3 +425,8 @@ function now($tz = null): Illuminate\Support\Carbon

return false;
}, E_WARNING);

// cknow/laravel-money 8 finds its ISO currency table through config, which this harness never loads.
if (class_exists(Cknow\Money\Money::class)) {
Cknow\Money\Money::setCurrencies(new Money\Currencies\ISOCurrencies());
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,8 @@ public function down(): void
protected function indexExists(string $table, string $index): bool
{
try {
$indexes = Schema::connection(config('storefront.connection.db'))
->getConnection()
->getDoctrineSchemaManager()
->listTableIndexes($table);

return isset($indexes[$index]);
return collect(Schema::connection(config('storefront.connection.db'))->getIndexes($table))
->contains('name', strtolower($index));
} catch (Throwable $e) {
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions server/src/Http/Controllers/AddonCategoryController.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function createRecord(Request $request)
$this->validateRequest($request);

$record = $this->model->createRecordFromRequest($request, null, function (&$request, AddonCategory &$addonCategory) {
$addons = $request->array('addonCategory.addons');
$addons = $request->inputArray('addonCategory.addons');
$addonCategory->setAddons($addons);
});

Expand Down Expand Up @@ -77,7 +77,7 @@ public function updateRecord(Request $request, string $id)
try {
$this->validateRequest($request);
$record = $this->model->updateRecordFromRequest($request, $id, function (&$request, AddonCategory &$addonCategory) {
$addons = $request->array('addonCategory.addons');
$addons = $request->inputArray('addonCategory.addons');
$addonCategory->setAddons($addons);
});

Expand Down
2 changes: 1 addition & 1 deletion server/src/Http/Controllers/AnalyticsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ private function dateRanges(Request $request): array
{
$end = $request->date('end') ? Carbon::parse($request->date('end'))->endOfDay() : Carbon::now()->endOfDay();
$start = $request->date('start') ? Carbon::parse($request->date('start'))->startOfDay() : $end->copy()->subDays(29)->startOfDay();
$seconds = max($start->diffInSeconds($end), 1);
$seconds = max((int) $start->diffInSeconds($end, true), 1);
$previousEnd = $start->copy()->subSecond();
$previousStart = $previousEnd->copy()->subSeconds($seconds);

Expand Down
4 changes: 2 additions & 2 deletions server/src/Http/Controllers/CatalogController.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ public function assignSubjects(string $id, Request $request)
return response()->error('Catalog not found.', 404);
}

$this->syncSubjects($catalog, Store::class, $request->array('stores'));
$this->syncSubjects($catalog, FoodTruck::class, $request->array('food_trucks'));
$this->syncSubjects($catalog, Store::class, $request->inputArray('stores'));
$this->syncSubjects($catalog, FoodTruck::class, $request->inputArray('food_trucks'));

$catalog->load(['categories.products', 'hours', 'assignments.subject']);

Expand Down
2 changes: 1 addition & 1 deletion server/src/Http/Controllers/MetricsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public function all(Request $request)
{
$start = $request->date('start')?->startOfDay();
$end = $request->date('end')?->endOfDay();
$discover = $request->array('discover', []);
$discover = $request->inputArray('discover', []);

try {
$data = Metrics::forCompany($request->user()->company, $start, $end)->with($discover)->get();
Expand Down
16 changes: 8 additions & 8 deletions server/src/Http/Controllers/NetworkController.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public function sendInvites(string $id, NetworkActionRequest $request)
return response()->error('This category is not one of the network\'s categories.', 422);
}

$recipients = collect($request->array('recipients'))->filter(fn ($email) => is_string($email) && filter_var($email, FILTER_VALIDATE_EMAIL))->map(fn ($email) => strtolower(trim($email)))->unique()->values();
$recipients = collect($request->inputArray('recipients'))->filter(fn ($email) => is_string($email) && filter_var($email, FILTER_VALIDATE_EMAIL))->map(fn ($email) => strtolower(trim($email)))->unique()->values();
$expiresInDays = $request->input('expires_in_days');
$meta = array_filter([
'status' => 'pending',
Expand Down Expand Up @@ -579,8 +579,8 @@ public function addStores(string $id, NetworkActionRequest $request)
return response()->error('Network not found.', 404);
}

$stores = collect($request->array('stores'))->filter(fn ($storeId) => is_string($storeId))->values();
$remove = collect($request->array('remove'))->filter(fn ($storeId) => is_string($storeId))->values();
$stores = collect($request->inputArray('stores'))->filter(fn ($storeId) => is_string($storeId))->values();
$remove = collect($request->inputArray('remove'))->filter(fn ($storeId) => is_string($storeId))->values();
$members = NetworkStore::where('network_uuid', $network->uuid)->whereIn('store_uuid', $stores)->pluck('store_uuid');

// Existing members (which may belong to other companies) are left as they are; new
Expand Down Expand Up @@ -742,7 +742,7 @@ public function suspendStores(string $id, NetworkActionRequest $request)
return response()->error('Network not found.', 404);
}

return $this->setMembershipStatus($network, $request->array('stores'), NetworkStore::STATUS_SUSPENDED);
return $this->setMembershipStatus($network, $request->inputArray('stores'), NetworkStore::STATUS_SUSPENDED);
}

/**
Expand All @@ -758,7 +758,7 @@ public function reinstateStores(string $id, NetworkActionRequest $request)
return response()->error('Network not found.', 404);
}

return $this->setMembershipStatus($network, $request->array('stores'), NetworkStore::STATUS_ACTIVE);
return $this->setMembershipStatus($network, $request->inputArray('stores'), NetworkStore::STATUS_ACTIVE);
}

/**
Expand All @@ -775,7 +775,7 @@ public function approveStores(string $id, NetworkActionRequest $request)
return response()->error('Network not found.', 404);
}

$stores = $request->array('stores');
$stores = $request->inputArray('stores');
$this->closeAwaitingInvitations($network->uuid, $stores, 'accepted', 'approved_at');

return $this->setMembershipStatus($network, $stores, NetworkStore::STATUS_ACTIVE);
Expand All @@ -795,7 +795,7 @@ public function rejectStores(string $id, NetworkActionRequest $request)
return response()->error('Network not found.', 404);
}

$stores = $request->array('stores');
$stores = $request->inputArray('stores');
$this->closeAwaitingInvitations($network->uuid, $stores, 'rejected', 'rejected_at');

$removed = NetworkStore::where('network_uuid', $network->uuid)
Expand Down Expand Up @@ -898,7 +898,7 @@ public function removeStores(string $id, NetworkActionRequest $request)
return response()->error('Network not found.', 404);
}

$stores = $request->array('stores');
$stores = $request->inputArray('stores');

// delete each
foreach ($stores as $storeId) {
Expand Down
2 changes: 1 addition & 1 deletion server/src/Http/Controllers/ProductController.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public function processImports(Request $request)
*/
public function createEntities(Request $request)
{
$productIds = $request->array('products');
$productIds = $request->inputArray('products');
$products = Product::whereIn('uuid', $productIds)->get();
$entities = [];

Expand Down
2 changes: 1 addition & 1 deletion server/src/Observers/NetworkObserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class NetworkObserver
public function updating(Network $network): void
{
$network->flushAttributesCache();
$alertable = Request::array('network.alertable');
$alertable = Request::inputArray('network.alertable');

// set alertables to public_id
$network->alertable = collect($alertable)->mapWithKeys(
Expand Down
2 changes: 1 addition & 1 deletion server/src/Support/OrderInsights.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public static function lateMinutes(Order $order, ?Carbon $dueAt): int
return 0;
}

return (int) $dueAt->diffInMinutes(now());
return (int) $dueAt->diffInMinutes(now(), true);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ public function send($notifiables, $notification)
{
}

public function sendNow($notifiables, $notification)
public function sendNow($notifiables, $notification, ?array $channels = null)
{
}
}
Expand Down Expand Up @@ -1612,7 +1612,7 @@ public function send($notifiables, $notification)
throw new RuntimeException('Email provider unavailable');
}

public function sendNow($notifiables, $notification)
public function sendNow($notifiables, $notification, ?array $channels = null)
{
throw new RuntimeException('Email provider unavailable');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public function send($notifiables, $notification)
{
}

public function sendNow($notifiables, $notification)
public function sendNow($notifiables, $notification, ?array $channels = null)
{
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public function send($notifiables, $notification)
{
}

public function sendNow($notifiables, $notification)
public function sendNow($notifiables, $notification, ?array $channels = null)
{
}
}
Expand Down
15 changes: 15 additions & 0 deletions server/tests/Unit/Http/Controllers/OrderChatControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,21 @@ public function put($path, $contents, $options = [])
return true;
}

public function path($path)
{
return $path;
}

public function putFile($path, $file = null, $options = [])
{
return false;
}

public function putFileAs($path, $file, $name = null, $options = [])
{
return false;
}

public function url($path)
{
return 'https://cdn.test/' . $path;
Expand Down
1 change: 1 addition & 0 deletions server/tests/Unit/Support/OrderInsightsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ function orderInsightsEntity(string $name, bool $unavailable): Entity
$due = Carbon::parse('2026-10-14 11:15:00');

expect(OrderInsights::lateMinutes(orderInsightsOrder(), $due))->toBe(45)
->and(OrderInsights::lateMinutes(orderInsightsOrder(), Carbon::parse('2026-10-14 11:14:30')))->toBe(45)
->and(OrderInsights::lateMinutes(orderInsightsOrder(), Carbon::parse('2026-10-14 12:30:00')))->toBe(0)
->and(OrderInsights::lateMinutes(orderInsightsOrder(), null))->toBe(0)
->and(OrderInsights::lateMinutes(orderInsightsOrder(['status' => 'completed']), $due))->toBe(0)
Expand Down
2 changes: 1 addition & 1 deletion server/tests/Unit/Support/StorefrontTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ public function send($notifiables, $notification)
$this->calls[] = 'send';
}

public function sendNow($notifiables, $notification)
public function sendNow($notifiables, $notification, ?array $channels = null)
{
$this->calls[] = 'send_now';
}
Expand Down
Loading