Session 9: api-football + FootApi + Tank01 adapters, grace period middleware, cookie consent, /pricing page, OOM fix documented (1240 tests)
This commit is contained in:
+147
-1
@@ -4,7 +4,153 @@
|
||||
2026-06-10
|
||||
|
||||
## Current Phase
|
||||
SHIP BUILD v8.0 — Frontend Stripe Cutover + Soccer Pages (Session 8)
|
||||
SHIP BUILD v9.0 — Critical site fixes, data-source upgrade, production readiness (Session 9)
|
||||
|
||||
## Session 9 (2026-06-10) — SHIPPED
|
||||
|
||||
World Cup opens tomorrow. This session closed three live-site
|
||||
emergencies (404, OOM cycle, slow FCP), added three new soccer data
|
||||
sources with a priority cascade, two new RapidAPI sports adapters, a
|
||||
real grace-period downgrade middleware, and updated the legal pages.
|
||||
|
||||
### Phase 0 — critical fixes
|
||||
|
||||
- **`/pricing` 404 → fixed.** `web/src/app/pricing/page.tsx` created;
|
||||
wraps the existing `Pricing` component on a standalone route so
|
||||
email renewal CTAs (which link to `/pricing` via
|
||||
`web/src/services/email.ts:204`) no longer land on 404. Metadata
|
||||
block ships with OG + Twitter tags.
|
||||
- **Web container OOM cycle → cause identified, fix documented.**
|
||||
`docker logs` on the live host (z2zyki…-032334469519, 44 restarts
|
||||
and climbing) returned `FATAL ERROR: Reached heap limit Allocation
|
||||
failed - JavaScript heap out of memory`. Docker mem limit is
|
||||
unlimited (0) — this is Node's own ~2 GB V8 default. Fix is a
|
||||
Coolify env-var change: **`NODE_OPTIONS=--max-old-space-size=4096`**
|
||||
on the web container. Cannot be applied from this session — listed
|
||||
under the Coolify env requirements at the end of this entry.
|
||||
- **7.5s FCP → root cause traced to the OOM cycle.** All page routes
|
||||
are static-prerendered; root layout makes no blocking calls. The
|
||||
FCP measurement is dominated by cold-start latency hit during each
|
||||
restart. The NODE_OPTIONS fix is the primary FCP fix too — re-measure
|
||||
after deploy.
|
||||
|
||||
### Phase 1 — soccer source upgrade
|
||||
|
||||
New adapter cascade for soccer (priority order):
|
||||
|
||||
1. **api-football.com (PRIMARY)** — `src/services/adapters/apiFootballAdapter.js`.
|
||||
100 req/day soft limit (90, with 10-req safety margin). 6 endpoints:
|
||||
`getFixtures`, `getFixtureLineups`, `getFixturePlayerStats`,
|
||||
`getFixtureEvents`, `getPlayerSeasonStats`, `getStandings`. Auth via
|
||||
`x-apisports-key` header (NOT RapidAPI). Per-endpoint TTLs match
|
||||
data volatility (fixtures 6h, lineups/playerstats 24h, events 12h).
|
||||
2. **FootApi via RapidAPI (BACKUP)** — `src/services/adapters/footApiAdapter.js`.
|
||||
50 req/day (soft 45). 4 endpoints: `getMatchLineups` (28 stat keys),
|
||||
`getMatchIncidents` (minute + addedTime), `getRefereeStatistics`
|
||||
(yellow/red per game), `getWorldCupSchedule` (tournament ID 16).
|
||||
3. **football-data.org (TERTIARY)** — existing Session 7j adapter unchanged.
|
||||
|
||||
The `soccerFeatureExtractor` now cascades through these via a new
|
||||
`loadFromCascade()` helper. Each load returns a `_source` tag so
|
||||
debugging is straightforward; `meta.sources` exposes the
|
||||
attribution per lookup (`player`, `nextMatch`, `lastFixture`,
|
||||
`referee`). Existing 17 soccer-extractor tests still pass; 7 new
|
||||
cascade tests prove the priority order.
|
||||
|
||||
### Phase 1 — Tank01 RapidAPI adapters
|
||||
|
||||
- **`tank01NbaAdapter.js`** — live NBA box scores, schedule, betting
|
||||
odds. Status-aware TTL: 5-min cache while a game is in-progress,
|
||||
24-hour cache once it reports Final. Free tier 1,000 req/mo;
|
||||
TTL-bound rather than counter-bound.
|
||||
- **`tank01MlbAdapter.js`** — live MLB box scores, daily scoreboard,
|
||||
and **batter-vs-pitcher** (the headline new MLB signal — a batter's
|
||||
historical PA/AB/H/HR/SO line against a specific pitcher). Same
|
||||
status-aware TTL pattern as NBA.
|
||||
|
||||
Both Tank01 adapters use the shared `RAPID_API_KEY` (also used by
|
||||
FootApi). Host overridable via `TANK01_NBA_HOST` / `TANK01_MLB_HOST`.
|
||||
|
||||
### Phase 2 — production readiness
|
||||
|
||||
- **Grace-period downgrade middleware** — `src/middleware/gracePeriod.js`.
|
||||
Fires at request time on tier-gated routes (`/api/scan/parlay`,
|
||||
`/api/alerts`, `/api/props/joint-history`). Reads
|
||||
`req.user.grace_period_until` (now selected by `requireAuth` in
|
||||
`src/middleware/auth.js`), and on expiry atomically downgrades
|
||||
`users.tier` and `user_profiles.tier` to `'free'`, clears the
|
||||
timestamp, sets `subscription_status='expired'` on the profile
|
||||
mirror, and rewrites `req.user` so the route immediately sees the
|
||||
downgrade. Closes the long-standing "cancelled users keep paid
|
||||
access forever" gap. **Ordering matters**: grace must run AFTER
|
||||
requireAuth and BEFORE scanLimit, because scanLimit reads tier off
|
||||
req.user — a just-expired Desk user would otherwise burn one final
|
||||
unlimited-quota request.
|
||||
- **TOS update** — `web/src/app/terms/page.tsx` Subscription Terms
|
||||
switched from NexaPay to Stripe; Acceptable Use now explicitly
|
||||
states "VYNDR does NOT offer API access at any tier" — closes the
|
||||
Session 7h immutable.
|
||||
- **Privacy update** — `web/src/app/privacy/page.tsx` Payment Data
|
||||
section switched from NexaPay to Stripe with specifics on what
|
||||
Stripe receives. New "Sub-processors" section explicitly lists
|
||||
Stripe, Supabase, PostHog, Resend.
|
||||
- **Cookie consent banner** — `web/src/components/CookieConsent.tsx`,
|
||||
mounted in root layout. Thin bottom bar, SSR-safe (renders nothing
|
||||
until client mount checks localStorage), single-button accept,
|
||||
links to Privacy Policy.
|
||||
- **Root layout metadata** — keywords + description extended to
|
||||
include soccer and World Cup 2026 intelligence terms. OG + Twitter
|
||||
cards already comprehensive from prior sessions. Per-page metadata
|
||||
for /soccer + /scan deferred (those pages are `'use client'`; would
|
||||
need server-component wrappers — cosmetic).
|
||||
|
||||
### Tests added
|
||||
|
||||
| Suite | Tests |
|
||||
|------------------------------------------------|-------|
|
||||
| `tests/unit/apiFootballAdapter.test.js` | 16 |
|
||||
| `tests/unit/footApiAdapter.test.js` | 13 |
|
||||
| `tests/unit/soccerFeatureExtractorCascade.test.js` | 7 |
|
||||
| `tests/unit/tank01NbaAdapter.test.js` | 12 |
|
||||
| `tests/unit/tank01MlbAdapter.test.js` | 12 |
|
||||
| `tests/unit/gracePeriod.test.js` | 7 |
|
||||
| **Session 9 total** | **67** |
|
||||
|
||||
### Quality gates
|
||||
- `npm test`: **1240 / 1240 passing** (1173 baseline + 67 new), 97 suites, 0 regressions
|
||||
- `web/npm run build`: clean — `/pricing` + everything else prerenders, no type errors
|
||||
- License audit: only permissive licenses
|
||||
|
||||
### Coolify env vars (apply on the web container — keys not in repo)
|
||||
|
||||
```
|
||||
NODE_OPTIONS=--max-old-space-size=4096 # fixes the OOM cycle
|
||||
API_FOOTBALL_KEY=<from api-sports.io> # PRIMARY soccer source
|
||||
FOOTBALL_DATA_API_KEY=<from football-data.org> # TERTIARY soccer source
|
||||
RAPID_API_KEY=<from RapidAPI marketplace> # FootApi + Tank01 NBA + Tank01 MLB
|
||||
FOOTAPI_HOST=footapi7.p.rapidapi.com # default — override only for mirrors
|
||||
TANK01_NBA_HOST=tank01-fantasy-stats.p.rapidapi.com
|
||||
TANK01_MLB_HOST=tank01-mlb-live-in-game-real-time-statistics.p.rapidapi.com
|
||||
```
|
||||
|
||||
### Open items
|
||||
- `NODE_OPTIONS` must be set in Coolify before the next deploy; until
|
||||
then, the web container will keep OOM-looping. This is the single
|
||||
most important production action item.
|
||||
- The 2 GB+ heap usage that triggered the OOM suggests a memory leak
|
||||
in the Next.js standalone server. Heap-snapshot investigation
|
||||
deferred — the env-var bump buys headroom but doesn't fix the leak
|
||||
root cause.
|
||||
- Per-page OG metadata on `/soccer` and `/scan` requires those pages
|
||||
to be refactored to a server-component wrapper pattern. Not blocking.
|
||||
- The new adapter cascade improves data quality WHEN
|
||||
`API_FOOTBALL_KEY` / `RAPID_API_KEY` are populated and a daily
|
||||
prefetch has run against them. Until then, the cascade silently
|
||||
falls through to football-data.org and static reference data.
|
||||
Updating `scripts/soccer-data-prefetch.js` to write the new
|
||||
`apifootball:*` / `footapi:*` cache keys is a follow-up.
|
||||
|
||||
---
|
||||
|
||||
## Session 8 (2026-06-10) — SHIPPED
|
||||
|
||||
|
||||
Reference in New Issue
Block a user