feat: gear registry — manage bikes/shoes per athlete, set per activity
- New /api/gear CRUD endpoints (gear.json per user) - Gear tab in AthleteView (owner-only): add, edit, retire items - EditDrawer gear field becomes a dropdown when registry has items - Strava API sync now resolves gear_id → name, adds to registry automatically - Strava ZIP import reads Gear column from activities.csv - POST /api/strava/import-gear for one-time backfill from stored originals
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
# Gear Feature Plan
|
||||
|
||||
## Why the gap exists
|
||||
|
||||
Neither sync path populates gear today. The Strava API returns `gear_id` per activity
|
||||
(brut's originals show `b3437566`, `g10422777` etc.) but `strava_to_parsed()` ignores it.
|
||||
The ZIP path also ignores the gear column in activities.csv.
|
||||
Diego_p's "Rose Backroad" was set manually via the EditDrawer free-text field.
|
||||
|
||||
---
|
||||
|
||||
## Data model — `{user_dir}/gear.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "uuid-abc123",
|
||||
"name": "Rose Backroad",
|
||||
"type": "bike",
|
||||
"retired": false,
|
||||
"strava_id": "b3437566"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `type` enum: `bike | shoes | skis | other`
|
||||
- Per-activity gear stays as a plain string (the gear **name**) — backward compatible with existing sidecars
|
||||
- `strava_id` is optional, used for deduplication during Strava sync
|
||||
|
||||
---
|
||||
|
||||
## Build order
|
||||
|
||||
### [x] Step 1 — `gear.json` CRUD API ✓
|
||||
File: `bincio/serve/routers/gear.py`
|
||||
- `GET /api/gear` → list items (auth required)
|
||||
- `POST /api/gear` → add item (auto-generate UUID id)
|
||||
- `PATCH /api/gear/{id}` → update (name, type, retired)
|
||||
- `DELETE /api/gear/{id}` → delete
|
||||
File lives at `{user_dir}/gear.json`, same pattern as `athlete.json`.
|
||||
Add gear router to `server.py`.
|
||||
|
||||
### [x] Step 2 — Gear tab in AthleteView (ownerOnly) ✓
|
||||
- Added `'gear'` to `Tab` type and `ALL_TABS` in `AthleteView.svelte`
|
||||
- Inline gear management: list, add, edit, retire — no separate component
|
||||
|
||||
### [x] Step 3 — EditDrawer gear selector ✓
|
||||
- At drawer open, fetches `/api/gear`
|
||||
- Shows `<select>` from registry (if items exist), with "Other…" revealing text input
|
||||
- Falls back to plain text input if no gear items registered
|
||||
- Value still stored as gear name string — backward compatible
|
||||
|
||||
### [x] Step 4 — Strava sync gear extraction ✓
|
||||
- `strava_api.py`: added `fetch_gear()` + `gear` field on `strava_to_parsed()` via `_gear_name` meta key
|
||||
- `ingest.py`: during sync, resolves gear_id → name, adds new items to registry
|
||||
- New endpoint `POST /api/strava/import-gear`: one-time backfill from stored originals
|
||||
|
||||
### [x] Step 5 — ZIP import gear column ✓
|
||||
- `strava_zip.py`: reads `Gear` column from activities.csv and sets `parsed.gear`
|
||||
|
||||
### [x] Step 6 — One-time backfill endpoint ✓
|
||||
`POST /api/strava/import-gear` implemented in `strava.py`.
|
||||
Reference in New Issue
Block a user