feat: add books APIs

This commit is contained in:
gosangam
2026-05-09 23:19:19 +05:30
commit b055b8ba0c
43 changed files with 4307 additions and 0 deletions
+417
View File
@@ -0,0 +1,417 @@
# Prabhupāda Says — BFF (Backend for Frontend)
## What this project is
A Go-based Backend for Frontend (BFF) API server for [prabhupadasays.org](https://prabhupadasays.org). It sits between the Next.js frontend (and future mobile apps) and the data layer — aggregating content from MongoDB, user data from PostgreSQL, search from Meilisearch, caching from DragonflyDB, and media from S3 into a unified API surface.
---
## Tech Stack
- **Go 1.23** — API server
- **Chi v5** — lightweight HTTP router with middleware support
- **Zap** — structured logging (uber-go/zap)
- **MongoDB** — content database (books, verses, letters, essays, glossary, disciples, transcriptions)
- **PostgreSQL** — relational data (users, bookmarks, collections, mentions, URL shortener)
- **Meilisearch** — typo-tolerant full-text search across all content
- **DragonflyDB** — Redis-compatible cache (API responses, rate limiting, sessions)
- **AWS S3** — media storage (avatars, images, audio files)
- **FCM + APNs** — push notifications for Android and iOS
- **JWT** — stateless authentication
---
## Architecture
```
┌──────────────┐ ┌─────────────────────────────────────────┐
│ Next.js │────▶│ Go BFF API (:8080) │
│ (frontend) │ │ │
├──────────────┤ │ cmd/api/main.go → server.go → handlers │
│ Mobile Apps │────▶│ │
│ (future) │ │ ┌──────────┐ ┌───────────────┐ │
└──────────────┘ │ │ Handlers │──│ Services │ │
│ └──────────┘ └───────┬───────┘ │
│ │ │
│ ┌─────────────────────┼──────────┐ │
│ ▼ ▼ ▼ ▼ ▼ │
│ Mongo Postgres Meili S3 Dragonfly│
│ (content)(users, (search)(media)(cache)│
│ mentions, │
│ URL short, │
│ relations) │
└─────────────────────────────────────────┘
```
---
## Project Structure
```
spsays-bff/
├── cmd/
│ └── api/
│ └── main.go → Entry point (config, logger, server, graceful shutdown)
├── internal/
│ ├── config/
│ │ └── config.go → Environment config (all services: Mongo, Postgres, Meili, S3, etc.)
│ ├── server/
│ │ └── server.go → Chi router setup, global middleware, route registration
│ ├── handler/ → HTTP handlers (one file per domain)
│ │ ├── health.go → Health check endpoint
│ │ ├── book.go → Book routes (SB, CC, KB, BG, generic verse & prose)
│ │ ├── letter.go → Letter routes (by year, recipient, location)
│ │ ├── essay.go → Essay routes
│ │ ├── glossary.go → Glossary routes (letters, terms)
│ │ ├── disciple.go → Disciple routes (filter, search, aggregations)
│ │ ├── journal.go → Journal routes
│ │ ├── magazine.go → Magazine routes
│ │ ├── other_document.go → Other-document routes
│ │ └── transcription.go → Transcription routes (by year, place, type, slug)
│ ├── service/ → Business logic layer (orchestrates repositories)
│ ├── repository/ → Data access layer (Mongo, Postgres, Meili, S3, Dragonfly)
│ │ ├── mongo.go → MongoDB connection + database handle
│ │ ├── book_repo.go → Book / verse queries
│ │ ├── letter_repo.go → Letter queries
│ │ ├── essay_repo.go → Essay queries
│ │ ├── glossary_repo.go → Glossary queries
│ │ ├── disciple_repo.go → Disciple queries
│ │ ├── journal_repo.go → Journal queries
│ │ ├── magazine_repo.go → Magazine queries
│ │ ├── other_document_repo.go → Other-document queries
│ │ └── transcription_repo.go → Transcription queries (with contentJson fallback)
│ ├── middleware/
│ │ ├── logger.go → Zap-based request logging (method, path, status, duration)
│ │ ├── auth.go → JWT Bearer token authentication + context user extraction
│ │ └── ratelimit.go → In-memory token bucket rate limiter (→ DragonflyDB in production)
│ └── model/
│ ├── content.go → Content models (Book, Verse, Letter, Essay, GlossaryEntry,
│ │ Disciple, Transcription, ContentBlock, Synonym)
│ └── user.go → User models (User, Bookmark, Collection, Mention, ShortURL)
├── pkg/ → Public packages (importable by other projects)
│ ├── response/
│ │ └── response.go → Standardized API response helpers (JSON, Error, Envelope, Meta)
│ ├── validator/
│ │ └── validator.go → Field validation (Required, MaxLength, Email, In, etc.)
│ ├── push/ → Push notification sender (FCM + APNs) — placeholder
│ └── shorturl/ → URL shortener logic — placeholder
├── migrations/ → PostgreSQL migrations (goose or golang-migrate)
├── go.mod → Module: github.com/prabhupadasays/spsays-bff
├── go.sum
├── Makefile → build, run, dev, test, clean, lint, tidy
├── .env.example → Template for all environment variables
└── .gitignore
```
---
## API Routes
All routes are prefixed with `/api/v1`.
### Health
| Method | Route | Description |
|--------|-------|-------------|
| `GET` | `/health` | Server health check |
### Books
| Method | Route | Description |
|--------|-------|-------------|
| `GET` | `/api/v1/books` | List all books (lightweight summaries) |
| `GET` | `/api/v1/books/sb` | SB book metadata |
| `GET` | `/api/v1/books/sb/{canto}` | SB canto chapter listing |
| `GET` | `/api/v1/books/sb/{canto}/{chapter}` | SB chapter verse index |
| `GET` | `/api/v1/books/sb/{canto}/{chapter}/verses` | SB paginated verses |
| `GET` | `/api/v1/books/sb/{canto}/{chapter}/{verse}` | SB single verse |
| `GET` | `/api/v1/books/cc` | CC book metadata |
| `GET` | `/api/v1/books/cc/{lila}` | CC līlā chapter listing |
| `GET` | `/api/v1/books/cc/{lila}/{chapter}` | CC chapter verse index |
| `GET` | `/api/v1/books/cc/{lila}/{chapter}/verses` | CC paginated verses |
| `GET` | `/api/v1/books/cc/{lila}/{chapter}/{verse}` | CC single verse |
| `GET` | `/api/v1/books/kb` | KB book metadata |
| `GET` | `/api/v1/books/kb/{volume}` | KB volume chapter listing |
| `GET` | `/api/v1/books/kb/{volume}/{slug}` | KB single chapter |
| `GET` | `/api/v1/books/bg` | BG book metadata |
| `GET` | `/api/v1/books/bg/{chapter}` | BG chapter verse index |
| `GET` | `/api/v1/books/bg/{chapter}/verses` | BG paginated verses |
| `GET` | `/api/v1/books/bg/{chapter}/{verse}` | BG single verse |
| `GET` | `/api/v1/books/{code}` | Any other book metadata |
| `GET` | `/api/v1/books/{code}/chapters` | Prose book chapter listing (no paragraphs) |
| `GET` | `/api/v1/books/{code}/chapters/{slug}` | Prose book single chapter |
| `GET` | `/api/v1/books/{code}/{chapter}` | Verse book chapter index |
| `GET` | `/api/v1/books/{code}/{chapter}/verses` | Verse book paginated verses |
| `GET` | `/api/v1/books/{code}/{chapter}/{verse}` | Verse book single verse |
### Letters
| Method | Route | Description |
|--------|-------|-------------|
| `GET` | `/api/v1/letters` | Total letter count |
| `GET` | `/api/v1/letters/years` | Year → count aggregation |
| `GET` | `/api/v1/letters/recipients` | Recipient → count aggregation |
| `GET` | `/api/v1/letters/locations` | Location → count aggregation |
| `GET` | `/api/v1/letters/year/{year}` | Letters for a year |
| `GET` | `/api/v1/letters/recipient/{name}` | Letters for a recipient |
| `GET` | `/api/v1/letters/location/{name}` | Letters from a location |
| `GET` | `/api/v1/letters/{slug}` | Single letter |
### Essays
| Method | Route | Description |
|--------|-------|-------------|
| `GET` | `/api/v1/essays` | All essay summaries |
| `GET` | `/api/v1/essays/{slug}` | Single essay |
### Glossary
| Method | Route | Description |
|--------|-------|-------------|
| `GET` | `/api/v1/glossary` | All glossary entries |
| `GET` | `/api/v1/glossary/letters` | Letter → count mapping |
| `GET` | `/api/v1/glossary/letter/{letter}` | Entries for a letter (with diacritical grouping) |
| `GET` | `/api/v1/glossary/letter/{letter}/terms` | Term names only for sidebar |
| `GET` | `/api/v1/glossary/term/{term}` | Single entry by term |
### Disciples
| Method | Route | Description |
|--------|-------|-------------|
| `GET` | `/api/v1/disciples` | Filtered list (?letter=&suffix=) |
| `GET` | `/api/v1/disciples/count` | Total + filtered counts |
| `GET` | `/api/v1/disciples/letters` | Letter → count aggregation |
| `GET` | `/api/v1/disciples/suffixes` | Suffix → count aggregation |
| `GET` | `/api/v1/disciples/search` | Search by name (?q=) |
### Journals
| Method | Route | Description |
|--------|-------|-------------|
| `GET` | `/api/v1/journals` | All journal metadata |
| `GET` | `/api/v1/journals/{book}` | Single journal meta |
| `GET` | `/api/v1/journals/{book}/chapters` | Journal chapters |
| `GET` | `/api/v1/journals/{book}/chapters/{slug}` | Single chapter |
### Magazines
| Method | Route | Description |
|--------|-------|-------------|
| `GET` | `/api/v1/magazines` | All magazine metadata |
| `GET` | `/api/v1/magazines/{book}` | Single magazine meta |
| `GET` | `/api/v1/magazines/{book}/issues` | Magazine issues |
| `GET` | `/api/v1/magazines/{book}/issues/{slug}` | Single issue |
### Other Documents
| Method | Route | Description |
|--------|-------|-------------|
| `GET` | `/api/v1/other-documents` | All document metadata |
| `GET` | `/api/v1/other-documents/{book}` | Single document meta |
| `GET` | `/api/v1/other-documents/{book}/chapters` | Document chapters |
| `GET` | `/api/v1/other-documents/{book}/chapters/{slug}` | Single chapter |
### Transcriptions
| Method | Route | Description |
|--------|-------|-------------|
| `GET` | `/api/v1/transcriptions` | Total transcription count |
| `GET` | `/api/v1/transcriptions/years` | Year → count aggregation |
| `GET` | `/api/v1/transcriptions/places` | Place → count aggregation |
| `GET` | `/api/v1/transcriptions/types` | Type distribution (`{lectures, conversations}`) |
| `GET` | `/api/v1/transcriptions/year/{year}` | Transcriptions for a year (summaries) |
| `GET` | `/api/v1/transcriptions/place/{place}` | Transcriptions for a place (summaries) |
| `GET` | `/api/v1/transcriptions/type/{type}` | Transcriptions by type (`lecture` or `conversation`) |
| `GET` | `/api/v1/transcriptions/adjacent/{slug}` | Prev/next within same year (`?year=N`) |
| `GET` | `/api/v1/transcriptions/{slug}` | Single transcription (full content + audio URL) |
### Authenticated (JWT required) — planned
| Method | Route | Description |
|--------|-------|-------------|
| `POST` | `/api/v1/auth/register` | User registration |
| `POST` | `/api/v1/auth/login` | User login (returns JWT) |
| `POST` | `/api/v1/auth/refresh` | Refresh JWT token |
| `GET` | `/api/v1/users/me` | Get current user profile |
| `PUT` | `/api/v1/users/me` | Update current user profile |
| `GET` | `/api/v1/collections` | List user's collections |
| `POST` | `/api/v1/collections` | Create a collection |
| `POST` | `/api/v1/collections/:id/items` | Add item to collection |
| `DELETE` | `/api/v1/collections/:id/items/:itemId` | Remove item from collection |
| `GET` | `/api/v1/mentions` | List user's mentions |
| `POST` | `/api/v1/mentions` | Create a mention |
| `POST` | `/api/v1/s` | Create a short URL |
| `POST` | `/api/v1/notifications/register` | Register device for push notifications |
---
## Data Models
### Content Models (MongoDB)
| Model | Collection | Description |
|-------|------------|-------------|
| `Book` | `book_meta` | Book metadata (title, author, chapters, etc.) |
| `Verse` | `verses` | Individual verses (BG, SB, CC, ISO, NOI, BS, MM, NBS) |
| `Letter` | `letters` | 6,800+ letters (1947–1977) |
| `Essay` | `essays` | 23 standalone essays |
| `GlossaryEntry` | `glossary` | 2,749 Sanskrit/Vedic terms |
| `Disciple` | `disciples` | 4,747 initiated disciples |
| `Transcription` | `transcriptions` | 3,787 lectures, conversations, philosophy discussions, SB dictations |
### User Models (PostgreSQL)
| Model | Table | Description |
|-------|-------|-------------|
| `User` | `users` | User accounts (email, name, avatar, role) |
| `Bookmark` | `bookmarks` | Saved content references with optional notes |
| `Collection` | `collections` | User-created collections of bookmarks |
| `Mention` | `mentions` | Content shared/tagged by users |
| `ShortURL` | `short_urls` | Shortened URLs with click tracking |
---
## API Response Format
All responses follow a consistent envelope:
```json
{
"success": true,
"data": { ... },
"meta": {
"page": 1,
"limit": 20,
"total": 150,
"total_pages": 8
}
}
```
Error responses:
```json
{
"success": false,
"error": {
"code": 400,
"message": "validation failed"
}
}
```
---
## Middleware Stack
| Middleware | Description |
|-----------|-------------|
| `RequestID` | Generates unique request ID (chi built-in) |
| `RealIP` | Extracts real client IP from proxy headers |
| `Logger` | Zap-based structured request logging |
| `Recoverer` | Panic recovery (chi built-in) |
| `Compress` | Gzip compression (level 5) |
| `CORS` | Cross-origin resource sharing (frontend URL whitelisted) |
| `Auth` | JWT Bearer token validation (applied to protected routes) |
| `RateLimit` | Token bucket rate limiter (in-memory → DragonflyDB in production) |
---
## Environment Variables
```
# Server
APP_ENV=development # "development" | "production"
PORT=8080
# MongoDB (content)
MONGO_URI=mongodb://... # required
MONGO_DB=spsays
# PostgreSQL (users, relations)
POSTGRES_URI=postgres://...
# Meilisearch
MEILISEARCH_HOST=http://localhost:7700
MEILISEARCH_API_KEY=
# DragonflyDB / Redis
REDIS_URI=redis://localhost:6379
# S3
S3_REGION=
S3_BUCKET=
S3_ACCESS_KEY=
S3_SECRET_KEY=
S3_ENDPOINT= # optional — for MinIO or S3-compatible services
# Push Notifications
FCM_CREDENTIALS_FILE= # path to Firebase service account JSON
APNS_KEY_FILE= # path to APNs auth key (.p8)
APNS_KEY_ID=
APNS_TEAM_ID=
APNS_BUNDLE_ID=
# JWT
JWT_SECRET= # required in production
JWT_EXPIRATION=24h
# Frontend (CORS)
FRONTEND_URL=http://localhost:3000
```
---
## How to Run
```bash
# Setup
cp .env.example .env # then fill in values
# Development (with live reload — requires air)
make dev
# Build + run
make run
# Build only
make build
# Run tests
make test
# Lint (requires golangci-lint)
make lint
# Tidy deps
make tidy
```
---
## Relationship to Other Projects
| Project | Path | Description |
|---------|------|-------------|
| **knowledgebase** | `../knowledgebase/` | Next.js frontend — consumes this BFF's API |
| **extractor** | `../extractor/` | Python/TS extraction scripts + raw data + seed scripts |
| **spsays-bff** | `./` (this project) | Go BFF API server |
The frontend currently fetches directly from MongoDB via Prisma. The migration plan:
1. ✅ Done — BFF API routes matching current Prisma queries
2. Add Meilisearch indexing + search endpoints
3. Add PostgreSQL for user features (auth, bookmarks, collections, mentions)
4. Switch frontend from Prisma to BFF API calls
5. Add mobile app support (same API, push notifications)
---
## Design Principles
- **`internal/`** — private to this module (handlers, services, repositories, middleware, models, config)
- **`pkg/`** — public packages that could be imported by other projects (response helpers, validators, push sender)
- **Repository pattern** — data access isolated behind interfaces (easy to swap Mongo ↔ Postgres, mock for tests)
- **Service layer** — business logic between handlers and repositories (orchestration, validation, caching)
- **Handler layer** — thin HTTP handlers that parse requests, call services, return responses
- **No ORM** — use native drivers (`mongo-go-driver`, `pgx`) for full control
- **Explicit book routing** — SB, CC, and KB have dedicated route groups to avoid URL ambiguity with generic `{code}/{chapter}/{verse}` patterns