""" Phase 2 — Step 2.2: public_features (Firestore → Postgres) Reads the single 'admin_settings/public_features' doc from Firestore and flattens each field into a key/value row in public_features. Run on VPS: docker compose exec backend python -m migration.migrate_public_features """ import asyncio import sys from sqlalchemy.dialects.postgresql import insert as pg_insert from settings.orm import PublicFeature from shared.firebase import init_firebase, get_db as get_firestore from migration.utils import AsyncPgSession, log_run, pg_count SCRIPT = "migrate_public_features" COLLECTION = "admin_settings" DOC_ID = "public_features" async def run() -> None: init_firebase() fs = get_firestore() if fs is None: print("ERROR: Firebase not initialised — check service account path.", file=sys.stderr) sys.exit(1) doc = fs.collection(COLLECTION).document(DOC_ID).get() if not doc.exists: print("No public_features document found in Firestore — skipping.") await log_run(SCRIPT, 0, 0, notes="source doc not found") return data = doc.to_dict() source_count = len(data) print(f"Source (Firestore): {source_count} fields in {COLLECTION}/{DOC_ID}") records = [{"key": k, "value": v} for k, v in data.items()] async with AsyncPgSession() as session: async with session.begin(): stmt = pg_insert(PublicFeature).values(records) stmt = stmt.on_conflict_do_nothing(index_elements=["key"]) await session.execute(stmt) dest_count = await pg_count(session, "public_features") print(f"Postgres public_features: {dest_count} rows ✓") await log_run(SCRIPT, source_count, dest_count) if __name__ == "__main__": asyncio.run(run())