feat: add ssr
This commit is contained in:
@@ -0,0 +1,428 @@
|
||||
# Prabhupāda Says — Project Context
|
||||
|
||||
## What this project is
|
||||
|
||||
A Next.js app that converts legacy RTF files containing Vedic scriptures (Bhagavad-gītā, Śrīmad-Bhāgavatam, Caitanya-caritāmṛta, Kṛṣṇa Book, letters, magazines, etc.) into structured, browsable content with proper Unicode Sanskrit.
|
||||
|
||||
The original RTF files use **custom font encodings** (Indevr, ScaGoudy, Inbenr/Inbeni, RM Devanagari, Balaram, etc.) where ASCII characters map to Devanagari/Bengali glyphs and IAST diacritical marks.
|
||||
|
||||
---
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Next.js 16** with Turbopack, React 19, TypeScript
|
||||
- **Tailwind CSS v4** with `@tailwindcss/postcss`
|
||||
- **shadcn/ui** (base-nova style, neutral base color, lucide icons) — components: Button, Card, Badge, Tooltip, Input
|
||||
- **next-themes** for dark/light mode
|
||||
- **Prisma v6** with MongoDB (provider `prisma-client-js`)
|
||||
- **MongoDB** native driver (`mongodb` package) for seeding (Prisma requires replica set for writes)
|
||||
- **Python 3** with BeautifulSoup4 + `indic-transliteration` for HTML extraction scripts
|
||||
- **Database**: MongoDB at `DATABASE_URL` env variable with `?authSource=admin`
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
spsays-training/
|
||||
├── app/
|
||||
│ ├── page.tsx → "Prabhupāda Says" landing page (Books, Glossary, RTFs)
|
||||
│ ├── layout.tsx → ThemeProvider + TooltipProvider wrapper
|
||||
│ ├── globals.css → shadcn/Tailwind v4 CSS with dark mode
|
||||
│ ├── books/
|
||||
│ │ ├── page.tsx → Books index (BG, SB, CC, SB1962, KB)
|
||||
│ │ ├── chapter-viewer.tsx → Shared client component for verse-based books
|
||||
│ │ ├── extracted-verse-count.tsx → (i) tooltip for verse counts
|
||||
│ │ ├── bg/ → Bhagavad-gītā pages
|
||||
│ │ │ ├── page.tsx → Chapter list
|
||||
│ │ │ └── [chapter]/
|
||||
│ │ │ ├── page.tsx → Redirects to first verse
|
||||
│ │ │ └── [verse]/page.tsx → Single verse viewer
|
||||
│ │ ├── sb/ → Śrīmad-Bhāgavatam pages
|
||||
│ │ │ ├── page.tsx → Canto list
|
||||
│ │ │ └── [canto]/
|
||||
│ │ │ ├── page.tsx → Chapter list
|
||||
│ │ │ └── [chapter]/
|
||||
│ │ │ ├── page.tsx → Redirects to first verse
|
||||
│ │ │ └── [verse]/page.tsx → Single verse viewer
|
||||
│ │ ├── cc/ → Caitanya-caritāmṛta pages (same pattern, [lila]/[chapter]/[verse])
|
||||
│ │ ├── sb1962/ → 1962 SB First Canto (same pattern as BG, [chapter]/[verse])
|
||||
│ │ └── kb/ → Kṛṣṇa Book (prose, no verses)
|
||||
│ │ ├── page.tsx → Volume list
|
||||
│ │ └── [volume]/
|
||||
│ │ ├── page.tsx → Chapter list + intro segments
|
||||
│ │ └── [slug]/page.tsx → Reading page with sidebar
|
||||
│ ├── glossary/
|
||||
│ │ ├── page.tsx → Server component wrapper
|
||||
│ │ └── glossary-client.tsx → Searchable client component
|
||||
│ ├── rtfs/
|
||||
│ │ ├── page.tsx → RTF file listing (gated by ALLOW_RTF env)
|
||||
│ │ └── [slug]/page.tsx → RTF iframe viewer (gated by ALLOW_RTF env)
|
||||
│ └── api/
|
||||
│ ├── books/
|
||||
│ │ ├── route.ts → GET all book metas
|
||||
│ │ └── [book]/
|
||||
│ │ ├── route.ts → GET single book meta
|
||||
│ │ └── verses/route.ts → GET verses (?chapter=&verse=&division=&indexOnly=&page=&limit=)
|
||||
│ └── glossary/route.ts → GET all glossary entries
|
||||
├── components/
|
||||
│ ├── ui/ → shadcn components (button, card, badge, tooltip, input)
|
||||
│ ├── page-header.tsx → Shared header with ThemeToggle
|
||||
│ ├── theme-provider.tsx → next-themes wrapper
|
||||
│ └── theme-toggle.tsx → Sun/moon toggle
|
||||
├── lib/
|
||||
│ ├── db.ts → Prisma client singleton
|
||||
│ ├── api.ts → Data access layer (getAllBooks, getBookMeta, getChapterVerses, etc.)
|
||||
│ ├── utils.ts → cn() helper
|
||||
│ └── jsons/books/ → Extracted JSON data
|
||||
│ ├── bg/ → BG chapters/verses/*.json + intro.json
|
||||
│ ├── sb/ → SB cantos/chapters/verses/*.json + intro.json
|
||||
│ ├── cc/ → CC lilas/chapters/verses/*.json + intro.json
|
||||
│ ├── sb-1962/ → 1962 SB chapters/verses/*.json + intro.json
|
||||
│ ├── kb/ → Kṛṣṇa Book
|
||||
│ │ ├── intro.json → Book metadata + chapter listing (89 chapters)
|
||||
│ │ ├── vol-1/intro/ → 5 intro segments (flap, dedication, George Harrison, preface, intro)
|
||||
│ │ ├── vol-1/chapters/ → 54 chapter JSONs
|
||||
│ │ └── vol-2/chapters/ → 35 chapter JSONs
|
||||
│ └── glossary/index.json → 2,749 glossary entries
|
||||
├── prisma/
|
||||
│ └── schema.prisma → Verse, BookMeta, ProseChapter, GlossaryEntry models
|
||||
├── scripts/
|
||||
│ ├── bg_extract.py → BG extraction from bg.html
|
||||
│ ├── sb_extract.py → SB extraction from sb.html
|
||||
│ ├── cc_extract.py → CC extraction from cc.html
|
||||
│ ├── sb1962_extract.py → 1962 SB extraction from others-before-1977.html
|
||||
│ ├── match_sb1962.py → Match 1962 SB transliterations with standard SB IAST
|
||||
│ ├── glossary_extract.py → Glossary extraction from glossary.html
|
||||
│ ├── kb_extract_intro.py → KB book-level intro + chapter listing
|
||||
│ ├── kb_extract_vol1_intro.py → KB Vol 1 front matter segments
|
||||
│ ├── kb_extract_vol1_chapters.py → KB Vol 1 chapters
|
||||
│ ├── kb_extract_vol2_chapters.py → KB Vol 2 chapters
|
||||
│ ├── fix-bengali-encoding.py → Fix Bengali font encoding in cc.html
|
||||
│ ├── inject-fonts.py → Inject @font-face rules into converted HTML
|
||||
│ ├── seed-db.ts → Main MongoDB seeder (all books + glossary)
|
||||
│ └── seed-kb-vol2.ts → KB Vol 2 seeder (uses $push workaround)
|
||||
└── public/
|
||||
├── rtfs/ → Original RTF files (~430MB, 17 files)
|
||||
├── html/ → HTML converted from RTF (textutil + @font-face injection)
|
||||
└── fonts/ → Custom TTF fonts (Balaram, ScaGoudy, Indevr, Inbenr, etc.)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database Schema (Prisma + MongoDB)
|
||||
|
||||
```prisma
|
||||
type Synonym {
|
||||
word String
|
||||
meanings String[]
|
||||
}
|
||||
|
||||
model Verse {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
book String // "bg" | "sb" | "cc" | "sb1962"
|
||||
division String? // null for BG/SB1962; canto for SB; lila for CC
|
||||
chapter Int
|
||||
verse Int
|
||||
verseEnd Int?
|
||||
reference String
|
||||
bookTitle String
|
||||
devanagari String
|
||||
bengali String? // only CC has Bengali
|
||||
transliteration String
|
||||
synonyms Synonym[]
|
||||
translation String
|
||||
purport String[]
|
||||
|
||||
@@unique([book, division, chapter, verse])
|
||||
@@index([book])
|
||||
@@index([book, division])
|
||||
@@index([book, division, chapter])
|
||||
@@map("verses")
|
||||
}
|
||||
|
||||
model BookMeta {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
book String @unique // "bg" | "sb" | "cc" | "sb1962" | "kb"
|
||||
data Json
|
||||
|
||||
@@map("book_meta")
|
||||
}
|
||||
|
||||
model ProseChapter {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
book String // "kb"
|
||||
volume Int
|
||||
chapter Int? // null for intro segments
|
||||
title String
|
||||
type String // "chapter" | "intro-segment"
|
||||
slug String
|
||||
order Int
|
||||
paragraphs String[]
|
||||
|
||||
@@unique([book, volume, slug])
|
||||
@@index([book])
|
||||
@@index([book, volume])
|
||||
@@index([book, volume, type])
|
||||
@@map("prose_chapters")
|
||||
}
|
||||
|
||||
model GlossaryEntry {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
term String
|
||||
definition String
|
||||
definitionHtml String
|
||||
|
||||
@@unique([term])
|
||||
@@map("glossary")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Books Extracted
|
||||
|
||||
| Book | Code | Type | Verses/Chapters | Division |
|
||||
|------|------|------|-----------------|----------|
|
||||
| Bhagavad-gītā As It Is | `bg` | verse | 653 verses, 18 chapters | — |
|
||||
| Śrīmad-Bhāgavatam | `sb` | verse | 8,260 verses, 213 chapters | 10 cantos |
|
||||
| Śrī Caitanya-caritāmṛta | `cc` | verse | 11,348 verses, 62 chapters | 3 līlās (adi/madhya/antya) |
|
||||
| SB First Canto (1962 Edition) | `sb1962` | verse | 762 verses, 19 chapters | — |
|
||||
| Kṛṣṇa Book | `kb` | prose | 89 chapters (54+35), 2 volumes | 2 volumes |
|
||||
| Glossary | — | glossary | 2,749 entries | — |
|
||||
|
||||
---
|
||||
|
||||
## Font Encoding System
|
||||
|
||||
### ScaGoudy → IAST mapping (used in BG, SB, CC, glossary)
|
||||
|
||||
```
|
||||
ä→ā Ä→Ā å→ṛ Å→Ṛ ñ→ṣ Ñ→Ṣ ö→ṭ Ö→Ṭ ë→ṇ Ë→Ṇ ò→ḍ Ò→Ḍ
|
||||
ç→ś Ç→Ś ì→ṅ ï→ñ ü→ū Ü→Ū é→ī É→Ī à→ṁ ù→ḥ è→ṝ û→ṁl ÿ→ḷ
|
||||
```
|
||||
|
||||
### Bengali fonts (Inbenr/Inbeni in CC)
|
||||
|
||||
Custom-encoded fonts where ASCII codepoints render as Bengali script. The cc.html had **Apple Symbol encoding corruption** — `textutil` mapped bytes through Symbol encoding instead of PUA. Fixed by `fix-bengali-encoding.py` which remaps 356,396 characters back to PUA range U+F0xx.
|
||||
|
||||
### Devanagari generation
|
||||
|
||||
IAST transliteration → Devanagari via `indic-transliteration` library. Key fixes:
|
||||
- `join_virama_words()` — joins virāma + consonant AND virāma + vowel (fixed 12,000+ rendering issues in SB)
|
||||
- `normalize_devanagari_anusvara()` — ङ् → ं before velars, ञ् → ं before palatals
|
||||
|
||||
---
|
||||
|
||||
## Verse Navigation (verse-based books)
|
||||
|
||||
- **One verse per page** with path-based URLs: `/books/bg/1/5`, `/books/sb/6/6/1`, `/books/cc/adi/1/3`
|
||||
- **ChapterViewer** (`app/books/chapter-viewer.tsx`): Client component with:
|
||||
- Sidebar showing ALL verse numbers (from lightweight index query)
|
||||
- Prev/Next buttons at top and bottom
|
||||
- Keyboard navigation (← →)
|
||||
- Client-side verse caching + prefetching adjacent verses
|
||||
- Click any verse in sidebar → fetches from API and navigates
|
||||
- **API route** supports: `?verse=N` (single), `?indexOnly=true` (lightweight index), `?page=&limit=` (paginated)
|
||||
- Server component SSRs the first verse, client handles navigation
|
||||
|
||||
---
|
||||
|
||||
## KB (Kṛṣṇa Book) — Prose Book
|
||||
|
||||
Different from verse books — chapters are prose paragraphs, no verse structure:
|
||||
- `ProseChapter` model with `paragraphs: String[]`
|
||||
- 5 intro segments per volume (flap, dedication, George Harrison intro, preface, introduction)
|
||||
- Sanskrit verses within prose are grouped into `<div style="text-align:center"><i>...</i></div>` blocks with `<br>` tags
|
||||
- Reading page has left sidebar with chapter index, prev/next navigation
|
||||
|
||||
---
|
||||
|
||||
## Data Access Layer (`lib/api.ts`)
|
||||
|
||||
```typescript
|
||||
// Book meta
|
||||
getAllBooks(): Promise<BookMeta[]>
|
||||
getBookMeta(book: string): Promise<BookMeta | null>
|
||||
|
||||
// Verse index (lightweight — for sidebar)
|
||||
getChapterVerseIndex(book, division, chapter): Promise<VerseIndexEntry[]>
|
||||
|
||||
// Single verse
|
||||
getSingleVerse(book, division, chapter, verse): Promise<VerseRecord | null>
|
||||
|
||||
// Paginated verses
|
||||
getChapterVerses(book, division, chapter, page?, limit?): Promise<PaginatedVerses>
|
||||
|
||||
// Prose chapters (for KB)
|
||||
getProseChapters(book, volume, type?): Promise<ProseChapterRecord[]>
|
||||
getProseChapter(book, volume, slug): Promise<ProseChapterRecord | null>
|
||||
|
||||
// Glossary
|
||||
getAllGlossaryEntries(): Promise<GlossaryRecord[]>
|
||||
```
|
||||
|
||||
The data access layer includes `normalizeProse()` which handles mixed storage (some chapters stored with native arrays, some with stringified JSON due to BSON corruption workaround). It also includes `sanitizeHtml()` which strips `<script>`, `<style>`, `<iframe>`, event handlers from paragraphs.
|
||||
|
||||
---
|
||||
|
||||
## Known Issues
|
||||
|
||||
1. **MongoDB BSON wire corruption**: The MongoDB server (7.0.31) behind a proxy/tunnel occasionally corrupts large BSON documents during transmission. Workaround: seed using `insertOne` + `$push` for paragraphs one at a time. The `insertMany` and large `insertOne` operations fail non-deterministically with "Invalid UTF-8 string" or "Unrecognized BSON type" errors. Retrying usually works.
|
||||
|
||||
2. **SB verse numbering**: The HTML had 5 duplicate `p20` verse reference tags. Fixed by using the `p21` TEXT N header as the authoritative verse number (corrects the wrong `p20` reference).
|
||||
|
||||
3. **SB 1962 chapter 15**: Verses 41-45 have different numbering between the 1962 edition and standard SB. The matching script skips these 5 verses (keeps original plain English transliteration).
|
||||
|
||||
4. **RTF routes gated**: `/rtfs` and `/rtfs/[slug]` only accessible when `ALLOW_RTF=true` env variable is set.
|
||||
|
||||
5. **`scripts/` excluded from tsconfig**: Added to `"exclude"` array to prevent TypeScript build errors from seed scripts.
|
||||
|
||||
6. **MongoDB requires no replica set for reads** but requires replica set for Prisma writes. All writes use the native MongoDB driver instead of Prisma.
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```
|
||||
DATABASE_URL=mongodb://user:pass@host:port/spsays?authSource=admin
|
||||
ALLOW_RTF=true # optional — enables RTF viewer routes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Run
|
||||
|
||||
```bash
|
||||
# Development
|
||||
npm run dev # Start dev server
|
||||
|
||||
# Prisma
|
||||
npx prisma generate # Regenerate Prisma client after schema changes
|
||||
|
||||
# Extraction scripts (Python 3 + beautifulsoup4 + indic-transliteration)
|
||||
python3 scripts/bg_extract.py # Extract BG → lib/jsons/books/bg/
|
||||
python3 scripts/sb_extract.py # Extract SB → lib/jsons/books/sb/
|
||||
python3 scripts/cc_extract.py # Extract CC → lib/jsons/books/cc/
|
||||
python3 scripts/glossary_extract.py # Extract glossary → lib/jsons/glossary/
|
||||
python3 scripts/sb1962_extract.py # Extract 1962 SB → lib/jsons/books/sb-1962/
|
||||
python3 scripts/match_sb1962.py # Match 1962 SB with standard SB IAST
|
||||
python3 scripts/kb_extract_intro.py # Extract KB intro → lib/jsons/books/kb/intro.json
|
||||
python3 scripts/kb_extract_vol1_intro.py # KB Vol 1 front matter
|
||||
python3 scripts/kb_extract_vol1_chapters.py # KB Vol 1 chapters
|
||||
python3 scripts/kb_extract_vol2_chapters.py # KB Vol 2 chapters
|
||||
|
||||
# Database seeding (TypeScript + tsx)
|
||||
npx tsx scripts/seed-db.ts # Seed all data to MongoDB
|
||||
npx tsx scripts/seed-kb-vol2.ts # Seed KB Vol 2 (separate due to BSON issues)
|
||||
|
||||
# Build
|
||||
npx next build # Production build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## URL Routes
|
||||
|
||||
| Route | Description |
|
||||
|-------|-------------|
|
||||
| `/` | Landing page — Books, Glossary, RTF Viewer cards |
|
||||
| `/books` | All books index |
|
||||
| `/books/bg` | BG chapter list |
|
||||
| `/books/bg/[chapter]` | Redirects to first verse |
|
||||
| `/books/bg/[chapter]/[verse]` | Single verse viewer |
|
||||
| `/books/sb` | SB canto list |
|
||||
| `/books/sb/[canto]` | Chapter list for a canto |
|
||||
| `/books/sb/[canto]/[chapter]` | Redirects to first verse |
|
||||
| `/books/sb/[canto]/[chapter]/[verse]` | Single verse viewer |
|
||||
| `/books/cc` | CC līlā list |
|
||||
| `/books/cc/[lila]` | Chapter list for a līlā |
|
||||
| `/books/cc/[lila]/[chapter]` | Redirects to first verse |
|
||||
| `/books/cc/[lila]/[chapter]/[verse]` | Single verse viewer |
|
||||
| `/books/sb1962` | 1962 SB chapter list |
|
||||
| `/books/sb1962/[chapter]` | Redirects to first verse |
|
||||
| `/books/sb1962/[chapter]/[verse]` | Single verse viewer |
|
||||
| `/books/kb` | KB volume list |
|
||||
| `/books/kb/[volume]` | Chapter list + intro segments |
|
||||
| `/books/kb/[volume]/[slug]` | Prose reading page with sidebar |
|
||||
| `/glossary` | Searchable glossary (2,749 entries) |
|
||||
| `/rtfs` | RTF file listing (requires ALLOW_RTF=true) |
|
||||
| `/rtfs/[slug]` | RTF iframe viewer (requires ALLOW_RTF=true) |
|
||||
| `/api/books` | GET all book metas |
|
||||
| `/api/books/[book]` | GET single book meta |
|
||||
| `/api/books/[book]/verses` | GET verses with query params |
|
||||
| `/api/glossary` | GET all glossary entries |
|
||||
|
||||
---
|
||||
|
||||
## Verse JSON Schema (BG/SB/CC/SB1962)
|
||||
|
||||
```json
|
||||
{
|
||||
"book_title": "Śrīmad-Bhāgavatam",
|
||||
"canto": 1,
|
||||
"chapter": 1,
|
||||
"verse": 1,
|
||||
"verse_end": null,
|
||||
"reference": "SB 1.1.1",
|
||||
"devanagari": "ॐ नमो भगवते वासुदेवाय\nजन्माद्यस्य यतो ...",
|
||||
"bengali": null,
|
||||
"transliteration": "om namo bhagavate vāsudevāya\njanmādy asya yato ...",
|
||||
"synonyms": [
|
||||
{ "word": "om", "meanings": ["O my Lord"] },
|
||||
{ "word": "namaḥ", "meanings": ["offering my obeisances"] }
|
||||
],
|
||||
"translation": "<b>I offer my obeisances unto Lord Śrī Kṛṣṇa...</b>",
|
||||
"purport": ["Obeisances unto the Personality of Godhead...", "paragraph 2..."]
|
||||
}
|
||||
```
|
||||
|
||||
## Prose Chapter JSON Schema (KB)
|
||||
|
||||
```json
|
||||
{
|
||||
"book": "kb",
|
||||
"volume": 1,
|
||||
"chapter": 1,
|
||||
"title": "Advent of Lord Kṛṣṇa",
|
||||
"type": "chapter",
|
||||
"paragraphs": [
|
||||
"<b>1 /Advent of Lord Kṛṣṇa</b>",
|
||||
"Once the world was overburdened...",
|
||||
"<div style=\"text-align:center\"><i>verse line 1<br>verse line 2</i></div>",
|
||||
"<i>Thus ends the Bhaktivedanta purport of the First Chapter of </i>Kṛṣṇa..."
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Intro Segment JSON Schema (KB)
|
||||
|
||||
```json
|
||||
{
|
||||
"slug": "words-from-apple",
|
||||
"title": "Words from Apple — by George Harrison",
|
||||
"type": "intro-segment",
|
||||
"book": "kb",
|
||||
"volume": 1,
|
||||
"order": 3,
|
||||
"paragraphs": ["<b>Words From Apple</b>", "Everybody is looking for KṚṢṆA.", "..."]
|
||||
}
|
||||
```
|
||||
|
||||
## Glossary JSON Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "Glossary of Vedic Terms",
|
||||
"description": "A comprehensive glossary of Sanskrit and Vedic terms...",
|
||||
"totalEntries": 2749,
|
||||
"letters": ["A", "B", "C", ...],
|
||||
"entries": [
|
||||
{
|
||||
"term": "Ācārya",
|
||||
"definition": "a spiritual master who teaches by his own example...",
|
||||
"definition_html": "a spiritual master who teaches by his own example..."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user