From ad559babeb43b94d043c643bfc0dbb1fea360c29 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Tue, 13 Oct 2026 14:17:09 +0800 Subject: [PATCH] feat(api-logs): log writes to the public invoice routes like the public API The customer invoice routes (ledger/public/...) ran no middleware, so paying an invoice was never written to the API request logs. They now run LogApiRequests. The public wallet API (ledger/v1/...) already runs it through fleetbase.api and is logged once core-api logs every route that opts in (fleetbase/core-api#302). Webhooks and internal routes stay unlogged. The route registrar test stub now records each route's group middleware. --- RELEASE.md | 6 ++++- scripts/pest-bootstrap.php | 2 +- server/src/routes.php | 18 +++++++++------ server/tests/Routes/RouteRegistrationTest.php | 22 +++++++++++++++++++ 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 13ab46a..0bec14e 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -7,9 +7,13 @@ Two exceptions: orders and drivers are written constantly, so a transaction's order or driver snapshot can lag by up to the five-minute list cache. +- **Public API requests weren't logged.** Writes to the public wallet API (`ledger/v1/...`) and the customer invoice routes (`ledger/public/...`, such as paying an invoice) are now written to the API request logs, like core's public API. Webhooks and the console's internal routes aren't logged. + --- ## Upgrading -Takes effect with fleetbase/core-api's cache dependency support ([fleetbase/core-api#301](https://github.com/fleetbase/core-api/pull/301)). With an older core-api the change is inert. +The transaction list fix takes effect with fleetbase/core-api's cache dependency support ([fleetbase/core-api#301](https://github.com/fleetbase/core-api/pull/301)). With an older core-api the change is inert. + +API request logging takes effect with [fleetbase/core-api#302](https://github.com/fleetbase/core-api/pull/302), which logs every route that runs `LogApiRequests` rather than only `v1/...`. With an older core-api, ledger requests are still not logged. --- ## Need help? diff --git a/scripts/pest-bootstrap.php b/scripts/pest-bootstrap.php index 81376c5..ce82a5f 100644 --- a/scripts/pest-bootstrap.php +++ b/scripts/pest-bootstrap.php @@ -87,7 +87,7 @@ function config(?string $key = null, mixed $default = null): mixed if (!$app->bound('router')) { if (!class_exists('Fleetbase\TestSupport\RouteRegistrar')) { - eval('namespace Fleetbase\TestSupport; class RouteRegistrar { public static array $routes = []; public static function reset(): void { self::$routes = []; } public function prefix(string $prefix): self { return $this; } public function namespace(string $namespace): self { return $this; } public function group(array|\Closure $attributes, ?\Closure $callback = null): self { ($callback ?? $attributes)($this); return $this; } public function get(string $uri, mixed $action): self { self::$routes[] = ["GET", $uri, $action]; return $this; } public function post(string $uri, mixed $action): self { self::$routes[] = ["POST", $uri, $action]; return $this; } public function fleetbaseRoutes(string $resource, ?\Closure $callback = null): self { self::$routes[] = ["RESOURCE", $resource, null]; if ($callback) { $callback($this, fn (string $method): string => $resource . "Controller@" . $method); } return $this; } }'); + eval('namespace Fleetbase\TestSupport; class RouteRegistrar { public static array $routes = []; public static array $middleware = []; public static function reset(): void { self::$routes = []; self::$middleware = []; } public function prefix(string $prefix): self { return $this; } public function namespace(string $namespace): self { return $this; } public function group(array|\Closure $attributes, ?\Closure $callback = null): self { $previous = self::$middleware; if (is_array($attributes)) { self::$middleware = array_merge(self::$middleware, (array) ($attributes["middleware"] ?? [])); } ($callback ?? $attributes)($this); self::$middleware = $previous; return $this; } public function get(string $uri, mixed $action): self { self::$routes[] = ["GET", $uri, $action, self::$middleware]; return $this; } public function post(string $uri, mixed $action): self { self::$routes[] = ["POST", $uri, $action, self::$middleware]; return $this; } public function fleetbaseRoutes(string $resource, ?\Closure $callback = null): self { self::$routes[] = ["RESOURCE", $resource, null]; if ($callback) { $callback($this, fn (string $method): string => $resource . "Controller@" . $method); } return $this; } }'); } $app->singleton('router', fn () => new Fleetbase\TestSupport\RouteRegistrar()); diff --git a/server/src/routes.php b/server/src/routes.php index f81bb53..924ddba 100644 --- a/server/src/routes.php +++ b/server/src/routes.php @@ -33,18 +33,22 @@ function ($router) { |-------------------------------------------------------------------------- | | These routes allow customers to view and pay invoices without logging in. - | They are scoped by the globally-unique invoice public_id / uuid. + | They are scoped by the globally-unique invoice public_id / uuid. Writes + | are logged to the API request logs like the public API's. | | GET /ledger/public/invoices/{public_id} | GET /ledger/public/invoices/{public_id}/gateways | POST /ledger/public/invoices/{public_id}/pay */ - $router->prefix('public')->namespace('Public')->group(function ($router) { - $router->get('invoices/{public_id}', 'PublicInvoiceController@show'); - $router->get('invoices/{public_id}/gateways', 'PublicInvoiceController@gateways'); - $router->post('invoices/{public_id}/pay', 'PublicInvoiceController@pay'); - $router->get('refunds/{refund_id}', 'PublicInvoiceController@refund'); - }); + $router->prefix('public')->namespace('Public')->group( + ['middleware' => [Fleetbase\Http\Middleware\LogApiRequests::class]], + function ($router) { + $router->get('invoices/{public_id}', 'PublicInvoiceController@show'); + $router->get('invoices/{public_id}/gateways', 'PublicInvoiceController@gateways'); + $router->post('invoices/{public_id}/pay', 'PublicInvoiceController@pay'); + $router->get('refunds/{refund_id}', 'PublicInvoiceController@refund'); + } + ); /* |-------------------------------------------------------------------------- diff --git a/server/tests/Routes/RouteRegistrationTest.php b/server/tests/Routes/RouteRegistrationTest.php index 15a2558..269ab2f 100644 --- a/server/tests/Routes/RouteRegistrationTest.php +++ b/server/tests/Routes/RouteRegistrationTest.php @@ -37,3 +37,25 @@ 'GET|reports/wallet-summary|ReportController@walletSummary', ); }); + +test('Ledger logs requests to its public API and customer invoice routes but not webhooks or internal routes', function () { + RouteRegistrar::reset(); + Facade::clearResolvedInstance('router'); + Container::getInstance()->instance('router', new RouteRegistrar()); + + require __DIR__ . '/../../src/routes.php'; + + $middleware = []; + foreach (RouteRegistrar::$routes as $route) { + if (is_string($route[2])) { + $middleware[$route[0] . '|' . $route[1] . '|' . $route[2]] = $route[3] ?? []; + } + } + + // The public API's fleetbase.api group runs LogApiRequests + expect($middleware['POST|wallet/topup|Api\v1\WalletApiController@topUp'])->toBe(['fleetbase.api']) + ->and($middleware['POST|invoices/{public_id}/pay|PublicInvoiceController@pay'])->toBe([Fleetbase\Http\Middleware\LogApiRequests::class]) + ->and($middleware['GET|invoices/{public_id}|PublicInvoiceController@show'])->toBe([Fleetbase\Http\Middleware\LogApiRequests::class]) + ->and($middleware['POST|webhooks/{driver}|WebhookController@handle'])->toBe([]) + ->and($middleware['GET|gateways/drivers|GatewayController@drivers'])->toBe(['fleetbase.protected']); +});