diff --git a/src/lib/api.js b/src/lib/api.js index 721e619..d4e3ede 100644 --- a/src/lib/api.js +++ b/src/lib/api.js @@ -1,5 +1,5 @@ -export async function fetchStations() { - const res = await fetch('/api/campagne_map_data'); +export async function fetchStations(fetch = globalThis.fetch) { + const res = await fetch('http://37100lab.it:8101/api/campagne_map_data'); if (!res.ok) throw new Error(`Errore HTTP ${res.status}`); const geojson = await res.json(); return geojson.features @@ -7,14 +7,17 @@ export async function fetchStations() { .sort((a, b) => b.pk - a.pk); } -export async function fetchDay(id, date, signal) { +export async function fetchDay(id, day, signal) { + // Converte YYYY-M-D (da formatDayParam) in YYYY-MM-DD per la chiamata API + const [y, m, d] = day.split('-'); + const date = `${y}-${m.padStart(2, '0')}-${d.padStart(2, '0')}`; + // L'API risponde con 5xx transienti: tre tentativi limitano i buchi nel dataset scaricato. for (let attempt = 0; attempt < 3; attempt++) { try { - const res = await fetch(`/api/get_measurements/${id}?day=${date}`, { signal }); + const res = await fetch(`/api/get_station_data?id=${id}&date=${date}`, { signal }); if (!res.ok) throw new Error(`HTTP ${res.status}`); - const data = await res.json(); - return data.misure ?? []; + return await res.json(); } catch (e) { // La cancellazione non è un errore: uscire subito senza consumare altri retry. if (e.name === 'AbortError') return []; diff --git a/src/lib/components/StationRow.svelte b/src/lib/components/StationRow.svelte index fb64a1b..345a50e 100644 --- a/src/lib/components/StationRow.svelte +++ b/src/lib/components/StationRow.svelte @@ -3,7 +3,6 @@ import { getDayRange, formatDayParam } from '$lib/timezone.js'; import { fetchDay } from '$lib/api.js'; import { downloadCsv, buildFilename } from '$lib/csv.js'; - import { buildCsvAsync } from '$lib/csv-pool.js'; import { enqueue } from '$lib/download-pool.js'; export let station; @@ -31,6 +30,7 @@ $: pct = totalDays > 0 ? (currentDay / totalDays) * 100 : 0; async function startDownload() { + const { buildCsvAsync } = await import('$lib/csv-pool.js'); // Un controller per sessione: abort() cancella tutte le fetch di questa centralina // senza interferire con i download paralleli di altre stazioni. controller = new AbortController(); diff --git a/src/routes/+page.js b/src/routes/+page.js deleted file mode 100644 index a3d1578..0000000 --- a/src/routes/+page.js +++ /dev/null @@ -1 +0,0 @@ -export const ssr = false; diff --git a/src/routes/+page.server.js b/src/routes/+page.server.js new file mode 100644 index 0000000..7fefc03 --- /dev/null +++ b/src/routes/+page.server.js @@ -0,0 +1,9 @@ +import { fetchStations } from '$lib/api.js'; + +export async function load({ fetch }) { + try { + return { stations: await fetchStations(fetch) }; + } catch { + return { stations: [], loadError: 'Impossibile caricare le centraline. Controlla la connessione e riprova.' }; + } +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 6397f0e..3297c6a 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,21 +1,7 @@
@@ -25,12 +11,10 @@
- {#if loading} -

Caricamento…

- {:else if error} -

{error}

+ {#if data.loadError} +

{data.loadError}

{:else} - + {/if}
diff --git a/src/routes/api/[...path]/+server.js b/src/routes/api/[...path]/+server.js deleted file mode 100644 index 518adc1..0000000 --- a/src/routes/api/[...path]/+server.js +++ /dev/null @@ -1,9 +0,0 @@ -export async function GET({ params, url }) { - const res = await fetch(`http://37100lab.it:8101/api/${params.path}${url.search}`); - return new Response(res.body, { - status: res.status, - headers: { - 'content-type': res.headers.get('content-type') ?? 'application/json', - }, - }); -} diff --git a/src/routes/api/get_station_data/+server.js b/src/routes/api/get_station_data/+server.js new file mode 100644 index 0000000..9feb675 --- /dev/null +++ b/src/routes/api/get_station_data/+server.js @@ -0,0 +1,37 @@ +import { dev } from '$app/environment'; + +function cacheControl(date) { + if (dev) return 'no-store'; + const toIso = d => new Intl.DateTimeFormat('en-CA', { timeZone: 'Europe/Rome' }).format(d); + const yesterday = toIso(new Date(Date.now() - 86_400_000)); + return date < yesterday ? 'public, max-age=604800' : 'public, max-age=60'; +} + +export async function GET({ url }) { + const id = url.searchParams.get('id'); + const date = url.searchParams.get('date'); // YYYY-MM-DD + + // Il backend vuole YYYY-M-D senza zero-padding + const [y, m, d] = date.split('-'); + const day = `${parseInt(y)}-${parseInt(m)}-${parseInt(d)}`; + + const res = await fetch(`http://37100lab.it:8101/api/get_measurements/${id}?day=${day}`); + + if (!res.ok) { + return new Response('[]', { status: res.status, headers: { 'content-type': 'application/json' } }); + } + + const text = await res.text(); + // Il backend Python emette NaN/Infinity per valori mancanti o fuori range, + // che non sono JSON valido e farebbero esplodere JSON.parse. + const sanitized = text.replace(/(?<=[:\s\[,])(?:nan|inf|-inf|infinity|-infinity)(?=[,\]\s}])/gi, 'null'); + const misure = (JSON.parse(sanitized).misure ?? []) + .filter(m => m != null && Number.isFinite(new Date(m.time).getTime())); + + return new Response(JSON.stringify(misure), { + headers: { + 'content-type': 'application/json', + 'cache-control': cacheControl(date), + }, + }); +}