๐ŸŸ

GeoData Fishing

Piattaforma serverless per il tracciamento delle catture di pesca con arricchimento geo-meteorologico automatico da 7+ API pubbliche in tempo reale

โšก CLOUDFLARE WORKERS ยท D1 ยท R2 ยท HONO v4
๐Ÿ“‹ v2.0.0 ๐Ÿ‘ค Giuseppe Scotto Lavina ยท Ramon Fede ๐Ÿ“… Maggio 2026 ๐Ÿงช 904 Test ยท 47 File ยท 94% Coverage ๐Ÿ”’ Security Audit Completato
01 โ€” Panoramica
Cos'รจ GeoData Fishing
Un sistema completo per registrare catture di pesca, arricchendole automaticamente con oltre 30 datapoint geo-meteorologici e scientifici in tempo reale. Zero API key richieste, 100% serverless.
๐ŸŽฏ
Problema Risolto
Il contesto della cattura

Quando un pescatore cattura un pesce, le condizioni ambientali โ€” meteo, pressione barometrica, fase lunare, livello dell'acqua โ€” sono cruciali per capire perchรฉ il pesce ha abboccato. GeoData acquisisce automaticamente tutti questi dati dal GPS del telefono, senza alcun input manuale.

๐Ÿ’ก
Approccio Tecnico
Resilienza con Promise.allSettled

Il sistema aggrega dati da 7+ API pubbliche gratuite in parallelo tramite Promise.allSettled. Se un provider รจ down, gli altri compensano. Un indicatore data_quality (0-100) traccia la completezza dei dati per ogni cattura.

๐Ÿ“Š Dati Raccolti Automaticamente per Cattura

๐ŸŒก๏ธ
Meteo Completo

Temperatura, umiditร , pressione, trend barometrico, vento (velocitร  + direzione + raffica), copertura nuvolosa, precipitazioni, UV index

๐Ÿ“
Geolocalizzazione

Reverse geocoding completo via Nominatim: cittร , provincia, via, indirizzo formattato, geohash a 7 caratteri per query di prossimitร 

๐ŸŒ™
Dati Lunari & Solunari

Fase lunare (8 fasi), illuminazione %, periodi solunari major/minor, day rating 1-5, transiti Moon Overhead/Underfoot

๐Ÿ’ง
Corpi d'Acqua

Fiumi, laghi, canali, stagni nel raggio di 10km via Overpass API (OpenStreetMap). Dighe nel raggio di 50km con altezza e materiale

๐Ÿงฒ
Geomagnetico (Kp)

Indice Kp planetario 0-9 da NOAA SWPC. Campo magnetico terrestre calmo (Kpโ‰ค2) = ottimo per la pesca

๐ŸŒŠ
Livelli Idrometrici

Dati real-time dalla rete AEGIS Regione Lazio โ€” livello fiume in metri, trend, stazione piรน vicina con distanza

๐ŸŒ…
Dati Solari

Orario alba/tramonto, flag giorno/notte, bonus twilight calcolato per la "golden hour" di pesca

๐ŸŒŠ
Maree

Predizioni maree via Open Tide API (modello TPXO9-v5), altezze e tempi delle prossime 24 ore

๐Ÿ”ฌ
Dati Scientifici

Temperatura acqua stimata (con lag stagionale), delta temperatura 24h, fishing score composito 0-100 su 8 fattori

55+ colonne vengono salvate per ogni cattura nel database D1, rendendo ogni record un dataset completo per analisi statistiche e correlazioni specie-condizioni.
02 โ€” Architettura
Infrastruttura Cloudflare
Architettura 100% serverless su Cloudflare Workers. Un singolo deploy gestisce API, database, storage foto e hosting frontend. Zero server da mantenere.
Architettura di Sistema โ€” Cloudflare Edge
๐Ÿ“ฑ Mobile Browser
GPS + Camera API
โ–ผ HTTPS โ–ผ
๐Ÿ“„ Static Assets
web/ โ†’ CDN
โ†
โšก Hono Worker
src/worker.js
โ†’
๐Ÿ—ƒ๏ธ D1 Database
SQLite Edge
๐Ÿ“ฆ R2 Bucket
Foto Catture
โ–ผ Promise.allSettled โ–ผ
Open-Meteo
METEO PRIMARY
Yr.no
METEO FALLBACK
Nominatim
GEOCODING
Overpass
OSM WATER
NOAA SWPC
Kp INDEX
AEGIS Lazio
WATER LEVEL
Open Tide
TIDES

โ˜๏ธ Servizi Cloudflare โ€” Dettaglio

โšก
Workers
Runtime serverless V8 isolate sull'edge globale
  • Cosa: Runtime JavaScript V8 isolates distribuito su 300+ data center Cloudflare
  • Perchรฉ: Zero cold start, latenza <50ms, auto-scaling, zero infrastruttura da gestire
  • Come: Framework Hono v4 โ€” routing, middleware CORS, security headers built-in
  • Entry point: src/worker.js โ€” esporta l'app Hono come export default app
  • Dev locale: wrangler dev --local con D1/R2 simulati localmente
๐Ÿ—ƒ๏ธ
D1 (SQLite Edge)
Database relazionale co-locato con il worker
  • Cosa: SQLite distribuito con binding DB diretto nel worker context
  • Perchรฉ: Zero latenza DB (co-locato), SQL standard, schema versionato con migration
  • Come: Data Access Layer in repository.js โ€” tutte le query passano dal DAL
  • Schema: 6 tabelle, 9 indici, 55+ colonne su catches, FK con CASCADE
  • Sicurezza: Sempre .bind() per parametri, mai concatenazione SQL
๐Ÿ“ฆ
R2 (Object Storage)
S3-compatibile, zero egress fee
  • Cosa: Storage binario per foto catture con binding PHOTOS
  • Perchรฉ: Zero costi di egress (a differenza di S3), API S3-compatibile
  • Come: Upload in catches.js via c.env.PHOTOS.put()
  • Organizzazione chiavi: catches/{catchId}/{timestamp}-{filename}
  • Limiti: Max 10MB/file, MIME whitelist (jpeg, png, webp, heic)
๐Ÿ“„
Static Assets
CDN globale, zero build step
  • Cosa: La directory web/ viene servita come CDN globale Cloudflare
  • Perchรฉ: Zero build step, cache automatica, same-origin con l'API (no CORS issues)
  • Config: [assets] directory = "./web" in wrangler.toml
  • Frontend: Vanilla JS SPA, CSS glassmorphic dark mode, Leaflet + Chart.js per mappe e grafici
  • PWA: Service Worker Workbox, manifest installabile, icone custom Nautilus, aggiornamento via toast persistente
  • Zero CDN: Leaflet, Chart.js, Workbox e Inter font serviti da web/ โ€” nessuna dipendenza esterna a runtime

๐Ÿ“ Struttura del Progetto

GeoData/ โ”œโ”€โ”€ src/ โ† CODICE PRODUZIONE (Cloudflare Workers) โ”‚ โ”œโ”€โ”€ worker.js Entry point Hono โ€” middleware, routing, error handler โ”‚ โ”œโ”€โ”€ config/ โ”‚ โ”‚ โ””โ”€โ”€ geo.json Cache config (100m radius, 30min TTL), provider config โ”‚ โ”œโ”€โ”€ db/ โ”‚ โ”‚ โ”œโ”€โ”€ schema.sql Schema D1 completo (CREATE TABLE IF NOT EXISTS) โ”‚ โ”‚ โ”œโ”€โ”€ migrations/ ALTER TABLE per evoluzione incrementale โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ 003_water_levels.sql โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ 004_bait_presentation.sql โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ 005_locations.sql โ˜… Tabella geo-fencing luoghi โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ 006_dedup_catches.sql UNIQUE(timestamp, angler_name) โ€” colonne esplicite โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ 007_fix_column_scramble.sql Ripristino colonne scrambled da 006 โ”‚ โ”‚ โ””โ”€โ”€ repository.js โ˜… Data Access Layer โ€” TUTTE le query D1 (~988 righe) โ”‚ โ”œโ”€โ”€ routes/ โ”‚ โ”‚ โ”œโ”€โ”€ catches.js CRUD catture + upload foto R2 + validazioni โ”‚ โ”‚ โ”œโ”€โ”€ sessions.js CRUD sessioni di pesca โ”‚ โ”‚ โ”œโ”€โ”€ tags.js CRUD tag + associazione M:N a catture โ”‚ โ”‚ โ”œโ”€โ”€ photos.js Serve foto da R2 (GET, pubblico senza API key) โ”‚ โ”‚ โ”œโ”€โ”€ geo.js GET geo-data live senza salvare โ”‚ โ”‚ โ”œโ”€โ”€ analytics.js Analitiche specie/location aggregate โ”‚ โ”‚ โ”œโ”€โ”€ admin.js โ˜… Admin API โ€” import/export, locations CRUD, erase-all โ”‚ โ”‚ โ””โ”€โ”€ admin-ui.js โ˜… Admin HTML UI glassmorphic (~1700 righe) โ”‚ โ””โ”€โ”€ lib/ โ”‚ โ”œโ”€โ”€ geo-data.js โ˜… Orchestratore principale โ€” Promise.allSettled 7 API โ”‚ โ”œโ”€โ”€ weather.js Bridge verso weather-manager + historical โ”‚ โ”œโ”€โ”€ location.js Nominatim + Overpass (water bodies, dams) + Tides โ”‚ โ”œโ”€โ”€ scientific.js NOAA Kp, stima temp acqua, twilight bonus โ”‚ โ”œโ”€โ”€ lunar.js Fase lunare + periodi solunari (calcolo locale) โ”‚ โ”œโ”€โ”€ fishing-score.js Score 0-100 basato su 8 fattori pesati โ”‚ โ”œโ”€โ”€ cache.js In-memory radial cache (Map, TTL, Haversine, cap 100) โ”‚ โ”œโ”€โ”€ water-level.js โ˜… AEGIS Lazio โ€” livello idrometrico (OAuth2) โ”‚ โ”œโ”€โ”€ utils.js haversine, geohash, cardinal, weather descriptions โ”‚ โ””โ”€โ”€ providers/ โ”‚ โ”œโ”€โ”€ weather-manager.js Orchestratore waterfall tra provider meteo โ”‚ โ”œโ”€โ”€ open-meteo.js Provider primario meteo (10K req/giorno) โ”‚ โ””โ”€โ”€ yr-no.js Provider fallback MET Norway โ”‚ โ”œโ”€โ”€ web/ โ† FRONTEND (Cloudflare Static Assets + PWA) โ”‚ โ”œโ”€โ”€ index.html SPA entry โ€” dual nav, intro video overlay, Leaflet/Chart.js SRI โ”‚ โ”œโ”€โ”€ sw.js โ˜… Service Worker Workbox v7.4.0 โ€” precache + runtime caching โ”‚ โ”œโ”€โ”€ manifest.json PWA manifest โ€” Nautilus Carpfishing, icone, theme โ”‚ โ”œโ”€โ”€ css/app.css Design system glassmorphic dark mode (~2300 righe) โ”‚ โ”œโ”€โ”€ css/vendor/ โ”‚ โ”‚ โ””โ”€โ”€ leaflet.css Leaflet maps CSS (self-hosted) โ”‚ โ”œโ”€โ”€ js/ โ”‚ โ”‚ โ”œโ”€โ”€ app.js Entry point e SPA initialization (~110 righe) โ”‚ โ”‚ โ”œโ”€โ”€ api.js Client API โ€” fetch con X-API-Key header (~156 righe) โ”‚ โ”‚ โ”œโ”€โ”€ chart-builder.js Chart.js wrapper per grafici avanzati (~340 righe) โ”‚ โ”‚ โ”œโ”€โ”€ constants.js Costanti globali, DOM selectors e form options โ”‚ โ”‚ โ”œโ”€โ”€ state.js Global state manager reattivo (singleton store) โ”‚ โ”‚ โ”œโ”€โ”€ views/ Moduli visuali: capture.js, gallery.js, stats.js โ”‚ โ”‚ โ”œโ”€โ”€ components/ UI isolati: autocomplete, modal, photo-viewer, exif-confirm, location-picker, geo-edit-sheet โ”‚ โ”‚ โ”œโ”€โ”€ utils/ Utility pure: router.js, geo.js, image.js, helpers.js, exif.js, storage.js, persistence.js โ”‚ โ”‚ โ””โ”€โ”€ vendor/ โ˜… Librerie self-hosted: leaflet.js, chart.umd.js, workbox-sw.js โ”‚ โ””โ”€โ”€ assets/ โ”‚ โ”œโ”€โ”€ logo-topbar.png Medaglione logo Nautilus per header โ”‚ โ”œโ”€โ”€ logo-splash-cropped.webp Logo HD per intro animata iniziale โ”‚ โ”œโ”€โ”€ intro-mobile.mp4 Animazione intro premium con skip (video) โ”‚ โ”œโ”€โ”€ hero-bg-nautilus.png Background hero โ”‚ โ”œโ”€โ”€ nav-capture.png Icona navigazione Cattura โ”‚ โ”œโ”€โ”€ nav-gallery.png Icona navigazione Galleria โ”‚ โ”œโ”€โ”€ nav-stats.png Icona navigazione Stats โ”‚ โ””โ”€โ”€ icons/ PWA icons (512, 192, 180, 32, 16px) โ”‚ โ”œโ”€โ”€ scripts/ โ”‚ โ”œโ”€โ”€ bump-sw.mjs Auto-bump APP_VERSION in sw.js pre-deploy โ”‚ โ””โ”€โ”€ lighthouse-audit.mjs Audit Lighthouse programmatico (Puppeteer) โ”‚ โ”œโ”€โ”€ tests/ โ† 30 file test, 616 test, 1250 assertions, ~7s โ”œโ”€โ”€ wrangler.toml Config Workers/D1/R2/Assets โ””โ”€โ”€ package.json Solo 2 deps: hono + wrangler
Dipendenze minimali: L'intero progetto ha solo 2 dipendenze โ€” hono (runtime) e wrangler (dev/deploy). Zero bundler, zero framework frontend, zero ORM. Tutto vanilla JavaScript.
03 โ€” Fonti Dati
Provider API Esterni
Ogni cattura viene arricchita con dati da 7+ API pubbliche gratuite, orchestrate dal modulo geo-data.js tramite Promise.allSettled per massima resilienza.
ProviderModuloDati FornitiRate LimitTimeoutFallback
Open-Meteoopen-meteo.jsTemp, pressione, vento, umiditร , UV, precipitazioni, temp ieri per delta10K/giorno8sโ†’ Yr.no
Yr.no (MET Norway)yr-no.jsStessi dati meteo, formato MET Norway compactUser-Agent req.8sUltimo nella catena
Nominatim (OSM)location.jsReverse geocoding: cittร , provincia, via, indirizzo formattato1 req/sec!10sโ€”
Overpass API (OSM)location.jsCorpi d'acqua vicini (fiumi, laghi, canali) + dighe con altezza~10K/giorno15sMirror FR
NOAA SWPCscientific.jsIndice geomagnetico planetario Kp (0-9)Free5sโ†’ null
AEGIS Regione Laziowater-level.jsLivello idrometrico real-time, trend, stazione, distanzaFree (OAuth2)10sโ†’ skip
Open Tide APIlocation.jsPredizioni maree (TPXO9-v5), altezze, tempi prossime 24hFree8sโ†’ skip

๐Ÿ”„ Weather Provider Waterfall

Il modulo weather-manager.js implementa un pattern waterfall iterativo: prova i provider nell'ordine configurato in geo.json. Se il primo fallisce, passa al successivo automaticamente.

1
Open-Meteo (primario)

API meteo gratuita con forecast corrente e dati storici dal 1940. Fornisce temperature_2m_mean del giorno precedente per il calcolo delta temperatura 24h. Include dati orari per il calcolo del trend barometrico su 6 ore.

2
Yr.no โ€” MET Norway (fallback)

Se Open-Meteo รจ down, timeout o rate-limited, il manager tenta Yr.no. Richiede User-Agent obbligatorio. Non fornisce delta temp 24h (campo โ†’ null nel frontend) nรฉ dati storici.

3
Graceful Degradation

Se tutti i provider meteo falliscono, il campo weather รจ null ma la cattura viene comunque salvata con tutti gli altri dati (lunar, location, water). Il campo data_quality riflette il degrado (es. 85% invece di 100%).

๐ŸŒŠ Integrazione AEGIS Lazio โ€” Dettaglio

๐ŸŒŠ
Rete Idrometeorologica Regione Lazio
temporeale.regione.lazio.it/datascapeA

Autenticazione

  • Protocollo: OAuth2 Resource Owner Password Grant
  • Credenziali: AegisPubblico / AegisPubblico (pubbliche, embedded nel JS del portale)
  • Token caching: In-memory per isolate, con refresh 60s prima della scadenza

Endpoint

  • POST /connect/token โ€” OAuth2 token
  • GET /v3/locations โ€” Lista stazioni con coordinate
  • GET /v3/elements โ€” Letture sensori live

Logica di Selezione Stazioni

  • Filtra stazioni con hwl: true (ha sensore idrometrico)
  • Calcola distanza con Haversine da coordinate utente
  • Filtra per raggio massimo (default 30km)
  • Restituisce le 3 stazioni piรน vicine
  • Matching sensore: elementName === 'Water Level' o contiene "idrometro"/"livello"

Dati Restituiti

  • Nome stazione, ID, coordinate, altitudine
  • Livello acqua in metri, trend in m, timestamp lettura
  • Stato sensore (0 = normale)

โšก Radial Cache System

La cache in-memory usa un lookup spaziale Haversine: se una richiesta arriva da coordinate entro 100m di una precedente, restituisce il dato dalla cache. TTL 30 minuti, cap a 100 entry con eviction FIFO (oldest key). Si resetta al restart del worker isolate โ€” accettabile perchรฉ Workers sono stateless by design.

04 โ€” Algoritmo
Fishing Score Scientifico
Un punteggio 0-100 basato su 8 fattori ambientali pesati scientificamente, calcolato in fishing-score.js con base 50 e fattori additivi/sottrattivi.
FattorePesoLogicaFondamento Scientifico
Pressione Barometricaยฑ20Rising fast +20 ยท Rising +10 ยท Stable +5 ยท Falling -5 ยท Fast -15Variazione pressione altera la vescica natatoria
Fase Lunareยฑ15New/Full moon +15 ยท Near new/full +5Gravitร  lunare influenza maree e attivitร 
Periodo Solunarยฑ15Major (overhead/underfoot) +15 ยท Minor (rise/set) +8Teoria di J.A. Knight โ€” transit lunari = picchi
Condizioni Meteoยฑ10Nuvolosoโ‰ฅ60% +5 ยท Vento leggero +3 ยท Pioggia leggera +3 ยท Pioggia -10Luce ridotta attiva i predatori
Indice Kpยฑ8Calmo โ‰ค2 +8 ยท Moderato โ‰ค4 +3 ยท Tempesta โ‰ฅ6 -8Campo geomagnetico influenza sensori laterali
Crepuscolo+10Golden hour alba/tramonto, bonus proporzionale alla vicinanzaTransizione luce = picco attivitร  predatoria
Delta Temp 24hยฑ5Stabile <3ยฐC +5 ยท Rapido >8ยฐC -5Pesci ectotermici sensibili a shock termici
Temp Acqua Stimataยฑ5Ottimale 15-22ยฐC +5 ยท Buona 10-25ยฐC +2 ยท Estrema -5Range metabolico ideale delle specie

๐Ÿท๏ธ Classificazione

80-100 Excellent
65-79 Good
50-64 Fair
35-49 Poor
0-34 Bad
๐Ÿงฎ Formula Pseudocode
let score = 50; // base score += pressureTrend; // ยฑ20 (trend barometrico 6h) score += lunarPhase; // ยฑ15 (new/full moon bonus) score += solunarPeriod; // ยฑ15 (major/minor feeding) score += weatherConditions;// ยฑ10 (nuvole, vento, pioggia) score += kpIndex; // ยฑ8 (geomagnetico NOAA) score += twilightBonus; // +10 (golden hour) score += tempDelta24h; // ยฑ5 (stabilitร  termica) score += waterTemp; // ยฑ5 (range ottimale) score = Math.max(0, Math.min(100, score));
05 โ€” Database
Schema D1 (SQLite Edge)
Database SQLite distribuito sull'edge Cloudflare con 5 tabelle, 8 indici, foreign keys con CASCADE e schema versionato con migration incrementali.
catches (55+ colonne โ€” timestamp, geo, meteo, lunar, solunar, scientific, water level, catch) โ”œโ”€โ”€ catch_photos (r2_key, thumbnail, metadata) โ†’ FK ON DELETE CASCADE โ”œโ”€โ”€ catch_tags (associativa M:N catchโ†”tag) โ†’ FK ON DELETE CASCADE (entrambe) โ”‚ โ””โ”€โ”€ tags (name UNIQUE, color #RRGGBB) โ””โ”€โ”€ sessions (name, start/end, location) โ†’ catches.session_id SET NULL locations (โ˜… geo-fencing โ€” name, water_name, water_type, lat, lon, radius_m, notes)

๐Ÿ“‡ Indici Ottimizzati

geohash โ†’ proximity lat,lon โ†’ bounding box timestamp โ†’ time queries fish_species โ†’ lookup nearest_water_name โ†’ water catch_photos.catch_id โ†’ join catch_tags โ†’ M:N sessions.start_time

๐Ÿ”€ Repository Pattern (DAL)

Tutte le query DB passano da repository.js. Le route non fanno mai query dirette. Il DAL espone funzioni tipizzate con parametri bound:

๐Ÿ“ Migration System

Dual-track: Lo schema base usa CREATE TABLE IF NOT EXISTS (per nuove installazioni). Le migration usano ALTER TABLE ADD COLUMN (per DB esistenti). Entrambi devono essere sincronizzati โ€” ogni nuova colonna va aggiunta in entrambi.
06 โ€” Sicurezza
Security Model
Security audit completato ad Aprile 2026 (commit a4d808b). Remediation in 7 fasi completata a Maggio 2026 (tag v1.1.0-security) โ€” 135 test automatizzati coprono tutte le fix. Audit completo in SECURITY_AUDIT.md.
๐Ÿ”’
Autenticazione
API Key via custom header
  • Header: X-API-Key su tutti gli endpoint /api/*
  • Eccezione: GET /api/photos/* pubblico โ€” i tag <img> non possono inviare header custom
  • Storage: Cloudflare Workers Secret in produzione (wrangler secret put API_KEY)
  • Admin: HTTP Basic Auth su /api/admin/* con timingSafeEqual
  • Cache: Cache-Control: private, no-store su tutte le risposte API autenticate
๐Ÿ›ก๏ธ
Security Headers
Via hono/secure-headers middleware
  • CSP: default-src 'self' โ€” zero CDN esterni. Admin UI usa nonce per-request su scriptSrc.
  • HSTS: max-age=31536000; includeSubDomains
  • CORS: Whitelist statica โ€” NO wildcard, solo domini esatti di prod/staging/localhost
  • X-Frame: DENY ยท X-Content-Type: nosniff
  • Permissions-Policy: camera/geo self, 8 API sensibili bloccate
  • Vendor assets: SheetJS e font Inter self-hostati con SRI

โœ… Validazioni Implementate

DoveCosaCome
catches.js POST/PUTCoordinate range, string lengthslat: -90..90, lon: -180..180, strings max 200/100/2000
catches.js photo uploadFile size, MIME type, magic bytesMax 10MB, whitelist jpeg/png/webp/heic, magic bytes via image-validation.js
admin.js importLimite righe, lat/lon per rigaMax 10.000 righe (413), range check per ogni record
admin.js restore fotoR2 key, size, magic bytesRegex ^catches/\d+/[A-Za-z0-9._-]+$, max 10MB, magic bytes
admin.js backup/restoreConferma esplicita?confirm=RESTORE obbligatorio; snapshot rollback in R2 prima dell'operazione
tags.js POSTName, color formatName required max 100 chars, color regex #RRGGBB
photos.js GETPath traversal, chiavi riservateBlock .., prefix check catches/, blocco _rollback/ (403)
repository.js updateColumn name injectionRegex /^[a-z_][a-z0-9_]*$/ + allowedFields whitelist
repository.js analyticsLIKE wildcard escape% e _ rimossi dall'input
zip.js restoreZIP malformati, path traversalFirma local header, bounds check, EOCD limitato a 64KB
geo.jsCrash provider esterniPromise.allSettled + try/catch โ†’ sempre 200, partial: true
app.js frontendXSS output encodingescapeHtml() con 5 entity su tutti gli output utente
worker.js globalError leak preventionapp.onError() + safeError() โ€” mai stack trace al client
admin.js importTiming oracleConfronto admin password con timingSafeEqual โ€” no timing leak
07 โ€” REST API
Endpoint Reference
API RESTful completa con 24+ endpoint montati su Hono. Base URL: http://localhost:8787/api (dev) o https://geodata-fishing.*.workers.dev/api (prod).
EndpointMetodiAuthDescrizione
/api/healthGET๐Ÿ”‘Health check con contatori (catches, sessions, photos, tags)
/api/statsGET๐Ÿ”‘Statistiche aggregate (species count, locations count)
/api/catchesGET POST๐Ÿ”‘Lista paginate (offset/limit) ยท Crea cattura dal GPS
/api/catches/:idGET PUT DEL๐Ÿ”‘CRUD singola cattura con photos e tags
/api/catches/:id/photosGET POST๐Ÿ”‘Lista + Upload foto (multipart o base64)
/api/catches/:id/tags/:tagIdPOST DEL๐Ÿ”‘Associa/rimuovi tag da cattura
/api/catches/nearbyGET๐Ÿ”‘Ricerca geospaziale per raggio (lat, lon, radius km)
/api/sessionsGET POST๐Ÿ”‘Lista + Crea sessioni di pesca
/api/sessions/:idGET DEL๐Ÿ”‘Dettaglio sessione con catture associate
/api/sessions/:id/endPUT๐Ÿ”‘Chiudi sessione con note finali
/api/tagsGET POST๐Ÿ”‘CRUD tag (name UNIQUE + color hex)
/api/tags/:idDEL๐Ÿ”‘Elimina tag (CASCADE su associazioni)
/api/geo-dataGET๐Ÿ”‘Dati geo-meteo live, integrato con location override
/api/analytics/species/:nameGET๐Ÿ”‘AVG score/temp/pressure/wind per specie
/api/analytics/locationGET๐Ÿ”‘Top 5 species + avg score per area geografica
/api/photos/*GET๐ŸŒServe foto da R2 (pubblico โ€” per tag <img>)
๐Ÿ”’ ADMIN โ€” Basic Auth (admin:<ADMIN_PASSWORD>)
/api/adminGET๐Ÿ”Pannello admin HTML (UI import/export/backup/luoghi/erase)
/api/admin/importPOST๐Ÿ”Import bulk catture da Excel con coordinate default
/api/admin/exportGET๐Ÿ”Export tutte le catture come JSON
/api/admin/backupGET๐Ÿ”โ˜… Genera e scarica backup completo ZIP (DB + foto R2)
/api/admin/backupPOST๐Ÿ”โ˜… Ripristina da backup ZIP โ€” atomic erase + restore. Max 50MB
/api/admin/catchesGET๐Ÿ”โ˜… Records browser paginato โ€” lista catture con filtri
/api/admin/catches/:idGET PUT DEL๐Ÿ”โ˜… Modifica/elimina singola cattura dall'admin
/api/admin/catches/:id/re-enrichPOST๐Ÿ”โ˜… Ri-arricchisce geo-meteo una cattura esistente
/api/admin/locationsGET POST๐Ÿ”CRUD geo-fencing: lista + crea luogo (centro + raggio)
/api/admin/locations/:idPUT DEL๐Ÿ”Aggiorna/elimina luogo configurato
/api/admin/erase-allPOST๐Ÿ”Reset completo DB. Richiede { confirm: "ERASE ALL" }
08 โ€” Frontend
Mobile-First SPA
Frontend Vanilla JS modulare senza framework, design system glassmorphic dark mode ottimizzato per l'uso in campo. Zero build step, PWA installabile.
๐Ÿ“ฑ
Stack Zero-Build Modulare
~2100 righe JS (6 moduli), ~2035 righe CSS
Vanilla JS (ES Modules) Zero Framework CSS Glassmorphic Leaflet Maps (self-hosted) Chart.js (self-hosted) Camera API (env: rear) Geolocation API (iOS-safe)
  • Routing: Hash router via `utils/router.js` con deep link (#/catch/{id}) e views modulari
  • State: Modulo `state.js` dedicato con subscribe patterns per gestire stato reattivamente tra views
  • PWA: Service Worker Workbox, manifest installabile, aggiornamento via toast persistente
  • Vendor assets: Leaflet, Chart.js, Workbox, Inter font โ€” tutti self-hosted in web/js/vendor/ e web/css/vendor/. Zero CDN esterni a runtime
  • Offline & state persistence: localStorage namespaced sotto geodata.* via utils/storage.js (low-level) + utils/persistence.js (domain accessors con sanitizzazione). Persisti: capture draft completo (tutti gli input + tag, esclusi foto/override), filtri Galleria, filtri Stats, API key, angler name, geo permission, grafici custom
  • UX: Toast, skeleton loading, autocomplete, pinch-to-zoom foto, Charts custom
๐Ÿงญ
3 View Principali
SPA con dual navigation (mobile + desktop)
๐Ÿ“ธ
Capture

Camera rear con preview live, cattura foto, form con autocomplete (21 esche, 29 specie), GPS lock, geo-data preview card, salvataggio con upload foto simultaneo. Upload da galleria con lettura automatica EXIF (GPS + data/ora) e dialog di conferma se i dati EXIF differiscono da quelli correnti. Override luogo/data tramite bottom-sheet unificato (geo-edit-sheet.js) con <dialog> nativo, swipe-to-dismiss, haptics e visualViewport tracking

๐Ÿ“‹
Gallery

Grid responsive con thumbnail da R2, ricerca per specie/luogo, filtro temporale (oggi/settimana). Tap โ†’ modal dettaglio con SVG score gauge, mappa Leaflet, 6 sezioni dati

๐Ÿ“Š
Stats

Contatori aggregati, top 5 specie, grafici avanzati (timeseries, scatter, doughnut, bar) via Chart.js wrapper, grafici personalizzabili dall'utente

๐Ÿงฉ Architettura Modulare Frontend

Moduli JS
FileRigheResponsabilitร 
app.js~110EntryPoint: init SPA, check intro video, event listener router globali
state.js, constants.js~200Definizione degli stati globali e query selectors/options costanti
views/*~600/eaModuli vista specifici: capture.js, gallery.js, stats.js separati per lazy logic
components/*varUI isolati: toast.js, modal.js, photo-viewer.js, autocomplete.js, exif-confirm.js, location-picker.js, geo-edit-sheet.js
api.js~156Client HTTP con X-API-Key, upload foto FormData/base64
chart-builder.js~340Chart.js wrapper: line, scatter, doughnut, bar. Config in localStorage
utils/router.js~76Hash route parsing (pure functions, zero DOM, testabile)
utils/geo.js~95Geolocation iOS-safe, permission tracking via localStorage
utils/image.js, helpers.js~100Compressione immagini e DOM helpers condivisi
utils/storage.js~90localStorage helpers namespaced (geodata.*), JSON/string round-trip, try/catch wrapped (private-mode/quota safe)
utils/persistence.js~95Domain accessors: loadCaptureDraft/save/clear, load/saveGalleryFilters, load/saveStatsFilters. Sanitizza shape al load (corrupt JSON โ†’ fallback)
Design System

Colori (CSS Vars)

  • BG: #0a0e1a
  • Card: rgba(15,23,42,.7) + blur
  • Accent: Cyan/Blue gradient
  • Score: Emeraldโ†’Red per range

Componenti

  • Toast (success/error/info)
  • Modal overlay collassabile
  • SVG ring gauge score
  • Skeleton loading
  • Bottom-sheet nativo (<dialog>) con swipe-to-dismiss

Responsive & Motion

  • Mobile-first <768px
  • Tablet 768-1199px
  • Desktop โ‰ฅ1200px
  • Safe area insets + dvh/dvw
  • Material 3 emphasized easing tokens
  • prefers-reduced-motion globale

๐Ÿ—‚๏ธ API Client (api.js)

Pattern: Thin wrapper su fetch() con header X-API-Key iniettato automaticamente. Key letta da <meta name="api-key"> (SSR) o localStorage (fallback). Upload foto supporta sia FormData (multipart) che JSON (base64).
09 โ€” Routing
Hash Router โ€” URL Condivisibili
L'app usa hash routing per URL condivisibili e deep link. Il fragment hash (#...) non viene mai inviato al server โ€” zero modifiche backend, massima sicurezza.
๐Ÿ”—
Route Supportate
Parsing puro in utils/router.js (~76 righe)
HashRisultato
#/captureVista Cattura
#/galleryVista Galleria
#/statsVista Statistiche
#/catch/{id}Deep link โ†’ gallery + modal dettaglio cattura
(vuoto o invalido)Fallback sicuro โ†’ vista Cattura
๐Ÿ—๏ธ
Architettura Router
Separazione logica pura / DOM
1
router.js (pure functions)

Logica di parsing: parseRoute(hash), catchHash(id), viewHash(view). Zero dipendenze browser โ€” testabile con bun test.

2
app.js (thin wrapper)

parseHash() chiama parseRoute(location.hash). setHash() usa history.replaceState โ€” non inquina la history. init() gestisce route iniziale + listener hashchange.

3
Share & Deep Link

Share button include location.origin + /#/catch/{id} via navigator.share(). Deep link carica gallery, poi apre modal. Cattura inesistente โ†’ toast errore + fallback.

๐Ÿ”’ Sicurezza Router

๐Ÿ”‘
API Key mai nell'URL

Il fragment hash non viene inviato al server โ€” la key resta nel header HTTP X-API-Key

โœ…
ID Validato

Regex strict /^catch\/(\d+)$/, range 1..2ยณยน-1 (SQLite INTEGER safe)

๐Ÿ›ก๏ธ
Whitelist Viste

Solo capture, gallery, stats accettati. Qualsiasi hash invalido โ†’ fallback sicuro a capture

โš ๏ธ Gotcha Router

10 โ€” PWA
Progressive Web App & Service Worker
Nautilus รจ una PWA installabile con Service Worker Workbox v7.4.0. Caching multi-strategia, aggiornamento automatico, e installazione nativa su iOS/Android/Desktop.
โšก
Service Worker (Workbox)
sw.js โ€” 4 strategie di caching
StrategiaTargetTTL / Cap
PrecacheApp shell: HTML, CSS, JS, manifest, icone, font, vendor libs (tutto self-hosted)Versioned (APP_VERSION)
CacheFirstFoto da R2 (/api/photos/*)7gg / 100 foto
NetworkOnlyChiamate API (/api/* escluse foto)Mai cachate โ€” dati live
Dopo il commit eb2ebb0, Leaflet, Chart.js, Workbox e il font Inter sono tutti self-hosted in web/js/vendor/ e web/assets/fonts/. La strategia StaleWhileRevalidate per Google Fonts รจ stata rimossa โ€” zero dipendenze CDN a runtime.
๐Ÿ”„
Aggiornamento Automatico
scripts/bump-sw.mjs
1
Version Bump

bump-sw.mjs genera APP_VERSION = 'YYYY.MM.DD.HHmmss' e sovrascrive la costante in sw.js. Ogni entry nel precache usa questa versione come revision.

2
Deploy

npm run deploy = bump-sw.mjs + wrangler deploy. Il browser rileva il diff di byte nel SW e forza l'installazione della nuova versione.

3
Client Notify

skipWaiting() + clients.claim() attivano il nuovo SW immediatamente. Evento controllerchange โ†’ toast persistente "๐Ÿ”„ Aggiornamento disponibile" con bottone Aggiorna. Il reload avviene solo su click utente โ€” nessun interrupt in uso.

๐Ÿ“ฒ Installazione PWA

๐Ÿ“ฑ
iOS Safari
Condividi โ†’ Aggiungi alla Schermata Home
  • Rilevamento: User-Agent iPhone/iPod + iPad con maxTouchPoints โ‰ฅ 5 e pointer: coarse
  • Esclusione macOS: MacIntel senza touch coarse = desktop โ†’ bottone nascosto
  • Guida: Toast interattivo con istruzioni passo-passo (auto-dismiss 8s)
  • Meta tags: apple-mobile-web-app-capable, apple-touch-icon, status bar black-translucent
๐Ÿค–
Android / Desktop
beforeinstallprompt API
  • Trigger: Evento beforeinstallprompt โ†’ mostra bottone "Installa" nel header
  • Prompt nativo: Click โ†’ deferredPrompt.prompt() โ†’ dialog installazione OS
  • Post-install: Evento appinstalled โ†’ bottone nascosto automaticamente
  • Standalone check: Se giร  installata (display-mode: standalone), bottone mai visibile

๐Ÿ“‹ Web App Manifest

Identitร 

  • Nome: Nautilus Carpfishing
  • Short: Nautilus
  • Display: standalone
  • Orientation: portrait-primary

Icone

  • icon-512.png (512ร—512)
  • icon-192.png (192ร—192)
  • apple-touch-icon.png (180ร—180)
  • icon-32.png, icon-16.png

Theme

  • Background: #060a14
  • Theme: #060a14
  • Categories: sports, lifestyle
  • Lang: it
11 โ€” Testing
Test Suite & Coverage
Suite hardened con 904 test e 2229 asserzioni su 47 file. Copertura 94.28% funzioni e 95.72% linee. Tre fasi: backend lib, repository/routes integration, frontend utilities. Esecuzione ~7s con Bun test runner.
โœ… 904 test 94.28% Funzioni 95.72% Linee 2229 Assertions 47 file test 0 fail ยท 0 skip
File TestModulo TestatoCosa Verifica
FASE 1 โ€” BACKEND LIB (UNIT TEST)
scientific-geomagnetic.test.jsscientific.js (Kp)NOAA legacy/new format parsing, Kp impact levels, HTTP/network errors, empty arrays
scientific.test.jsscientific.jsWater temp estimation, twilight bonus, temp delta 24h, edge cases
weather.test.jsweather.jsHistorical weather parsing, pressure trend (6h), HTTP errors, sparse data
weather-manager.test.jsweather-manager.jsWaterfall fallback open-meteo โ†’ yr-no, all providers fail โ†’ throws
weather-providers.test.jsopen-meteo.js, yr-no.jsResponse parsing, field mapping, fallback to Yr.no, timeout handling
location.test.jslocation.jsNominatim parsing, tides, Overpass mirror fallback, dams metadata, sorting
geo-data.test.jsgeo-data.jsCurrent + historical paths, data_quality tracking, partial/total API failure
cache.test.jscache.jsRadial lookup, TTL expiry, FIFO eviction, historical cache, distance threshold
water-level.test.jswater-level.jsOAuth2 flow mock, station filtering (hwl), multi-sensor, Haversine, no-hwl fallback
fishing-score.test.jsfishing-score.jsTutti i fattori singoli e combinati, edge cases (tutti null, tutti ottimali), range 0-100
lunar.test.jslunar.js8 fasi lunari, illumination, solunar periods, day rating, timing precision
utils.test.jsutils.jsCardinal conversion, geohash, haversine, weather codes, escapeHtml
FASE 2 โ€” REPOSITORY & ROUTES (INTEGRATION)
repository-crud.test.jsrepository.js (DAL)Full CRUD: storeCatch (55 params), getCatch, updateCatch, photos, tags, sessions, locations, admin, analytics
repository-analytics.test.jsrepository.js (analytics)Species analytics queries, location stats, aggregation functions
worker.test.jsworker.js + routesIntegration test: mock D1/R2, auth guard, CRUD, photo upload, validation, error handling
routes.test.jsAll routes (62 test)Catches PUT/DELETE, photo upload (base64/multipart), admin CRUD, analytics, sessions, tags, CORS
routes-deep.test.jsUncovered pathsCatches POST create E2E, geo matched_location, admin enriched import, enrich, location override
catches-filters.test.jscatches.js (filters)Gallery filter queries, angler/location/date filters, pagination
analytics-api.test.jsanalytics.js routeTimeseries, distribution endpoint, pagination, error handling
admin-api.test.jsadmin.jsAuth guard (Basic Auth), import validation, export, error handling
admin-locations.test.jsadmin.js + repository.jsLocations CRUD, validation water_type, findMatchingLocation, erase all
location-override.test.jscatches.js + repository.jsHaversine sanity, geo-fencing match/miss, GET /api/catches override E2E
app-syntax.test.jsadmin-ui.jsTemplate literal syntax validation, escape characters check
FASE 3 โ€” FRONTEND (MOCK DOM)
helpers.test.jsutils/helpers.jsgetScoreClass/Color boundaries, formatDate/DateTime, escapeHtml XSS payload
frontend-state.test.jsstate.jsDefault values, state shape integrity, mutability
frontend-constants.test.jsconstants.jsBAITS/SPECIES/BAIT_BRANDS: no duplicates, expected entries, non-empty strings
frontend-geo.test.jsutils/geo.jsformatCoords, isIOS (navigator mock), hasGrantedGeolocation (localStorage), getPosition
frontend-image.test.jsutils/image.jsdataURLtoBlob: JPEG/PNG/WebP, binary preservation, edge cases
chart-builder.test.jschart-builder.jsCRUD chart config, validation, all 4 renderers (mock DOM/Chart.js), lifecycle, horizontal bar
router.test.jsutils/router.jsHash parsing, catch ID validation (range, XSS), view whitelist, fallback
persistence.test.jsutils/storage.js + utils/persistence.jslocalStorage helpers + domain accessors (capture draft, gallery/stats filters): round-trip, defaults, sanitize shape, corrupt JSON fallback, namespacing

๐Ÿงช Strategia di Testing (3 Fasi)

Mock Pattern
  • D1: Mock completo con prepare().bind().run()/first()/all() + prepareFn configurabile
  • R2: Mock put(), get() e delete() con Map in-memory
  • Fetch: globalThis.fetch mockato per ogni provider API (7 provider)
  • DOM: document.getElementById stub per Chart.js rendering
  • Navigator: Mock navigator.userAgent, platform, geolocation per iOS detection
  • localStorage: Map-based mock per chart configs e geo permissions
  • AbortController: Test timeout con controller.abort()
Comandi
# Run all tests bun test # With coverage report bun test --coverage # Single file bun test tests/fishing-score.test.js # Watch mode bun test --watch

๐Ÿ“Š Copertura per Modulo

Backend โ€” 100% linee (18 moduli)
geo-data.js โœ“ open-meteo.js โœ“ utils.js โœ“ admin-ui.js โœ“ analytics.js โœ“ catches.js 99% geo.js โœ“ tags.js โœ“ worker.js โœ“ location.js โœ“ scientific.js โœ“ weather.js โœ“ yr-no.js โœ“ sessions.js โœ“ water-level.js 99% repository.js 95% admin.js 94% cache.js 89%
Frontend โ€” 100% linee (7 moduli)
constants.js โœ“ state.js โœ“ helpers.js โœ“ router.js โœ“ chart-builder.js โœ“ geo.js 96% image.js 18%

image.js richiede Canvas/Image browser API โ€” non testabile in bun. Escludendolo, coverage >97%.

12 โ€” Deploy
Workflow di Deploy
Un singolo comando deploya API, database, storage e frontend. Il Service Worker viene versionato automaticamente. Tutto gestito da Wrangler.
1
Setup Secrets

wrangler secret put API_KEY โ€” imposta l'API key nel runtime produzione. Mai in codice.

2
Init Database

wrangler d1 execute geodata-fishing --file=src/db/schema.sql โ€” crea le tabelle D1.

3
Run Migrations

wrangler d1 execute geodata-fishing --file=src/db/migrations/003_water_levels.sql --remote ยท 004_bait_presentation.sql ยท 005_locations.sql ยท 006_dedup_catches.sql ยท 007_fix_column_scramble.sql โ€” aggiunge colonne, tabelle e corregge eventuali scramble. โš ๏ธ 006 usa colonne esplicite nel INSERTโ€ฆSELECT (mai SELECT * tra tabelle che possono divergere per ALTER TABLE).

4
Deploy

npm run deploy โ€” esegue bump-sw.mjs (versiona il Service Worker) poi wrangler deploy. Worker, static assets e bindings D1/R2 in un colpo solo.

โš™๏ธ wrangler.toml

name = "geodata-fishing" main = "src/worker.js" compatibility_date = "2025-12-01" [assets] directory = "./web" [[d1_databases]] binding = "DB" database_name = "geodata-fishing" database_id = "<uuid>" [[r2_buckets]] binding = "PHOTOS" bucket_name = "geodata-photos"
Perchรฉ Cloudflare? Deploy istantaneo con un singolo comando. Zero server, zero scaling manuale, zero cold start. Database SQLite co-locato col codice per latenza minima. Object storage senza costi di egress. Frontend servito dalla stessa infrastruttura โ€” tutto in un unico deploy, un solo wrangler.toml.