/** * seed-db.ts — Push all extracted JSON data into MongoDB using the native driver. * * Prisma with MongoDB requires a replica set for writes (even createMany/deleteMany). * This script uses the MongoDB native driver directly to avoid that constraint. * The collections and field names match the Prisma schema so the frontend can * still read via Prisma without issues. * * Usage: * npx tsx scripts/seed-db.ts * * Prerequisites: * - DATABASE_URL must be set in .env * - All extraction scripts (bg, sb, cc, glossary) must have been run */ import dotenv from "dotenv"; import fs from "fs"; import path from "path"; import { MongoClient } from "mongodb"; // Load env dotenv.config({ path: ".env.local" }); dotenv.config({ path: ".env" }); const DATABASE_URL = process.env.DATABASE_URL; if (!DATABASE_URL) { console.error("❌ DATABASE_URL is not set in .env"); process.exit(1); } const BASE_DIR = path.resolve(__dirname, ".."); const LIB_DIR = path.join(BASE_DIR, "lib", "jsons"); // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- function readJson(filePath: string): T { return JSON.parse(fs.readFileSync(filePath, "utf-8")); } function readAllJsonInDir(dir: string): unknown[] { if (!fs.existsSync(dir)) return []; return fs .readdirSync(dir) .filter((f) => f.endsWith(".json")) .sort() .map((f) => readJson(path.join(dir, f))); } /** * Insert documents one at a time with a small delay every N docs. * * Uses insertOne to avoid BSON wire-protocol corruption that occurs * with insertMany on certain MongoDB server/proxy configurations. * A brief pause every 100 docs prevents connection overload. */ async function insertBatched( col: ReturnType["collection"]>, docs: Record[], ) { for (let i = 0; i < docs.length; i++) { await col.insertOne(docs[i]); // Pause briefly every 100 docs to give the connection breathing room if ((i + 1) % 100 === 0) { await new Promise((r) => setTimeout(r, 100)); } } } // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- type BgVerse = { book_title: string; chapter: number; verse: number; verse_end?: number; reference: string; devanagari: string; transliteration: string; synonyms: { word: string; meanings: string[] }[]; translation: string; purport: string[]; }; type SbVerse = { book_title: string; canto: number; chapter: number; verse: number; verse_end?: number; reference: string; devanagari: string; transliteration: string; synonyms: { word: string; meanings: string[] }[]; translation: string; purport: string[]; }; type CcVerse = { book_title: string; lila: string; chapter: number; verse: number; verse_end?: number; reference: string; devanagari: string; bengali: string; transliteration: string; synonyms: { word: string; meanings: string[] }[]; translation: string; purport: string[]; }; type ProseChapterJson = { book: string; volume: number; chapter?: number; title: string; type: string; slug?: string; order?: number; paragraphs: string[]; }; type Sb1962Verse = { book_title: string; chapter: number; verse: number; verse_end?: number; reference: string; transliteration: string; synonyms: { word: string; meanings: string[] }[]; translation: string; purport: string[]; }; type GlossaryData = { title: string; description: string; totalEntries: number; letters: string[]; entries: { term: string; definition: string; definition_html: string; }[]; }; // --------------------------------------------------------------------------- // BG // --------------------------------------------------------------------------- async function seedBg(db: ReturnType) { const introPath = path.join(LIB_DIR, "books", "bg", "intro.json"); if (!fs.existsSync(introPath)) { console.log(" ⏭ BG intro.json not found, skipping"); return; } const intro = readJson(introPath); // Book meta const metaCol = db.collection("book_meta"); await metaCol.deleteMany({ book: "bg" }); await metaCol.insertOne({ book: "bg", data: intro }); console.log(" 📖 BG book meta inserted"); // Verses const versesCol = db.collection("verses"); await versesCol.deleteMany({ book: "bg" }); const chaptersDir = path.join(LIB_DIR, "books", "bg", "chapters"); if (!fs.existsSync(chaptersDir)) { console.log(" ⏭ BG chapters dir not found, skipping verses"); return; } const chapterDirs = fs .readdirSync(chaptersDir) .filter((d) => fs.statSync(path.join(chaptersDir, d)).isDirectory()) .sort(); const docs: Record[] = []; for (const chDir of chapterDirs) { const versesDir = path.join(chaptersDir, chDir, "verses"); const verses = readAllJsonInDir(versesDir) as BgVerse[]; for (const v of verses) { docs.push({ book: "bg", division: null, chapter: v.chapter, verse: v.verse, verseEnd: v.verse_end ?? null, reference: v.reference, bookTitle: v.book_title, devanagari: v.devanagari, bengali: null, transliteration: v.transliteration, synonyms: v.synonyms, translation: v.translation, purport: v.purport, }); } } await insertBatched(versesCol, docs); console.log(` ✅ BG: ${docs.length} verses`); } // --------------------------------------------------------------------------- // SB // --------------------------------------------------------------------------- async function seedSb(db: ReturnType) { const introPath = path.join(LIB_DIR, "books", "sb", "intro.json"); if (!fs.existsSync(introPath)) { console.log(" ⏭ SB intro.json not found, skipping"); return; } const intro = readJson(introPath); const metaCol = db.collection("book_meta"); await metaCol.deleteMany({ book: "sb" }); await metaCol.insertOne({ book: "sb", data: intro }); console.log(" 📖 SB book meta inserted"); const versesCol = db.collection("verses"); await versesCol.deleteMany({ book: "sb" }); const cantosDir = path.join(LIB_DIR, "books", "sb", "cantos"); if (!fs.existsSync(cantosDir)) { console.log(" ⏭ SB cantos dir not found, skipping verses"); return; } const cantoDirs = fs .readdirSync(cantosDir) .filter((d) => fs.statSync(path.join(cantosDir, d)).isDirectory()) .sort(); let totalCount = 0; for (const cantoDir of cantoDirs) { const chaptersDir = path.join(cantosDir, cantoDir, "chapters"); if (!fs.existsSync(chaptersDir)) continue; const chapterDirs = fs .readdirSync(chaptersDir) .filter((d) => fs.statSync(path.join(chaptersDir, d)).isDirectory()) .sort(); const cantoDocs: Record[] = []; for (const chDir of chapterDirs) { const versesDir = path.join(chaptersDir, chDir, "verses"); const verses = readAllJsonInDir(versesDir) as SbVerse[]; for (const v of verses) { cantoDocs.push({ book: "sb", division: String(v.canto), chapter: v.chapter, verse: v.verse, verseEnd: v.verse_end ?? null, reference: v.reference, bookTitle: v.book_title, devanagari: v.devanagari, bengali: null, transliteration: v.transliteration, synonyms: v.synonyms, translation: v.translation, purport: v.purport, }); } } await insertBatched(versesCol, cantoDocs); totalCount += cantoDocs.length; console.log(` Canto ${cantoDir}: ${cantoDocs.length} verses`); } console.log(` ✅ SB: ${totalCount} verses`); } // --------------------------------------------------------------------------- // CC // --------------------------------------------------------------------------- async function seedCc(db: ReturnType) { const introPath = path.join(LIB_DIR, "books", "cc", "intro.json"); if (!fs.existsSync(introPath)) { console.log(" ⏭ CC intro.json not found, skipping"); return; } const intro = readJson(introPath); const metaCol = db.collection("book_meta"); await metaCol.deleteMany({ book: "cc" }); await metaCol.insertOne({ book: "cc", data: intro }); console.log(" 📖 CC book meta inserted"); const versesCol = db.collection("verses"); await versesCol.deleteMany({ book: "cc" }); const lilasDir = path.join(LIB_DIR, "books", "cc", "lilas"); if (!fs.existsSync(lilasDir)) { console.log(" ⏭ CC lilas dir not found, skipping verses"); return; } const lilaDirs = fs .readdirSync(lilasDir) .filter((d) => fs.statSync(path.join(lilasDir, d)).isDirectory()) .sort(); let totalCount = 0; for (const lilaDir of lilaDirs) { const chaptersDir = path.join(lilasDir, lilaDir, "chapters"); if (!fs.existsSync(chaptersDir)) continue; const chapterDirs = fs .readdirSync(chaptersDir) .filter((d) => fs.statSync(path.join(chaptersDir, d)).isDirectory()) .sort(); const lilaDocs: Record[] = []; for (const chDir of chapterDirs) { const versesDir = path.join(chaptersDir, chDir, "verses"); const verses = readAllJsonInDir(versesDir) as CcVerse[]; for (const v of verses) { lilaDocs.push({ book: "cc", division: v.lila, chapter: v.chapter, verse: v.verse, verseEnd: v.verse_end ?? null, reference: v.reference, bookTitle: v.book_title, devanagari: v.devanagari, bengali: v.bengali, transliteration: v.transliteration, synonyms: v.synonyms, translation: v.translation, purport: v.purport, }); } } await insertBatched(versesCol, lilaDocs); totalCount += lilaDocs.length; console.log(` Līlā ${lilaDir}: ${lilaDocs.length} verses`); } console.log(` ✅ CC: ${totalCount} verses`); } // --------------------------------------------------------------------------- // SB 1962 // --------------------------------------------------------------------------- async function seedSb1962(db: ReturnType) { const introPath = path.join(LIB_DIR, "books", "sb-1962", "intro.json"); if (!fs.existsSync(introPath)) { console.log(" ⏭ SB 1962 intro.json not found, skipping"); return; } const intro = readJson(introPath); const metaCol = db.collection("book_meta"); await metaCol.deleteMany({ book: "sb1962" }); await metaCol.insertOne({ book: "sb1962", data: intro }); console.log(" 📖 SB 1962 book meta inserted"); const versesCol = db.collection("verses"); await versesCol.deleteMany({ book: "sb1962" }); const chaptersDir = path.join(LIB_DIR, "books", "sb-1962", "chapters"); if (!fs.existsSync(chaptersDir)) { console.log(" ⏭ SB 1962 chapters dir not found, skipping verses"); return; } const chapterDirs = fs .readdirSync(chaptersDir) .filter((d) => fs.statSync(path.join(chaptersDir, d)).isDirectory()) .sort(); const docs: Record[] = []; for (const chDir of chapterDirs) { const versesDir = path.join(chaptersDir, chDir, "verses"); const verses = readAllJsonInDir(versesDir) as Sb1962Verse[]; for (const v of verses) { docs.push({ book: "sb1962", division: null, chapter: v.chapter, verse: v.verse, verseEnd: v.verse_end ?? null, reference: v.reference, bookTitle: v.book_title, devanagari: "", bengali: null, transliteration: v.transliteration, synonyms: v.synonyms, translation: v.translation, purport: v.purport, }); } } await insertBatched(versesCol, docs); console.log(` ✅ SB 1962: ${docs.length} verses`); } // --------------------------------------------------------------------------- // Kṛṣṇa Book // --------------------------------------------------------------------------- async function seedKb(db: ReturnType) { const introPath = path.join(LIB_DIR, "books", "kb", "intro.json"); if (!fs.existsSync(introPath)) { console.log(" ⏭ KB intro.json not found, skipping"); return; } const intro = readJson(introPath); // Book meta const metaCol = db.collection("book_meta"); await metaCol.deleteMany({ book: "kb" }); await metaCol.insertOne({ book: "kb", data: intro }); console.log(" 📖 KB book meta inserted"); // Prose chapters collection const proseCol = db.collection("prose_chapters"); await proseCol.deleteMany({ book: "kb" }); const docs: Record[] = []; // Volume 1 intro segments const vol1IntroDir = path.join(LIB_DIR, "books", "kb", "vol-1", "intro"); if (fs.existsSync(vol1IntroDir)) { const introFiles = fs .readdirSync(vol1IntroDir) .filter((f) => f.endsWith(".json")) .sort(); const introDocsV1: Record[] = []; for (const file of introFiles) { const seg = readJson(path.join(vol1IntroDir, file)); introDocsV1.push({ book: "kb", volume: seg.volume ?? 1, chapter: seg.chapter ?? null, title: seg.title, type: seg.type || "intro-segment", slug: seg.slug || file.replace(".json", ""), order: seg.order ?? 0, paragraphs: seg.paragraphs, }); } await insertBatched(proseCol, introDocsV1); console.log(` Vol 1 intro segments: ${introFiles.length}`); } // Volume 1 chapters const vol1ChapDir = path.join(LIB_DIR, "books", "kb", "vol-1", "chapters"); if (fs.existsSync(vol1ChapDir)) { const chapterFiles = fs .readdirSync(vol1ChapDir) .filter((f) => f.endsWith(".json")) .sort(); const chapDocsV1: Record[] = []; for (const file of chapterFiles) { const ch = readJson(path.join(vol1ChapDir, file)); chapDocsV1.push({ book: "kb", volume: ch.volume ?? 1, chapter: ch.chapter ?? null, title: ch.title, type: ch.type || "chapter", slug: String(ch.chapter ?? file.replace(".json", "")).padStart(2, "0"), order: 100 + (ch.chapter ?? 0), paragraphs: ch.paragraphs, }); } await insertBatched(proseCol, chapDocsV1); console.log(` Vol 1 chapters: ${chapterFiles.length}`); } // Volume 2 intro segments const vol2IntroDir = path.join(LIB_DIR, "books", "kb", "vol-2", "intro"); if (fs.existsSync(vol2IntroDir)) { const introFiles = fs .readdirSync(vol2IntroDir) .filter((f) => f.endsWith(".json")) .sort(); const introDocsV2: Record[] = []; for (const file of introFiles) { const seg = readJson(path.join(vol2IntroDir, file)); introDocsV2.push({ book: "kb", volume: seg.volume ?? 2, chapter: seg.chapter ?? null, title: seg.title, type: seg.type || "intro-segment", slug: seg.slug || file.replace(".json", ""), order: seg.order ?? 0, paragraphs: seg.paragraphs, }); } await insertBatched(proseCol, introDocsV2); console.log(` Vol 2 intro segments: ${introFiles.length}`); } // Volume 2 chapters const vol2ChapDir = path.join(LIB_DIR, "books", "kb", "vol-2", "chapters"); if (fs.existsSync(vol2ChapDir)) { const chapterFiles = fs .readdirSync(vol2ChapDir) .filter((f) => f.endsWith(".json")) .sort(); const chapDocsV2: Record[] = []; for (const file of chapterFiles) { const ch = readJson(path.join(vol2ChapDir, file)); chapDocsV2.push({ book: "kb", volume: ch.volume ?? 2, chapter: ch.chapter ?? null, title: ch.title, type: ch.type || "chapter", slug: String(ch.chapter ?? file.replace(".json", "")).padStart(2, "0"), order: 100 + (ch.chapter ?? 0), paragraphs: ch.paragraphs, }); } await insertBatched(proseCol, chapDocsV2); console.log(` Vol 2 chapters: ${chapterFiles.length}`); } const totalDocs = await proseCol.countDocuments({ book: "kb" }); console.log(` ✅ KB: ${totalDocs} prose entries`); } // --------------------------------------------------------------------------- // Glossary // --------------------------------------------------------------------------- async function seedGlossary(db: ReturnType) { const glossaryPath = path.join(LIB_DIR, "glossary", "index.json"); if (!fs.existsSync(glossaryPath)) { console.log(" ⏭ Glossary index.json not found, skipping"); return; } const data = readJson(glossaryPath); const col = db.collection("glossary"); await col.deleteMany({}); const glossaryDocs = data.entries.map((entry) => ({ term: entry.term, definition: entry.definition, definitionHtml: entry.definition_html, })); await insertBatched(col, glossaryDocs); console.log(` ✅ Glossary: ${glossaryDocs.length} entries`); } // --------------------------------------------------------------------------- // Create indexes (matching Prisma schema) // --------------------------------------------------------------------------- async function createIndexes(db: ReturnType) { console.log(" 📇 Creating indexes..."); const versesCol = db.collection("verses"); await versesCol.createIndex({ book: 1 }); await versesCol.createIndex({ book: 1, division: 1 }); await versesCol.createIndex({ book: 1, division: 1, chapter: 1 }); await versesCol.createIndex( { book: 1, division: 1, chapter: 1, verse: 1 }, { unique: true }, ); const metaCol = db.collection("book_meta"); await metaCol.createIndex({ book: 1 }, { unique: true }); const glossaryCol = db.collection("glossary"); await glossaryCol.createIndex({ term: 1 }, { unique: true }); const proseCol = db.collection("prose_chapters"); await proseCol.createIndex({ book: 1 }); await proseCol.createIndex({ book: 1, volume: 1 }); await proseCol.createIndex({ book: 1, volume: 1, slug: 1 }, { unique: true }); await proseCol.createIndex({ book: 1, volume: 1, type: 1 }); console.log(" ✅ Indexes created"); } // --------------------------------------------------------------------------- // Main // --------------------------------------------------------------------------- async function main() { console.log("🌱 Seeding database...\n"); const client = new MongoClient(DATABASE_URL!); await client.connect(); console.log("📡 Connected to MongoDB\n"); const db = client.db(); // uses the database name from the URL const start = Date.now(); console.log("[1/6] Bhagavad-gītā"); await seedBg(db); console.log(); console.log("[2/6] Śrīmad-Bhāgavatam"); await seedSb(db); console.log(); console.log("[3/6] Śrī Caitanya-caritāmṛta"); await seedCc(db); console.log(); console.log("[4/6] Śrīmad-Bhāgavatam (1962 Edition)"); await seedSb1962(db); console.log(); console.log("[5/7] Kṛṣṇa Book"); await seedKb(db); console.log(); console.log("[6/7] Glossary"); await seedGlossary(db); console.log(); console.log("[7/7] Indexes"); await createIndexes(db); console.log(); const elapsed = ((Date.now() - start) / 1000).toFixed(1); console.log(`🎉 Done in ${elapsed}s`); await client.close(); } main().catch((e) => { console.error("❌ Seed failed:", e); process.exit(1); });