// helpers/normalizeActivity.js function safeJsonParse(value, fallback) { try { return value ? JSON.parse(value) : fallback; } catch { return fallback; } } function normalizeActivity(a) { // ============================ // GPS compatibile con /photos // ============================ const gps = (a.center_latitude != null && a.center_longitude != null) ? { lat: a.center_latitude, lng: a.center_longitude, alt: a.min_elevation_meters ?? null } : null; // ============================ // JSON fields // ============================ const location = safeJsonParse(a.location, null); const tags = safeJsonParse(a.tags, []); const diagnostics = safeJsonParse(a.diagnostics, null); // Preview percorso per thumbnails attività const track_preview = safeJsonParse(a.track_preview, []); // ============================ // Path stile /photos // ============================ const path = `/activities/${a.user}/original/${a.cartella}/${a.file_name}`; // ============================ // MIME type // ============================ const mime_type = a.format || "application/octet-stream"; // ============================ // mtimeMs compatibile progressiveSync // ============================ const mtimeMs = a.last_modified_millis ?? a.date_modified_millis ?? a.date_added_millis ?? Date.now(); return { // ============================ // Campi base come /photos // ============================ id: a.id, user: a.user, cartella: a.cartella, name: a.file_name, path, mime_type, size_bytes: a.size_bytes, mtimeMs, taken_at: a.start_time_millis ? new Date(Number(a.start_time_millis)).toISOString() : null, data: a.start_time_millis ? new Date(Number(a.start_time_millis)).toISOString() : null, location, gps, // ============================ // Campi speciali delle activity // ============================ file_name: a.file_name, file_extension: a.file_extension, format: a.format, display_name: a.display_name, route_name: a.route_name, title: a.title, description: a.description, notes: a.notes, sport: a.sport, activity_type: a.activity_type, device_manufacturer: a.device_manufacturer, device_model: a.device_model, source_app: a.source_app, start_time_millis: a.start_time_millis, end_time_millis: a.end_time_millis, duration_millis: a.duration_millis, distance_meters: a.distance_meters, elevation_gain_meters: a.elevation_gain_meters, elevation_loss_meters: a.elevation_loss_meters, min_elevation_meters: a.min_elevation_meters, max_elevation_meters: a.max_elevation_meters, file_distance_meters: a.file_distance_meters, file_elevation_gain_meters: a.file_elevation_gain_meters, computed_distance_meters: a.computed_distance_meters, computed_elevation_gain_meters: a.computed_elevation_gain_meters, average_speed_mps: a.average_speed_mps, max_speed_mps: a.max_speed_mps, average_pace_sec_per_km: a.average_pace_sec_per_km, average_heart_rate: a.average_heart_rate, max_heart_rate: a.max_heart_rate, average_cadence: a.average_cadence, max_cadence: a.max_cadence, calories: a.calories, power_avg: a.power_avg, power_max: a.power_max, point_count: a.point_count, segment_count: a.segment_count, lap_count: a.lap_count, min_latitude: a.min_latitude, max_latitude: a.max_latitude, min_longitude: a.min_longitude, max_longitude: a.max_longitude, center_latitude: a.center_latitude, center_longitude: a.center_longitude, last_modified_millis: a.last_modified_millis, date_added_millis: a.date_added_millis, date_modified_millis: a.date_modified_millis, last_scan_millis: a.last_scan_millis, is_favorite: !!a.is_favorite, is_hidden: !!a.is_hidden, rating: a.rating, tags, // Preview percorso per thumbnails FIT/GPX/TCX track_preview, scan_status: a.scan_status, scan_error: a.scan_error, // Importante per soft delete in gallery deleted_at: a.deleted_at || null, diagnostics }; } module.exports = normalizeActivity;