From a2869c1d876cf63bb44744b5d3dd443aab0e4cfd Mon Sep 17 00:00:00 2001 From: Alexander Bocken Date: Sun, 24 May 2026 10:14:01 +0200 Subject: [PATCH] feat(hikes): anonymize GPX timestamps to 08:00 today Re-base every track + image timestamp so each hike starts at 08:00 on the build date, preserving all relative timing (total duration, per-stage gaps, photo "nach X"). The per-hike track JSON is the single source for the page metrics and the client-built GPX download, so both come out anonymized; the real recording times stay only in the private source track.gpx. Also close two stale-data leaks that would otherwise still expose real times: sweep prior-build track.*.json (keep only the current hash) and remove orphan slug dirs from static/ (renamed/deleted hikes). --- package.json | 2 +- scripts/build-hikes.ts | 69 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index fc6d13cf..3687002b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homepage", - "version": "1.85.3", + "version": "1.86.0", "private": true, "type": "module", "scripts": { diff --git a/scripts/build-hikes.ts b/scripts/build-hikes.ts index d95649c1..d2550f7d 100644 --- a/scripts/build-hikes.ts +++ b/scripts/build-hikes.ts @@ -1064,6 +1064,34 @@ async function buildHike(slug: string, cache: GeocodeCache): Promise /^track\..*\.json$/.test(f) && f !== keep + ); + await Promise.all(stale.map((f) => fs.unlink(path.join(dir, f)).catch(() => {}))); + if (stale.length > 0) { + console.log(`[build-hikes:${slug}] removed ${stale.length} stale track JSON(s)`); + } + } + const date = typeof fm.date === 'string' ? fm.date : (typeof fm.date === 'number' ? new Date(fm.date).toISOString().slice(0, 10) : new Date().toISOString().slice(0, 10)); @@ -1378,6 +1422,31 @@ async function main() { if (entry) hikes.push(entry); } + // Sweep whole orphan slug dirs from static/ — e.g. a renamed or deleted + // hike. Otherwise its old per-slug track JSON (with the real, un-anonymised + // recording times) keeps shipping at a guessable URL. Keep current content + // slugs and any special "_*" entry (e.g. the overview hero). Guarded by a + // non-empty slug list so a failed content read never wipes everything. + if (slugs.length > 0) { + try { + const keep = new Set(slugs); + const present = await fs.readdir(STATIC_DIR, { withFileTypes: true }); + const orphans = present.filter( + (e) => e.isDirectory() && !e.name.startsWith('_') && !keep.has(e.name) + ); + await Promise.all( + orphans.map((e) => fs.rm(path.join(STATIC_DIR, e.name), { recursive: true, force: true })) + ); + if (orphans.length > 0) { + console.log( + `[build-hikes] removed ${orphans.length} orphan slug dir(s) from static/: ${orphans.map((o) => o.name).join(', ')}` + ); + } + } catch { + // static/hikes may not exist yet on a clean checkout — nothing to sweep. + } + } + await saveGeocodeCache(cache); hikes.sort((a, b) => (a.date < b.date ? 1 : a.date > b.date ? -1 : 0));