refactor: merge internal/sync into internal/garmin, regroup api files and routes

Garmin auth/sync routes move under /api/garmin/*; sync.Service becomes
garmin.Sync with garmin.SyncConfig/ClientConfig; applog becomes
internal/log; the test mock moves into the garmin package as MockClient
(breaking the test-only import cycle the merge created); stale test
URLs and type names updated to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 16:04:18 +02:00
parent 19c9aecdeb
commit e2b2bf9611
61 changed files with 2007 additions and 982 deletions

View File

@@ -4,7 +4,7 @@
**Goal:** Make connecting a Garmin account a mandatory, persisted gate during onboarding (and on every subsequent login until it succeeds), instead of an optional step left to the Profile page.
**Architecture:** Add a nullable `garmin_connected_at` timestamp to `profile`, set once on the first successful Garmin authentication (`handleAuthLogin`/`handleAuthMFA`). Expose it via `GET /api/session/me`. `LoginGate.tsx` becomes a three-way fork (`!has_profile``CreateProfile`, `has_profile && !garmin_connected` → new `ConnectGarmin`, else → `App`), so a user who abandons the connect step re-enters at `ConnectGarmin` on their next login rather than skipping straight into the app.
**Architecture:** Add a nullable `garmin_connected_at` timestamp to `profile`, set once on the first successful Garmin authentication (`handleGarminAuthLogin`/`handleGarminAuthMFA`). Expose it via `GET /api/session/me`. `LoginGate.tsx` becomes a three-way fork (`!has_profile``CreateProfile`, `has_profile && !garmin_connected` → new `ConnectGarmin`, else → `App`), so a user who abandons the connect step re-enters at `ConnectGarmin` on their next login rather than skipping straight into the app.
**Tech Stack:** Go (`database/sql` via `modernc.org/sqlite`, chi router), React + TypeScript (Vite), no frontend test framework.
@@ -246,7 +246,7 @@ git commit -m "feat(store): persist garmin_connected_at, set once on first succe
### Task 2: Wire `MarkGarminConnected` into the auth handlers and expose it via session/me
**Files:**
- Modify: `backend/internal/api/auth.go` (`recordAuthResult`, both call sites)
- Modify: `../../../backend/internal/api/garmin.go` (`recordAuthResult`, both call sites)
- Modify: `backend/internal/api/session.go` (`sessionMeResponse`, `handleSessionMe`)
- Modify: `backend/internal/api/api_test.go` (new tests)
- Modify: `backend/internal/api/isolation_test.go` (new adversarial test)
@@ -342,7 +342,7 @@ func TestIsolation_GarminConnectedNeverLeaksAcrossUsers(t *testing.T) {
Run: `cd backend && go test ./internal/api/... -run 'TestSessionMe_ReportsGarminConnected|TestSessionMe_GarminConnectedStaysFalse|TestIsolation_GarminConnected' -v`
Expected: FAIL — `sessionMeResponse` has no field `GarminConnected`.
- [ ] **Step 3: Update `recordAuthResult` in `auth.go`**
- [ ] **Step 3: Update `recordAuthResult` in `garmin.go`**
Change the imports:
@@ -379,8 +379,8 @@ func (s *Server) recordAuthResult(ctx context.Context, userID int64, res garmin.
}
```
In `handleAuthLogin`, change `s.recordAuthResult(userID, res)` to `s.recordAuthResult(r.Context(), userID, res)`.
In `handleAuthMFA`, change `s.recordAuthResult(userID, res)` to `s.recordAuthResult(r.Context(), userID, res)`.
In `handleGarminAuthLogin`, change `s.recordAuthResult(userID, res)` to `s.recordAuthResult(r.Context(), userID, res)`.
In `handleGarminAuthMFA`, change `s.recordAuthResult(userID, res)` to `s.recordAuthResult(r.Context(), userID, res)`.
- [ ] **Step 4: Update `sessionMeResponse` and `handleSessionMe` in `session.go`**
@@ -446,7 +446,7 @@ Expected: `gofmt -l .` empty; everything else passes.
- [ ] **Step 7: Commit**
```bash
git add backend/internal/api/auth.go backend/internal/api/session.go backend/internal/api/api_test.go backend/internal/api/isolation_test.go
git add backend/internal/api/garmin.go backend/internal/api/session.go backend/internal/api/api_test.go backend/internal/api/isolation_test.go
git commit -m "feat(api): persist and expose garmin_connected on successful auth"
```