Deepen three architectural seams: FIT consolidation, host election extraction, injectable db

- Extract shared fitToGpx into connected-services/fit.ts (FIT is a Garmin
  open standard used by Wahoo, Coros, Garmin — not provider-specific)
- Add importActivity() to sync/imports.server.ts so providers call one
  function instead of createActivity + recordImport separately; eliminates
  direct dependency on activities.server.ts from provider adapters
- Update wahoo importer + webhook to use both shared helpers
- Extract useHostElection(yjs) hook from useRouting so host election is
  independently testable without mounting the full routing stack
- Add setDb() to journal db.ts for module-level injection in unit tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-05-10 15:52:31 +02:00
parent 48d97be8f1
commit e387e1f798
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
8 changed files with 104 additions and 122 deletions

View file

@ -0,0 +1,38 @@
// Shared FIT file → GPX converter for provider importers.
//
// FIT is an open standard (Garmin/ANT+) used by Wahoo, Garmin, Coros, and
// others. This module is shared across all providers that produce FIT files
// so the conversion logic lives in one place.
import FitParser from "fit-file-parser";
import { generateGpx } from "@trails-cool/gpx";
export async function fitToGpx(buffer: Buffer, name: string): Promise<string | null> {
const parsed = await new Promise<Record<string, unknown>>((resolve, reject) => {
const parser = new FitParser({ force: true });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
parser.parse(buffer as any, (error: unknown, data: any) => {
if (error) reject(error);
else resolve(data ?? {});
});
});
const records = (parsed.records ?? []) as Array<{
position_lat?: number;
position_long?: number;
altitude?: number;
timestamp?: string | Date;
}>;
const trackPoints = records
.filter((r) => r.position_lat != null && r.position_long != null)
.map((r) => ({
lat: r.position_lat!,
lon: r.position_long!,
ele: r.altitude,
time: r.timestamp instanceof Date ? r.timestamp.toISOString() : r.timestamp,
}));
if (trackPoints.length < 2) return null;
return generateGpx({ name, tracks: [trackPoints] });
}