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

@@ -80,20 +80,20 @@ export const api = {
body: JSON.stringify({ display_name: displayName }),
}),
// Auth
login: () => request<AuthResponse>("/api/auth/login", { method: "POST" }),
submitMFA: (code: string) =>
request<AuthResponse>("/api/auth/mfa", { method: "POST", body: JSON.stringify({ code }) }),
authStatus: () => request<AuthResponse>("/api/auth/status"),
// Garmin Auth
garminLogin: () => request<AuthResponse>("/api/garmin/auth/login", { method: "POST" }),
garminMFA: (code: string) =>
request<AuthResponse>("/api/garmin/auth/mfa", { method: "POST", body: JSON.stringify({ code }) }),
garminStatus: () => request<AuthResponse>("/api/garmin/auth/status"),
// Sync
syncRun: () => request<{ status: string }>("/api/sync/run", { method: "POST" }),
// Garmin Sync
garminSyncRun: () => request<{ status: string }>("/api/garmin/sync/run", { method: "POST" }),
// Deletes every synced activity (and its laps/samples/kind assignments)
// and rewinds the backfill watermark -- destructive, gated by a
// confirmation in the UI.
resetSync: () => request<{ status: string }>("/api/sync/reset", { method: "POST" }),
syncRuns: () => request<SyncRun[]>("/api/sync/runs"),
syncStatus: () => request<SyncStatus>("/api/sync/status"),
garminSyncReset: () => request<{ status: string }>("/api/garmin/sync/reset", { method: "POST" }),
garminSyncRuns: () => request<SyncRun[]>("/api/garmin/sync/runs"),
garminSyncStatus: () => request<SyncStatus>("/api/garmin/sync/status"),
// Training types -- fixed taxonomy, no create/delete
listWorkoutKinds: (includeInactive = false) =>

View File

@@ -18,9 +18,9 @@ export function GarminConnection({ onBeforeConnect }: { onBeforeConnect?: () =>
// it's open, so this no longer needs the "poll faster while syncing"
// dynamic interval it used to have.
useEffect(() => {
api.authStatus().then(setAuth).catch((e) => showError(String(e)));
api.garminStatus().then(setAuth).catch((e) => showError(String(e)));
const interval = setInterval(() => {
api.authStatus().then(setAuth).catch(() => {});
api.garminStatus().then(setAuth).catch(() => {});
}, 6000);
return () => clearInterval(interval);
}, []);
@@ -29,7 +29,7 @@ export function GarminConnection({ onBeforeConnect }: { onBeforeConnect?: () =>
setBusy(true);
try {
await onBeforeConnect?.();
const result = await api.login();
const result = await api.garminLogin();
setAuth(result);
setDisconnected(false);
if (result.status === "authenticated") showSuccess("Connected to Garmin successfully.");
@@ -48,7 +48,7 @@ export function GarminConnection({ onBeforeConnect }: { onBeforeConnect?: () =>
if (!code.trim()) return;
setBusy(true);
try {
const result = await api.submitMFA(code.trim());
const result = await api.garminMFA(code.trim());
setAuth(result);
if (result.status === "authenticated") showSuccess("Connected to Garmin successfully.");
} catch (e) {
@@ -61,7 +61,7 @@ export function GarminConnection({ onBeforeConnect }: { onBeforeConnect?: () =>
async function sync() {
setBusy(true);
try {
await api.syncRun();
await api.garminSyncRun();
setShowSyncModal(true);
} catch (e) {
showError(String(e));
@@ -76,14 +76,14 @@ export function GarminConnection({ onBeforeConnect }: { onBeforeConnect?: () =>
}
setBusy(true);
try {
await api.resetSync();
await api.garminSyncReset();
// Reset runs as a background sync job; wait for it to actually finish
// before reloading, otherwise other pages (e.g. Activities) would
// still show the just-deleted activities from their own stale state.
let status = await api.syncStatus();
let status = await api.garminSyncStatus();
while (status.in_progress) {
await new Promise((resolve) => setTimeout(resolve, 300));
status = await api.syncStatus();
status = await api.garminSyncStatus();
}
window.location.reload();
} catch (e) {

View File

@@ -61,7 +61,7 @@ export function SyncModal({ onClose }: { onClose: () => void }) {
let timeout: ReturnType<typeof setTimeout>;
const tick = () => {
api
.syncStatus()
.garminSyncStatus()
.then((s) => {
setStatus(s);
if (!s.in_progress) {