Files
spsays-training/scripts/seed-db.ts
T

508 lines
14 KiB
TypeScript

/**
* 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<T>(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)));
}
// ---------------------------------------------------------------------------
// 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 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<MongoClient["db"]>) {
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<string, unknown>[] = [];
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,
});
}
}
if (docs.length > 0) {
await versesCol.insertMany(docs, { ordered: false });
}
console.log(` ✅ BG: ${docs.length} verses`);
}
// ---------------------------------------------------------------------------
// SB
// ---------------------------------------------------------------------------
async function seedSb(db: ReturnType<MongoClient["db"]>) {
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 docs: Record<string, unknown>[] = [];
for (const chDir of chapterDirs) {
const versesDir = path.join(chaptersDir, chDir, "verses");
const verses = readAllJsonInDir(versesDir) as SbVerse[];
for (const v of verses) {
docs.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,
});
}
}
if (docs.length > 0) {
await versesCol.insertMany(docs, { ordered: false });
}
totalCount += docs.length;
console.log(` Canto ${cantoDir}: ${docs.length} verses`);
}
console.log(` ✅ SB: ${totalCount} verses`);
}
// ---------------------------------------------------------------------------
// CC
// ---------------------------------------------------------------------------
async function seedCc(db: ReturnType<MongoClient["db"]>) {
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 docs: Record<string, unknown>[] = [];
for (const chDir of chapterDirs) {
const versesDir = path.join(chaptersDir, chDir, "verses");
const verses = readAllJsonInDir(versesDir) as CcVerse[];
for (const v of verses) {
docs.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,
});
}
}
if (docs.length > 0) {
await versesCol.insertMany(docs, { ordered: false });
}
totalCount += docs.length;
console.log(` Līlā ${lilaDir}: ${docs.length} verses`);
}
console.log(` ✅ CC: ${totalCount} verses`);
}
// ---------------------------------------------------------------------------
// SB 1962
// ---------------------------------------------------------------------------
async function seedSb1962(db: ReturnType<MongoClient["db"]>) {
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<string, unknown>[] = [];
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,
});
}
}
if (docs.length > 0) {
await versesCol.insertMany(docs, { ordered: false });
}
console.log(` ✅ SB 1962: ${docs.length} verses`);
}
// ---------------------------------------------------------------------------
// Glossary
// ---------------------------------------------------------------------------
async function seedGlossary(db: ReturnType<MongoClient["db"]>) {
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<GlossaryData>(glossaryPath);
const col = db.collection("glossary");
await col.deleteMany({});
const docs = data.entries.map((entry) => ({
term: entry.term,
definition: entry.definition,
definitionHtml: entry.definition_html,
}));
if (docs.length > 0) {
await col.insertMany(docs, { ordered: false });
}
console.log(` ✅ Glossary: ${docs.length} entries`);
}
// ---------------------------------------------------------------------------
// Create indexes (matching Prisma schema)
// ---------------------------------------------------------------------------
async function createIndexes(db: ReturnType<MongoClient["db"]>) {
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 });
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/6] Glossary");
await seedGlossary(db);
console.log();
console.log("[6/6] 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);
});