mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-07 11:54:59 -04:00
140 lines
4.0 KiB
Markdown
140 lines
4.0 KiB
Markdown
|
|
# Phase 4 Local-First Web API
|
||
|
|
|
||
|
|
## Purpose
|
||
|
|
|
||
|
|
Phase 4 makes the Web API start behaving like a local-first product. Phases 2
|
||
|
|
and 3 created the domain store and explicit ingestion path; this phase adds
|
||
|
|
read models that prefer local SQLite data and report data availability honestly.
|
||
|
|
|
||
|
|
This is still a backend phase. Do not start React yet.
|
||
|
|
|
||
|
|
## Manager Decision
|
||
|
|
|
||
|
|
Build one credible local-first Race Hub API slice before replacing the frontend.
|
||
|
|
The current Web UI can keep working from the existing endpoints, but the backend
|
||
|
|
should expose store-backed responses that a future React Race Hub can trust.
|
||
|
|
|
||
|
|
Phase 4 should add:
|
||
|
|
|
||
|
|
- store-backed read models for ingested meetings, sessions, drivers, results,
|
||
|
|
and grid;
|
||
|
|
- dataset/status metadata so the UI knows what is local, missing, or stale;
|
||
|
|
- optional small API fallbacks only when explicitly requested;
|
||
|
|
- tests for local-first behavior without network.
|
||
|
|
|
||
|
|
## Package Boundary
|
||
|
|
|
||
|
|
Prefer adding a backend read-model layer instead of embedding SQL inside HTTP
|
||
|
|
handlers.
|
||
|
|
|
||
|
|
Recommended shape:
|
||
|
|
|
||
|
|
```text
|
||
|
|
internal/query/
|
||
|
|
racehub.go Race Hub read model assembly
|
||
|
|
metadata.go dataset availability/source metadata
|
||
|
|
query_test.go temp-db tests
|
||
|
|
```
|
||
|
|
|
||
|
|
Then wire `internal/web` to use that layer.
|
||
|
|
|
||
|
|
If the implementation keeps the read layer inside `internal/web` temporarily,
|
||
|
|
it must still avoid duplicating store SQL across handlers.
|
||
|
|
|
||
|
|
## Initial API Scope
|
||
|
|
|
||
|
|
Add a new Race Hub endpoint:
|
||
|
|
|
||
|
|
```text
|
||
|
|
GET /api/v1/race-hub?session_key=9472
|
||
|
|
```
|
||
|
|
|
||
|
|
Response should include:
|
||
|
|
|
||
|
|
- meeting;
|
||
|
|
- session;
|
||
|
|
- drivers;
|
||
|
|
- session results enriched with driver/team fields;
|
||
|
|
- starting grid enriched with driver/team fields;
|
||
|
|
- dataset availability metadata.
|
||
|
|
|
||
|
|
Recommended metadata shape:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"source": "local",
|
||
|
|
"session_key": 9472,
|
||
|
|
"datasets": {
|
||
|
|
"meeting": {"status": "available", "source": "local"},
|
||
|
|
"session": {"status": "available", "source": "local"},
|
||
|
|
"drivers": {"status": "available", "source": "local", "count": 20},
|
||
|
|
"results": {"status": "missing", "source": "none", "count": 0},
|
||
|
|
"starting_grid": {"status": "available", "source": "local", "count": 20}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Exact field names can vary, but the response must make missing datasets visible
|
||
|
|
instead of silently returning empty app states.
|
||
|
|
|
||
|
|
## Existing Endpoint Policy
|
||
|
|
|
||
|
|
Do not rewrite every existing endpoint yet. It is enough to:
|
||
|
|
|
||
|
|
- add the new local-first Race Hub endpoint;
|
||
|
|
- optionally make `/api/v1/meetings`, `/api/v1/sessions`, `/api/v1/drivers`,
|
||
|
|
`/api/v1/results`, and `/api/v1/grid` read from local data when present;
|
||
|
|
- preserve old OpenF1 behavior when local data is absent unless the request asks
|
||
|
|
for local-only behavior.
|
||
|
|
|
||
|
|
Recommended query controls:
|
||
|
|
|
||
|
|
```text
|
||
|
|
?source=local local only; no OpenF1 fallback
|
||
|
|
?source=auto local first, existing OpenF1 fallback when missing
|
||
|
|
```
|
||
|
|
|
||
|
|
Default should be conservative for existing endpoints. The new Race Hub endpoint
|
||
|
|
can default to local-first with honest missing metadata.
|
||
|
|
|
||
|
|
## Server Wiring
|
||
|
|
|
||
|
|
`web.Server` currently only receives `*api.OpenF1Client`. Add an optional
|
||
|
|
`*store.Store` or query service so Web mode can read the domain DB.
|
||
|
|
|
||
|
|
CLI/server behavior should remain simple:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
go run cmd/main.go --web
|
||
|
|
go run cmd/main.go --web --db /path/to/boxbox.db
|
||
|
|
```
|
||
|
|
|
||
|
|
If the DB does not exist or has no ingested data, Web mode should still start.
|
||
|
|
|
||
|
|
## Non-Goals
|
||
|
|
|
||
|
|
Do not include these in Phase 4:
|
||
|
|
|
||
|
|
- React/Vite frontend setup.
|
||
|
|
- replacing the current static Web UI;
|
||
|
|
- automatic ingestion from Web browsing;
|
||
|
|
- live SignalR persistence;
|
||
|
|
- laps/stints/pits/weather/race-control read models unless the store schema is
|
||
|
|
expanded and tested first.
|
||
|
|
|
||
|
|
## Acceptance Criteria
|
||
|
|
|
||
|
|
Phase 4 is complete when:
|
||
|
|
|
||
|
|
- a local-first Race Hub endpoint exists;
|
||
|
|
- it can return ingested session data without OpenF1 calls;
|
||
|
|
- it reports missing datasets explicitly;
|
||
|
|
- Web mode can be pointed at a domain DB with `--db`;
|
||
|
|
- offline tests cover the read model and HTTP handler behavior;
|
||
|
|
- focused tests and build pass.
|
||
|
|
|
||
|
|
## Next Phase After This
|
||
|
|
|
||
|
|
Phase 5 is the first frontend implementation phase. That is the point to switch
|
||
|
|
from Cursor to Claude for React/UI work.
|