Files
Overheard/scripts/migrate-geohash-precision.js

86 lines
3.1 KiB
JavaScript

// 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}`);