fix: fallback to direct file fetch in ActivityDetailLoader
If the index-based lookup fails (shard fetch silently failed, stale index state, etc.), try fetching the activity detail file directly from each user shard's _merged/activities/ directory. This makes private activities and newly-synced activities accessible even when the index resolution fails. Also add console.error logging when shards fail in resolveShards to help diagnose root causes.
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { loadIndex } from '../lib/dataloader';
|
import { loadIndex } from '../lib/dataloader';
|
||||||
import ActivityDetail from './ActivityDetail.svelte';
|
import ActivityDetail from './ActivityDetail.svelte';
|
||||||
import type { ActivitySummary } from '../lib/types';
|
import type { ActivitySummary, BASIndex } from '../lib/types';
|
||||||
|
|
||||||
export let base: string = '/';
|
export let base: string = '/';
|
||||||
|
|
||||||
@@ -10,6 +10,56 @@
|
|||||||
let notFound = false;
|
let notFound = false;
|
||||||
let loading = true;
|
let loading = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fallback: if the activity isn't found in the loaded index (e.g. the user's
|
||||||
|
* shard fetch failed silently), try to fetch the detail file directly from
|
||||||
|
* each known user shard's _merged/activities/ directory.
|
||||||
|
*/
|
||||||
|
async function fetchActivityDirect(id: string): Promise<ActivitySummary | null> {
|
||||||
|
try {
|
||||||
|
const r = await fetch(`${base}data/index.json`);
|
||||||
|
if (!r.ok) return null;
|
||||||
|
const root: BASIndex = await r.json();
|
||||||
|
for (const shard of (root.shards ?? [])) {
|
||||||
|
if (!shard.handle) continue;
|
||||||
|
const url = `${base}data/${shard.handle}/_merged/activities/${id}.json`;
|
||||||
|
try {
|
||||||
|
const dr = await fetch(url);
|
||||||
|
if (!dr.ok) continue;
|
||||||
|
const d = await dr.json();
|
||||||
|
if (d.id !== id) continue;
|
||||||
|
return {
|
||||||
|
id: d.id,
|
||||||
|
title: d.title ?? id,
|
||||||
|
sport: d.sport ?? 'other',
|
||||||
|
sub_sport: d.sub_sport ?? null,
|
||||||
|
started_at: d.started_at ?? '',
|
||||||
|
distance_m: d.distance_m ?? null,
|
||||||
|
duration_s: d.duration_s ?? null,
|
||||||
|
moving_time_s: d.moving_time_s ?? null,
|
||||||
|
elevation_gain_m: d.elevation_gain_m ?? null,
|
||||||
|
avg_speed_kmh: d.avg_speed_kmh ?? null,
|
||||||
|
max_speed_kmh: d.max_speed_kmh ?? null,
|
||||||
|
avg_hr_bpm: d.avg_hr_bpm ?? null,
|
||||||
|
max_hr_bpm: d.max_hr_bpm ?? null,
|
||||||
|
avg_cadence_rpm: d.avg_cadence_rpm ?? null,
|
||||||
|
avg_power_w: d.avg_power_w ?? null,
|
||||||
|
mmp: d.mmp ?? null,
|
||||||
|
source: d.source ?? null,
|
||||||
|
privacy: d.privacy ?? 'public',
|
||||||
|
detail_url: `${shard.handle}/_merged/activities/${id}.json`,
|
||||||
|
track_url: d.bbox && d.privacy !== 'private' && d.privacy !== 'no_gps'
|
||||||
|
? `${shard.handle}/_merged/activities/${id}.geojson`
|
||||||
|
: null,
|
||||||
|
preview_coords: null,
|
||||||
|
handle: shard.handle,
|
||||||
|
};
|
||||||
|
} catch { /* try next shard */ }
|
||||||
|
}
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
// Extract activity ID from the URL path: /activity/{id}/
|
// Extract activity ID from the URL path: /activity/{id}/
|
||||||
const match = window.location.pathname.match(/\/activity\/([^/]+)/);
|
const match = window.location.pathname.match(/\/activity\/([^/]+)/);
|
||||||
@@ -19,6 +69,13 @@
|
|||||||
try {
|
try {
|
||||||
const index = await loadIndex(base);
|
const index = await loadIndex(base);
|
||||||
activity = index.activities.find(a => a.id === id) ?? null;
|
activity = index.activities.find(a => a.id === id) ?? null;
|
||||||
|
|
||||||
|
if (!activity) {
|
||||||
|
// Shard lookup failed — try fetching the detail file directly.
|
||||||
|
// This handles transient shard fetch errors, stale index state, etc.
|
||||||
|
activity = await fetchActivityDirect(id);
|
||||||
|
}
|
||||||
|
|
||||||
if (!activity) notFound = true;
|
if (!activity) notFound = true;
|
||||||
} catch {
|
} catch {
|
||||||
notFound = true;
|
notFound = true;
|
||||||
|
|||||||
@@ -99,6 +99,13 @@ async function resolveShards(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Log shard fetch failures to help diagnose missing-activity issues
|
||||||
|
shardResults.forEach((r, i) => {
|
||||||
|
if (r.status === 'rejected') {
|
||||||
|
console.error('[bincio] shard fetch failed:', index.shards[i]?.url, r.reason);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const own = index.activities ?? [];
|
const own = index.activities ?? [];
|
||||||
const fromShards = shardResults.flatMap(r => r.status === 'fulfilled' ? r.value : []);
|
const fromShards = shardResults.flatMap(r => r.status === 'fulfilled' ? r.value : []);
|
||||||
return [...own, ...fromShards];
|
return [...own, ...fromShards];
|
||||||
|
|||||||
Reference in New Issue
Block a user