The DispatchUserNotificationJob has been logging sent=true for every
allowed channel without actually sending anything — a TODO marker covered
by the existing test suite, which only asserted log rows. The downstream
"missed today" widget read those rows and reported falsely. This commit
makes the telemetry truthful by wiring the real send.
- App\Notifications\FuelPriceAlert — Notification class with via() that
returns the per-tier-filtered channel list passed in by the dispatcher.
Implements toMail / toOneSignal / toVonageWhatsApp / toVonageSms.
ShouldQueue on the 'notifications' queue.
- App\Notifications\Channels\OneSignalChannel — raw HTTP to OneSignal
REST API, gated on services.onesignal.{app_id,api_key} + user
push_token. Logs every call to api_logs via ApiLogger.
- App\Notifications\Channels\VonageWhatsAppChannel — raw HTTP to Vonage
Messages API, gated on whatsapp_verified_at + whatsapp_number.
- App\Notifications\Channels\VonageSmsChannel — raw HTTP to Vonage SMS
API, gated on whatsapp_number.
- DispatchUserNotificationJob now calls $user->notify(new
FuelPriceAlert(...)) before logging.
- New tests: assert the notification IS dispatched with the right
channels, and that nothing is dispatched when no channels are allowed.
Channels gracefully no-op when their credentials are unset (logging at
info level), so existing tests without a Notification::fake() still
pass — the channels just early-return on missing config.
No new composer dependencies — Vonage SDK avoided in favour of raw HTTP
through the existing ApiLogger pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `VerifyApiKey` middleware protecting all API routes with `X-Api-Key` header validation. Wraps `/api/stations`, `/api/stats/searches`, and `/api/prediction` in throttled middleware group (60 req/min). Updates StationSearchTest to use `RefreshDatabase`, adds `meta` assertion checks, and validates `fuel_type` in HTTP request assertions. Removes auth routes from API docs and replaces with API key authentication instructions. Adds `api_secret_key` config option.
- New method uses web_search_20260209 server-side tool so Claude fetches
48h of oil/geopolitical news autonomously before predicting direction
- Prompt uses raw prices only — no pre-computed EWMA indicators
- pause_turn loop handles server-side search continuation (up to 5 iters)
- generatePrediction() now tries context method first, falls back to
generateLlmPrediction(), then EWMA
- Default model updated to claude-sonnet-4-6
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>