import type { SQLiteDatabase } from 'expo-sqlite'; import { getSetting, upsertRemoteActivity } from './queries'; export type SyncResult = { synced: number; total: number; fetched?: number; error?: string }; export async function syncFeed(db: SQLiteDatabase): Promise { const instanceUrl = (await getSetting(db, 'instance_url'))?.replace(/\/$/, ''); const token = await getSetting(db, 'api_token'); if (!instanceUrl || !token) { return { synced: 0, total: 0, error: 'No instance configured — add one in Settings.' }; } let resp: Response; try { resp = await fetch(`${instanceUrl}/api/feed`, { headers: { Authorization: `Bearer ${token}` }, }); } catch { return { synced: 0, total: 0, error: 'Could not reach instance — check your connection.' }; } if (resp.status === 401) { return { synced: 0, total: 0, error: 'Session expired — reconnect in Settings.' }; } if (!resp.ok) { return { synced: 0, total: 0, error: `Server error (${resp.status})` }; } const data: { activities?: RemoteSummary[] } = await resp.json(); const activities = data.activities ?? []; const syncMode = (await getSetting(db, 'sync_mode')) ?? 'summaries'; let synced = 0; for (const a of activities) { const detailJson = JSON.stringify({ id: a.id, title: a.title ?? a.id, sport: a.sport ?? null, started_at: a.started_at ?? null, distance_m: a.distance_m ?? null, moving_time_s: a.moving_time_s ?? null, elevation_gain_m: a.elevation_gain_m ?? null, avg_speed_kmh: a.avg_speed_kmh ?? null, avg_hr_bpm: a.avg_hr_bpm ?? null, avg_power_w: a.avg_power_w ?? null, }); const changed = await upsertRemoteActivity(db, a.id, detailJson); if (changed) synced++; } if (syncMode !== 'full') { return { synced, total: activities.length }; } // Full mode: fetch geojson + timeseries for any activity missing them const headers = { Authorization: `Bearer ${token}` }; let fetched = 0; for (const a of activities) { const row = db.getFirstSync<{ g: number; t: number }>( 'SELECT (geojson IS NOT NULL) as g, (timeseries_json IS NOT NULL) as t FROM activities WHERE id = ?', [a.id], ); if (row?.g && row?.t) continue; let gj: string | null = null; let ts: string | null = null; try { if (!row?.g) { const r = await fetch(`${instanceUrl}/api/activity/${a.id}/geojson`, { headers }); if (r.ok) gj = await r.text(); } if (!row?.t) { const r = await fetch(`${instanceUrl}/api/activity/${a.id}/timeseries`, { headers }); if (r.ok) ts = await r.text(); } } catch {} if (gj !== null || ts !== null) { await db.runAsync( `UPDATE activities SET geojson = COALESCE(geojson, ?), timeseries_json = COALESCE(timeseries_json, ?) WHERE id = ? AND origin = 'remote'`, [gj, ts, a.id], ); fetched++; } } return { synced, total: activities.length, fetched }; } type RemoteSummary = { id: string; title?: string; sport?: string; started_at?: string; distance_m?: number | null; moving_time_s?: number | null; elevation_gain_m?: number | null; avg_speed_kmh?: number | null; avg_hr_bpm?: number | null; avg_power_w?: number | null; };