whisper-money/app
Víctor Falcón 4b90bcfc96
fix(currency): make rate fetching resilient to slow CDN (#502)
## What

Hardens `CurrencyConversionService` against a slow/unreachable external
FX-rate CDN, which was crashing `/api/cashflow/trend`.

## Sentry

- Fixes PHP-LARAVEL-2X — `RuntimeException: Failed to fetch currency
rates ... cURL error 28: Operation timed out`
- Fixes PHP-LARAVEL-31 — `FatalError: Maximum execution time of 30
seconds exceeded` (Guzzle CurlFactory)

## Root cause

For a historical date the service walked up to 8 lookback dates × 2 URLs
at a **10s timeout each** (~160s worst case → 30s PHP fatal), and a
single network timeout threw a `RuntimeException` that 500'd the whole
request.

## Changes

- **Bounded timeouts**: 3s connect / 5s total per request.
- **Abort on unreachable source**: a connection/timeout error stops the
date walk (after trying the fallback host) instead of repeating the same
timeout for every candidate date. 404s still walk back to earlier
releases.
- **Graceful degradation**: failures return an empty rate map instead of
throwing — conversions already fall back to `0.0` on missing rates, so
the trend endpoint no longer 500s.
- **Persistent cache**: historical releases cached 30d (immutable),
`latest` 6h, unavailable results 10m (dampens retries during an outage).

## Tests

- Updated the former "throws" test to assert graceful degradation to
`0.0`.
- Added: timeout aborts the date walk after 2 attempts; rates cache
across service instances.
- All 12 tests pass.
2026-06-08 09:10:38 +02:00
..
Actions feat: parent/child category tree (#474) 2026-06-03 19:30:12 +02:00
Console/Commands feat(banking): add command to disconnect connections by id (#497) 2026-06-06 11:16:01 +02:00
Contracts feat(open-banking): finalize bank connection without a session via state token (#498) 2026-06-06 12:12:07 +02:00
Enums feat: parent/child category tree (#474) 2026-06-03 19:30:12 +02:00
Events Add Budgeting Feature to Track and Manage Spending (#36) 2026-01-21 15:25:50 +01:00
Exceptions/Banking Handle transient EnableBanking sync failures (#358) 2026-05-06 09:24:05 +02:00
Features feat(transactions): save and reuse transaction filters (#496) 2026-06-05 18:00:14 +02:00
Http fix(transactions): prevent crash when sorting by nullable column (#501) 2026-06-06 17:23:21 +02:00
Jobs feat(settings): let users disable bank transactions email (#472) 2026-06-02 12:24:39 +02:00
Listeners feat(discord): enrich Stripe event messages and dedupe deliveries (#460) 2026-05-31 12:50:37 +02:00
Mail feat(leads): add user lead re-invite campaign (#432) 2026-05-26 08:35:31 +02:00
Models feat(open-banking): finalize bank connection without a session via state token (#498) 2026-06-06 12:12:07 +02:00
Notifications fix: verify email via signed link without requiring login (#490) 2026-06-05 10:01:32 +02:00
Policies refactor(policies): extract HandlesUserOwnership trait (#478) 2026-06-03 17:43:30 +02:00
Providers feat: add Discord admin feed for daily stats and Stripe events (#458) 2026-05-30 18:14:46 +02:00
Rules feat: parent/child category tree (#474) 2026-06-03 19:30:12 +02:00
Services fix(currency): make rate fetching resilient to slow CDN (#502) 2026-06-08 09:10:38 +02:00