feat: rename segments + create routes from an activity (detail page & dashboard recent activities)
This commit is contained in:
@@ -90,12 +90,13 @@ docker compose -f docker-compose.deploy.yml up -d
|
||||
|
||||
- `main.py` — FastAPI app, DB init on startup (creates tables, seeds admin user, creates TimescaleDB hypertable)
|
||||
- `core/` — `config.py` (pydantic-settings from env), `database.py` (async engine for FastAPI + sync engine for Celery), `security.py` (JWT, bcrypt)
|
||||
- `api/` — routers: `auth`, `activities`, `routes`, `health`, `records`, `upload`, `profile`, `garmin_sync`, `users`, `segments`
|
||||
- `models/user.py` — all SQLAlchemy models: `User`, `Activity`, `ActivityDataPoint`, `ActivityLap`, `NamedRoute`, `Segment`, `SegmentEffort`, `PersonalRecord`, `HealthMetric`, `WeightLog`, `GarminConnectConfig` (the old `RouteSegment` model was removed in the segments rewrite; a new GPS-geometry `Segment`/`SegmentEffort` pair replaces it)
|
||||
- `services/fit_parser.py` — parses Garmin FIT and GPX files; handles raw FIT timestamps (FIT epoch offset 631065600s) and semicircle→degree conversion
|
||||
- `api/` — routers: `auth`, `activities`, `routes`, `health`, `records`, `upload`, `profile`, `garmin_sync`, `strava_sync`, `users`, `segments`
|
||||
- `models/user.py` — all SQLAlchemy models: `User`, `Activity`, `ActivityDataPoint`, `ActivityLap`, `NamedRoute`, `Segment`, `SegmentEffort`, `PersonalRecord`, `HealthMetric`, `WeightLog`, `GarminConnectConfig`, `StravaConfig` (the old `RouteSegment` model was removed in the segments rewrite; a new GPS-geometry `Segment`/`SegmentEffort` pair replaces it)
|
||||
- `services/fit_parser.py` — parses Garmin FIT, GPX, and Strava `.tcx` files; handles raw FIT timestamps (FIT epoch offset 631065600s) and semicircle→degree conversion
|
||||
- `services/wellness_parser.py` — parses Garmin wellness FIT files (metrics, sleep, HRV, SPO2, etc.)
|
||||
- `services/route_matcher.py` — bounding-box pre-filter + DTW (Dynamic Time Warping) for GPS track similarity
|
||||
- `services/garmin_connect_sync.py` — Garmin Connect API integration; `authenticate_garmin()` tries stored OAuth token first, falls back to email/password; Garmin credentials stored Fernet-encrypted using `SECRET_KEY` as the key
|
||||
- `services/strava_sync.py` — Strava API OAuth live sync (pulls activities via streams) and bulk-export import; a shared `persist_activity` path is used by both. On dedup, existing Garmin data is preferred over Strava for the same activity
|
||||
- `workers/tasks.py` — Celery tasks: `process_activity_file`, `parse_wellness_fit`, `detect_route`, `compute_personal_records`, `match_segment`, `match_activity_segments`, `process_garmin_health_zip`, `sync_garmin_connect_user`, `sync_all_garmin_connect` (beat-scheduled), `recalculate_hr_zones_for_user`, `backfill_moving_time`, `backfill_indoor_distances`, `recompute_personal_records_all`
|
||||
|
||||
### Key design decisions
|
||||
@@ -110,6 +111,8 @@ docker compose -f docker-compose.deploy.yml up -d
|
||||
|
||||
**PocketID OIDC**: Optional passkey auth. Config is read from the admin user's DB record first, falling back to env vars. The OAuth callback redirects to `/?token=<jwt>` and `useAuth.js` extracts the token from the URL at module load time.
|
||||
|
||||
**Personal records source filter**: Personal records are computed only from watch-recorded FIT activities; phone/Strava GPX/TCX imports are excluded because their GPS can "teleport" and produce bogus fast splits. Keep this filter in mind when touching `compute_personal_records` / `recompute_personal_records_all`.
|
||||
|
||||
### Frontend (`frontend/src/`)
|
||||
|
||||
- `App.jsx` — React Router v6, `RequireAuth` wrapper, all routes defined here
|
||||
@@ -121,7 +124,7 @@ docker compose -f docker-compose.deploy.yml up -d
|
||||
- TanStack Query (`@tanstack/react-query`) handles all server-state fetching and caching; Zustand is used only for auth, sync, and unit-preference state
|
||||
- `utils/format.js` — shared formatting helpers: `formatDuration`, `formatPace`, `formatDistance`, `formatCadence`, `hrZoneColor`, `sportIcon`, `sportColor`, etc.
|
||||
- `utils/track.js` — projects a lat/lng onto a GPS track (interpolated along-line snapping, used for map hover and segment selection); `utils/bodyBattery.js` — shared Body Battery colour/state helpers used by both the Health page and Dashboard mini chart
|
||||
- `pages/` — one `*Page.jsx` file per route: `Dashboard` (drag-to-edit widget grid), `Activities`, `ActivityDetail`, `Routes`, `Records`, `Health`, `Upload`, `Profile`, `Users`, `Login`
|
||||
- `pages/` — one `*Page.jsx` file per route: `Dashboard` (drag-to-edit widget grid), `Activities` (type/year/date-range/distance filters + week totals), `ActivityDetail`, `Routes`, `Records`, `Health`, `Summary` (all-time and per-year/per-sport totals + distance-per-year chart), `Upload`, `Profile`, `Users`, `Login`
|
||||
- `components/activity/` — `ActivityMap` (Leaflet), `MetricTimeline` (Recharts), `HRZoneBar`, `LapTable`, `SegmentsPanel` (per-activity segment efforts), `RouteLeaderboard` (top-10 by pace for a named route)
|
||||
- `components/health/` — `SleepHypnogram` (renders the `sleep_stages` hypnogram)
|
||||
- `components/ui/` — `Layout` (nav shell), `StatCard`, `RouteMiniMap` (small Leaflet map used in route/segment cards), `UnitToggle` (km/mi switch), `HrvBadge`
|
||||
@@ -173,6 +176,7 @@ Required in `.env` (or passed to Docker Compose):
|
||||
| `GARMIN_SYNC_INTERVAL_MINUTES` | How often the beat scheduler polls Garmin Connect (default: `30`) |
|
||||
| `POCKETID_ISSUER` / `POCKETID_CLIENT_ID` / `POCKETID_CLIENT_SECRET` | Optional OIDC |
|
||||
| `POCKETID_ALLOWED_GROUP` | Optional — restrict passkey login to a specific PocketID group |
|
||||
| `STRAVA_CLIENT_ID` / `STRAVA_CLIENT_SECRET` | Optional — enables Strava API OAuth live sync (register an app at strava.com/settings/api) |
|
||||
|
||||
## milevault_export/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user