Files
spsays-bff/PROJECT_CONTEXT.md
T
2026-05-09 23:19:19 +05:30

417 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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