Skip to content

fix(api-logs): log every route that opts in to LogApiRequests and let extensions relate ids - #302

Merged
roncodes merged 7 commits into
release/v1.6.69from
fix/log-storefront-api-requests
Oct 13, 2026
Merged

roncodes merged 7 commits into
release/v1.6.69from
fix/log-storefront-api-requests

Conversation

@roncodes

@roncodes roncodes commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

What

Storefront API write requests were never written to api_request_logs. That is why we could not trace the abuse of POST storefront/v1/customers/request-creation-code, which sent about $500 of Twilio SMS.

  • LogApiRequests::shouldLog() required Http::isPublicRequest(), which only matches route URIs starting with v1/ (since 39a6760, v1.6.17). Storefront adds LogApiRequests to its own storefront.api group, but its routes are storefront/v1/..., so they were skipped.
  • Even when a request was logged, nothing on the row identified which storefront key made it.

Core stays extension-agnostic: nothing here names storefront.

Change

  • Logging opts in through middleware. In core, only the public fleetbase.api group attaches LogApiRequests, and an extension attaches it to its own public group. Attaching the middleware is the opt-in, so shouldLog() now skips only internal routes (Http::isInternalRequest(), any int segment) and requests without a route. It no longer requires the core v1/ prefix.
  • isPublicRequest() is unchanged. It still matches v1/ only, so storefront filters, resources and validation behave as before.
  • Extensions can relate ids to a log row. LogApiRequest::RELATED_ATTRIBUTE (api_request_log_related) is a request attribute. getPayload() adds its ids, skipping empty ones, to the related column. feat(api-logs): relate logged storefront requests to the store or network whose key made them storefront#143 sets it to the store or network public id, so a row traces back to the storefront key and its company (company_uuid already comes from the session).

Also: ApiModelCache coverage on the release branch

After #300 and #301 merged, release/v1.6.69 fell under the 100% coverage floor. This PR:

  • Adds a hasSession() check to getCompanyUuid(). For a request with no session, generateQueryCacheKey() threw "Session store not set on request." before the session-scope guard ran. A test builds a key for such a request.
  • Adds tests for three statements from feat(api-cache): writes invalidate every cache that embeds the written table #301: the dependency memo reset at its limit, skipping static, protected and parameterised relation methods, and the catch when the transactions manager can't be resolved.

ApiModelCache.php is at 302/302 statements locally. The PR also merges the latest release branch.

Tests

  • tests/Unit/Http/MiddlewareContractsTest.php: a POST to an extension route (storefront/v1/customers/request-creation-code) is logged with the related ids from the request attribute. storefront/int/v1 writes and requests without a route are not logged. Existing tests cover v1/ (logged) and int/v1/ (not logged).
  • tests/Unit/Jobs/LogApiRequestTest.php: ids from the attribute, an array or a single value, end up in related, and empty ids are skipped.
  • tests/Unit/Support/ApiModelCacheTest.php: the four tests above.

Validation

  • LogApiRequests, LogApiRequest, ApiModelCache and HasApiModelCache tests pass: 151 tests.
  • php-cs-fixer (repo config, dry run): no changes needed.

…re or network

Storefront routes are mounted under storefront/v1/, but isPublicRequest only
matched v1/ since 39a6760, so LogApiRequests never logged storefront writes.
Treat storefront/v1/ as public too (internal int/ routes stay excluded), and add
the storefront store or network public id to the log's related column so a
request can be traced back to the key that made it.
getSessionScope returns no scope when the request has no session. Every test
request had one, so the release branch fell one statement short of the coverage
floor after #300.
getCompanyUuid called $request->session() unguarded, so generateQueryCacheKey
threw "Session store not set on request." before getSessionScope's hasSession
check could apply. Check hasSession first and fall back to the user and request
input as before. The test now covers the sessionless path through
generateQueryCacheKey instead of calling getSessionScope by reflection.
@codecov

codecov Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (f4a20cb) to head (304f951).
⚠️ Report is 12 commits behind head on release/v1.6.69.

Additional details and impacted files
@@                 Coverage Diff                  @@
##             release/v1.6.69      #302    +/-   ##
====================================================
  Coverage             100.00%   100.00%            
- Complexity              8276      8341    +65     
====================================================
  Files                    454       454            
  Lines                  26453     26583   +130     
====================================================
+ Hits                   26453     26583   +130     
Flag Coverage Δ
backend 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…let extensions relate ids

Core no longer names storefront. Every route that runs LogApiRequests opted in
through its middleware group (core's public API or an extension's), so
shouldLog only skips internal routes and requests without a route, instead of
requiring the core v1/ prefix. isPublicRequest is back to v1/ only, so
storefront filters and resources are unchanged.

LogApiRequest::RELATED_ATTRIBUTE ("api_request_log_related") is a request
attribute whose ids are added to the log's related column. Extensions set it
from their middleware, for example storefront with the public id of the store
or network whose key made the request.
… methods and an unresolvable transactions manager

#301 left three statements in ApiModelCache unrun, so the release branch is
under the 100% coverage floor.
@roncodes roncodes changed the title fix(api-logs): log storefront API requests and relate them to the store or network fix(api-logs): log every route that opts in to LogApiRequests and let extensions relate ids Oct 13, 2026
@roncodes
roncodes merged commit 4149b2e into release/v1.6.69 Oct 13, 2026
5 checks passed
@roncodes
roncodes deleted the fix/log-storefront-api-requests branch October 13, 2026 06:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant