feat: sva extracted and seeded

This commit is contained in:
gosangam
2026-04-30 19:45:17 +05:30
parent 1f2ac2198a
commit ae6a752787
47 changed files with 17163 additions and 205 deletions
+186
View File
@@ -0,0 +1,186 @@
import fs from "fs";
import path from "path";
import Link from "next/link";
import { PageHeader } from "@/components/page-header";
import { notFound } from "next/navigation";
import {
getBookMeta,
getSingleVerse,
getChapterVerseIndex,
type VerseIndexEntry,
type VerseRecord,
} from "@/lib/api";
import { ChapterViewer } from "@/app/books/chapter-viewer";
import type { MobileNavGroup } from "@/components/mobile-nav";
type NbsMeta = {
book: string;
title: string;
author: string;
totalChapters: number;
chapters: { number: number; title: string; totalVerses: number }[];
};
const NBS_JSON_DIR = path.join(process.cwd(), "lib", "jsons", "books", "nbs");
// Fallback functions that read from local JSON when DB is empty
async function getMetaWithFallback() {
const meta = await getBookMeta("nbs");
if (meta) return meta as unknown as NbsMeta;
const introPath = path.join(NBS_JSON_DIR, "intro.json");
if (!fs.existsSync(introPath)) return null;
try {
return JSON.parse(fs.readFileSync(introPath, "utf-8")) as NbsMeta;
} catch {
return null;
}
}
async function getVerseIndexWithFallback(
chapter: number,
): Promise<VerseIndexEntry[]> {
const index = await getChapterVerseIndex("nbs", null, chapter);
if (index.length > 0) return index;
// Fallback: read verse files from local JSON
const versesDir = path.join(
NBS_JSON_DIR,
"chapters",
String(chapter).padStart(2, "0"),
"verses",
);
if (!fs.existsSync(versesDir)) return [] as VerseIndexEntry[];
const files = fs
.readdirSync(versesDir)
.filter((f) => f.endsWith(".json"))
.sort();
return files.map((f): VerseIndexEntry => {
const v = JSON.parse(fs.readFileSync(path.join(versesDir, f), "utf-8"));
return {
verse: v.verse,
verseEnd: v.verse_end || null,
reference: v.reference,
};
});
}
async function getVerseWithFallback(
chapter: number,
verse: number,
): Promise<VerseRecord | null> {
const v = await getSingleVerse("nbs", null, chapter, verse);
if (v) return v;
// Fallback: read from local JSON
const versesDir = path.join(
NBS_JSON_DIR,
"chapters",
String(chapter).padStart(2, "0"),
"verses",
);
if (!fs.existsSync(versesDir)) return null;
const files = fs
.readdirSync(versesDir)
.filter((f) => f.endsWith(".json"))
.sort();
for (const f of files) {
const data = JSON.parse(fs.readFileSync(path.join(versesDir, f), "utf-8"));
if (data.verse === verse) {
return {
id: `nbs-${chapter}-${verse}`,
book: "nbs",
division: null,
chapter: data.chapter,
verse: data.verse,
verseEnd: data.verse_end || null,
reference: data.reference,
bookTitle: data.book_title,
devanagari: data.devanagari,
bengali: null,
transliteration: data.transliteration,
synonyms: data.synonyms,
translation: data.translation,
purport: data.purport,
};
}
}
return null;
}
export default async function VersePage({
params,
}: {
params: Promise<{ chapter: string; verse: string }>;
}) {
const { chapter, verse: verseParam } = await params;
const nbsMeta = await getMetaWithFallback();
if (!nbsMeta) notFound();
const chapterInfo = nbsMeta.chapters?.find(
(c) => c.number === parseInt(chapter),
);
if (!chapterInfo?.title) notFound();
const title: string = chapterInfo.title;
const verseIndex = await getVerseIndexWithFallback(parseInt(chapter));
if (verseIndex.length === 0) notFound();
const requestedVerse = parseInt(verseParam);
const verse = await getVerseWithFallback(parseInt(chapter), requestedVerse);
if (!verse) notFound();
// Build mobile nav groups — verses for the current chapter
const mobileNavGroups: MobileNavGroup[] = [
{
title: "Verses",
items: verseIndex.map((v) => ({
slug: String(v.verse),
label: v.verseEnd
? `Verse ${chapter}.${v.verse}–${v.verseEnd}`
: `Verse ${chapter}.${v.verse}`,
href: `/books/nbs/${chapter}/${v.verse}`,
type: "verse",
})),
},
];
return (
<div className="min-h-screen bg-background">
<PageHeader sticky wide>
<Link
href="/books/nbs"
className="text-sm text-muted-foreground hover:text-foreground"
>
&larr; Nārada-bhakti-sūtra
</Link>
<h1 className="mt-2 text-2xl font-bold text-foreground">
Chapter {chapter}: {title}
</h1>
</PageHeader>
<div className="w-full md:max-w-7xl mx-auto flex">
<ChapterViewer
initialVerse={verse}
initialVerseNum={requestedVerse}
verseIndex={verseIndex.map((v) => ({
verse: v.verse,
verseEnd: v.verseEnd ?? undefined,
reference: v.reference,
}))}
book="nbs"
division={null}
chapter={parseInt(chapter)}
basePath={`/books/nbs/${chapter}`}
showBengali={false}
mobileNavGroups={mobileNavGroups}
mobileNavBookTitle={`Chapter ${chapter}: ${title}`}
/>
</div>
</div>
);
}
+103
View File
@@ -0,0 +1,103 @@
import { redirect } from "next/navigation";
import { notFound } from "next/navigation";
import { getChapterVerseIndex } from "@/lib/api";
import { Card, CardContent } from "@/components/ui/card";
import { PageHeader } from "@/components/page-header";
import Link from "next/link";
import fs from "fs";
import path from "path";
const NBS_JSON_DIR = path.join(process.cwd(), "lib", "jsons", "books", "nbs");
type IntroSegment = {
slug: string;
title: string;
paragraphs: string[];
};
export default async function NbsChapterPage({
params,
}: {
params: Promise<{ chapter: string }>;
}) {
const { chapter } = await params;
// Handle intro segments (intro-introduction)
if (chapter.startsWith("intro-")) {
const rawSlug = chapter.replace(/^intro-/, "");
const filePath = path.join(NBS_JSON_DIR, "intro", `${rawSlug}.json`);
if (!fs.existsSync(filePath)) notFound();
let segment: IntroSegment;
try {
segment = JSON.parse(fs.readFileSync(filePath, "utf-8"));
} catch {
notFound();
}
return (
<div className="min-h-screen bg-background">
<PageHeader sticky wide>
<Link
href="/books/nbs"
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
>
&larr; Nārada-bhakti-sūtra
</Link>
<h1 className="mt-3 text-2xl font-bold text-foreground">
{segment.title}
</h1>
</PageHeader>
<main className="max-w-3xl mx-auto px-6 py-8">
<Card>
<CardContent className="py-8 px-6 sm:px-8">
<div className="space-y-4">
{segment.paragraphs.map((paragraph: string, i: number) => (
<div
key={i}
className="text-base text-foreground leading-relaxed"
dangerouslySetInnerHTML={{ __html: paragraph }}
/>
))}
</div>
</CardContent>
</Card>
</main>
</div>
);
}
// Handle numeric chapters — redirect to first verse
const chapterNum = parseInt(chapter);
if (isNaN(chapterNum)) notFound();
// Try DB first
const verseIndex = await getChapterVerseIndex("nbs", null, chapterNum);
// Fallback to local JSON
if (verseIndex.length === 0) {
const versesDir = path.join(
NBS_JSON_DIR,
"chapters",
String(chapterNum).padStart(2, "0"),
"verses",
);
if (fs.existsSync(versesDir)) {
const files = fs
.readdirSync(versesDir)
.filter((f: string) => f.endsWith(".json"))
.sort();
if (files.length > 0) {
const first = JSON.parse(
fs.readFileSync(path.join(versesDir, files[0]), "utf-8"),
);
redirect(`/books/nbs/${chapter}/${first.verse}`);
}
}
notFound();
}
redirect(`/books/nbs/${chapter}/${verseIndex[0].verse}`);
}