/** * Seeds the top-level `date_ideas` collection from date_ideas_seed.json. * * A LOCAL admin script — NOT a deployed callable (a deployed admin-write endpoint is avoidable * attack surface). Run with admin credentials; idempotent by document id, so re-running updates in * place rather than duplicating. Sponsored/seasonal ideas are edited here and pushed without an app * release; the Android app falls back to its shipped static seed when the collection is empty. * * Usage: * export GOOGLE_APPLICATION_CREDENTIALS=~/.config/closer/closer-admin-sa.json * npx ts-node functions/scripts/seedDateIdeas.ts # writes * npx ts-node functions/scripts/seedDateIdeas.ts --dry-run # prints what would change */ import * as admin from 'firebase-admin' import * as fs from 'fs' import * as path from 'path' interface SeedIdea { id: string title: string description?: string category?: string estimatedDuration?: string estimatedCost?: string isPremium?: boolean sponsored?: boolean sponsorName?: string externalUrl?: string } const DRY_RUN = process.argv.includes('--dry-run') function loadSeed(): SeedIdea[] { const file = path.join(__dirname, 'date_ideas_seed.json') const ideas = JSON.parse(fs.readFileSync(file, 'utf8')) as SeedIdea[] const ids = new Set() for (const idea of ideas) { if (!idea.id || !idea.title) throw new Error(`seed entry missing id/title: ${JSON.stringify(idea)}`) if (ids.has(idea.id)) throw new Error(`duplicate seed id: ${idea.id}`) ids.add(idea.id) if (idea.externalUrl && !idea.externalUrl.startsWith('https://')) { throw new Error(`externalUrl must be https: ${idea.id}`) } } return ideas } function toDoc(idea: SeedIdea): Record { const doc: Record = { title: idea.title, description: idea.description ?? '', category: idea.category ?? '', estimatedDuration: idea.estimatedDuration ?? '', estimatedCost: idea.estimatedCost ?? 'free', isPremium: idea.isPremium ?? false, sponsored: idea.sponsored ?? false, createdAt: Date.now(), } if (idea.sponsorName) doc.sponsorName = idea.sponsorName if (idea.externalUrl) doc.externalUrl = idea.externalUrl return doc } async function main() { const ideas = loadSeed() console.log(`Loaded ${ideas.length} date ideas (${ideas.filter((i) => i.sponsored).length} sponsored).`) if (DRY_RUN) { for (const idea of ideas) console.log(` would upsert date_ideas/${idea.id}`) console.log('Dry run — nothing written.') return } if (!admin.apps.length) admin.initializeApp() const db = admin.firestore() const col = db.collection('date_ideas') // Batch upsert (merge) by id — idempotent. let batch = db.batch() let n = 0 for (const idea of ideas) { batch.set(col.doc(idea.id), toDoc(idea), { merge: true }) if (++n % 400 === 0) { await batch.commit() batch = db.batch() } } await batch.commit() console.log(`Seeded ${ideas.length} ideas into date_ideas.`) } main().catch((err) => { console.error(err) process.exit(1) })