// scripts/migrate-geohash-precision.js // // One-time migration: re-encodes every existing message's `geohash` field at // precision 9 (previously 6), matching the precision addMessage() now writes // for new messages (see src/lib/firebase/messages.js). lat/lng/text/etc are // read from each doc but never written back - updateDoc() below is only ever // called with { geohash: newGeohash }, so this cannot touch any other field. // // Firestore's security rules normally only allow `echoCount`/`lastEchoAt` to // change via update() (see firestore.rules). Before running this script, // temporarily add 'geohash' to that allowlist and deploy: // // firebase deploy --only firestore:rules // // Then run this script: // // node scripts/migrate-geohash-precision.js // // Then revert firestore.rules back to its original allowlist and redeploy. import { initializeApp } from 'firebase/app'; import { getFirestore, collection, getDocs, doc, updateDoc } from 'firebase/firestore'; import ngeohash from 'ngeohash'; import { readFileSync } from 'fs'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; // --- load PUBLIC_FIREBASE_* values from .env (same file SvelteKit reads via // $env/static/public, which only works inside SvelteKit - this script is a // plain Node script, so .env is parsed by hand here instead) -------------- const __dirname = dirname(fileURLToPath(import.meta.url)); const envPath = join(__dirname, '..', '.env'); const env = {}; for (const line of readFileSync(envPath, 'utf-8').split('\n')) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const eq = trimmed.indexOf('='); if (eq === -1) continue; env[trimmed.slice(0, eq).trim()] = trimmed.slice(eq + 1).trim(); } const firebaseConfig = { apiKey: env.PUBLIC_FIREBASE_API_KEY, authDomain: env.PUBLIC_FIREBASE_AUTH_DOMAIN, projectId: env.PUBLIC_FIREBASE_PROJECT_ID, storageBucket: env.PUBLIC_FIREBASE_STORAGE_BUCKET, messagingSenderId: env.PUBLIC_FIREBASE_MESSAGING_SENDER_ID, appId: env.PUBLIC_FIREBASE_APP_ID }; const NEW_PRECISION = 9; // matches addMessage() in src/lib/firebase/messages.js const app = initializeApp(firebaseConfig); const db = getFirestore(app); const snapshot = await getDocs(collection(db, 'messages')); console.log(`found ${snapshot.docs.length} message(s)`); let updated = 0; let skipped = 0; for (const docSnap of snapshot.docs) { const data = docSnap.data(); if (typeof data.lat !== 'number' || typeof data.lng !== 'number') { console.warn(`skipping ${docSnap.id}: missing lat/lng`); skipped++; continue; } const newGeohash = ngeohash.encode(data.lat, data.lng, NEW_PRECISION); if (data.geohash === newGeohash) { skipped++; continue; } // ONLY the geohash field is written - everything else on the doc is // left exactly as it was. await updateDoc(doc(db, 'messages', docSnap.id), { geohash: newGeohash }); console.log(`${docSnap.id}: ${data.geohash} -> ${newGeohash}`); updated++; } console.log(`done. updated ${updated}, skipped ${skipped}`);