feat: add books APIs
This commit is contained in:
@@ -0,0 +1,633 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/prabhupadasays/spsays-bff/internal/repository"
|
||||
"github.com/prabhupadasays/spsays-bff/pkg/response"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
type BookHandler struct {
|
||||
repo *repository.BookRepo
|
||||
}
|
||||
|
||||
func NewBookHandler(repo *repository.BookRepo) *BookHandler {
|
||||
return &BookHandler{repo: repo}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GET /books — list all books (lightweight summaries only)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func (h *BookHandler) ListBooks(w http.ResponseWriter, r *http.Request) {
|
||||
docs, err := h.repo.GetAllBookMetas(r.Context())
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch books")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GET /books/{code} — full book metadata (chapters, cantos, līlās, etc.)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func (h *BookHandler) GetBook(w http.ResponseWriter, r *http.Request) {
|
||||
code := chi.URLParam(r, "code")
|
||||
doc, err := h.repo.GetBookMeta(r.Context(), code)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch book")
|
||||
return
|
||||
}
|
||||
if doc == nil {
|
||||
response.Error(w, http.StatusNotFound, "book not found")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, doc)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// VERSE BOOKS WITHOUT DIVISIONS (BG, SB1962, BS, MM, NBS)
|
||||
//
|
||||
// GET /books/{code}/{chapter} → verse index for sidebar
|
||||
// GET /books/{code}/{chapter}/{verse} → single verse with full content
|
||||
// GET /books/{code}/{chapter}/verses → paginated verses for the chapter
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// GetChapterVerseIndex returns the verse index for a chapter (for sidebar).
|
||||
// GET /books/{code}/{chapter}
|
||||
func (h *BookHandler) GetChapterVerseIndex(w http.ResponseWriter, r *http.Request) {
|
||||
code := chi.URLParam(r, "code")
|
||||
chapterStr := chi.URLParam(r, "chapter")
|
||||
|
||||
chapter, err := strconv.Atoi(chapterStr)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusBadRequest, "invalid chapter number")
|
||||
return
|
||||
}
|
||||
|
||||
verses, err := h.repo.GetVerseIndex(r.Context(), code, nil, chapter)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch chapter info")
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON(w, http.StatusOK, map[string]interface{}{
|
||||
"book": code,
|
||||
"chapter": chapter,
|
||||
"total_verses": len(verses),
|
||||
"verses": verses,
|
||||
})
|
||||
}
|
||||
|
||||
// GetVerse returns a single verse with full content.
|
||||
// GET /books/{code}/{chapter}/{verse}
|
||||
func (h *BookHandler) GetVerse(w http.ResponseWriter, r *http.Request) {
|
||||
code := chi.URLParam(r, "code")
|
||||
chapterStr := chi.URLParam(r, "chapter")
|
||||
verseStr := chi.URLParam(r, "verse")
|
||||
|
||||
chapter, err := strconv.Atoi(chapterStr)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusBadRequest, "invalid chapter number")
|
||||
return
|
||||
}
|
||||
verse, err := strconv.Atoi(verseStr)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusBadRequest, "invalid verse number")
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := h.repo.GetSingleVerse(r.Context(), code, nil, chapter, verse)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch verse")
|
||||
return
|
||||
}
|
||||
if doc == nil {
|
||||
response.Error(w, http.StatusNotFound, "verse not found")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, doc)
|
||||
}
|
||||
|
||||
// GetChapterVerses returns paginated verses for a chapter.
|
||||
// GET /books/{code}/{chapter}/verses?page=&limit=
|
||||
func (h *BookHandler) GetChapterVerses(w http.ResponseWriter, r *http.Request) {
|
||||
code := chi.URLParam(r, "code")
|
||||
chapterStr := chi.URLParam(r, "chapter")
|
||||
|
||||
chapter, err := strconv.Atoi(chapterStr)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusBadRequest, "invalid chapter number")
|
||||
return
|
||||
}
|
||||
|
||||
page, limit := parsePagination(r)
|
||||
|
||||
docs, total, err := h.repo.GetVerses(r.Context(), code, nil, chapter, page, limit)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch verses")
|
||||
return
|
||||
}
|
||||
|
||||
totalPages := int(math.Ceil(float64(total) / float64(limit)))
|
||||
response.JSONWithMeta(w, http.StatusOK, docs, &response.Meta{
|
||||
Page: page,
|
||||
Limit: limit,
|
||||
Total: total,
|
||||
TotalPages: totalPages,
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// VERSE BOOKS WITH DIVISIONS
|
||||
//
|
||||
// SB: division = canto → /books/sb/{canto}/{chapter}/{verse}
|
||||
// CC: division = līlā → /books/cc/{lila}/{chapter}/{verse}
|
||||
//
|
||||
// GET /books/{code}/{division} → chapter list for that division
|
||||
// GET /books/{code}/{division}/{chapter} → verse index for sidebar
|
||||
// GET /books/{code}/{division}/{chapter}/{verse} → single verse
|
||||
// GET /books/{code}/{division}/{chapter}/verses → paginated verses
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// GetDivisionInfo returns the chapter listing for a specific division (canto/līlā).
|
||||
// It extracts the relevant division from the book_meta data blob.
|
||||
// GET /books/sb/{division} or /books/cc/{division}
|
||||
func (h *BookHandler) GetDivisionInfo(w http.ResponseWriter, r *http.Request) {
|
||||
code := chi.URLParam(r, "code")
|
||||
division := chi.URLParam(r, "division")
|
||||
|
||||
doc, err := h.repo.GetBookMeta(r.Context(), code)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch book")
|
||||
return
|
||||
}
|
||||
if doc == nil {
|
||||
response.Error(w, http.StatusNotFound, "book not found")
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the division's chapter list from the data blob.
|
||||
// SB data: { cantos: [{ number, title, totalChapters, chapters: [{number, title, totalVerses}] }] }
|
||||
// CC data: { lilas: [{ key, name, totalChapters, chapters: [{number, title, totalVerses}] }] }
|
||||
result := extractDivisionFromMeta(code, division, doc.Data)
|
||||
if result == nil {
|
||||
response.Error(w, http.StatusNotFound, "division not found")
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON(w, http.StatusOK, result)
|
||||
}
|
||||
|
||||
// GetDivisionChapterVerseIndex returns the verse index for a chapter within a division.
|
||||
// GET /books/{code}/{division}/{chapter}
|
||||
func (h *BookHandler) GetDivisionChapterVerseIndex(w http.ResponseWriter, r *http.Request) {
|
||||
code := chi.URLParam(r, "code")
|
||||
division := chi.URLParam(r, "division")
|
||||
chapterStr := chi.URLParam(r, "chapter")
|
||||
|
||||
chapter, err := strconv.Atoi(chapterStr)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusBadRequest, "invalid chapter number")
|
||||
return
|
||||
}
|
||||
|
||||
verses, err := h.repo.GetVerseIndex(r.Context(), code, &division, chapter)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch chapter info")
|
||||
return
|
||||
}
|
||||
|
||||
response.JSON(w, http.StatusOK, map[string]interface{}{
|
||||
"book": code,
|
||||
"division": division,
|
||||
"chapter": chapter,
|
||||
"total_verses": len(verses),
|
||||
"verses": verses,
|
||||
})
|
||||
}
|
||||
|
||||
// GetDivisionVerse returns a single verse within a division.
|
||||
// GET /books/{code}/{division}/{chapter}/{verse}
|
||||
func (h *BookHandler) GetDivisionVerse(w http.ResponseWriter, r *http.Request) {
|
||||
code := chi.URLParam(r, "code")
|
||||
division := chi.URLParam(r, "division")
|
||||
chapterStr := chi.URLParam(r, "chapter")
|
||||
verseStr := chi.URLParam(r, "verse")
|
||||
|
||||
chapter, err := strconv.Atoi(chapterStr)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusBadRequest, "invalid chapter number")
|
||||
return
|
||||
}
|
||||
verse, err := strconv.Atoi(verseStr)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusBadRequest, "invalid verse number")
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := h.repo.GetSingleVerse(r.Context(), code, &division, chapter, verse)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch verse")
|
||||
return
|
||||
}
|
||||
if doc == nil {
|
||||
response.Error(w, http.StatusNotFound, "verse not found")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, doc)
|
||||
}
|
||||
|
||||
// GetDivisionChapterVerses returns paginated verses within a division.
|
||||
// GET /books/{code}/{division}/{chapter}/verses?page=&limit=
|
||||
func (h *BookHandler) GetDivisionChapterVerses(w http.ResponseWriter, r *http.Request) {
|
||||
code := chi.URLParam(r, "code")
|
||||
division := chi.URLParam(r, "division")
|
||||
chapterStr := chi.URLParam(r, "chapter")
|
||||
|
||||
chapter, err := strconv.Atoi(chapterStr)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusBadRequest, "invalid chapter number")
|
||||
return
|
||||
}
|
||||
|
||||
page, limit := parsePagination(r)
|
||||
|
||||
docs, total, err := h.repo.GetVerses(r.Context(), code, &division, chapter, page, limit)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch verses")
|
||||
return
|
||||
}
|
||||
|
||||
totalPages := int(math.Ceil(float64(total) / float64(limit)))
|
||||
response.JSONWithMeta(w, http.StatusOK, docs, &response.Meta{
|
||||
Page: page,
|
||||
Limit: limit,
|
||||
Total: total,
|
||||
TotalPages: totalPages,
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PROSE BOOKS — simple chapters (BBD, EJ, EK, MG, NOD, TLC, SSR, DS, etc.)
|
||||
//
|
||||
// GET /books/{code}/chapters → chapter list (sidebar)
|
||||
// GET /books/{code}/chapters/{slug} → single chapter content
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ListProseChapters returns chapter listing for a prose book.
|
||||
// GET /books/{code}/chapters?volume=&type=
|
||||
//
|
||||
// For books with verses (ISO, BS, NOI), also appends verse entries
|
||||
// (mantras/texts) from the book metadata so the sidebar is complete.
|
||||
func (h *BookHandler) ListProseChapters(w http.ResponseWriter, r *http.Request) {
|
||||
code := chi.URLParam(r, "code")
|
||||
volumeStr := r.URL.Query().Get("volume")
|
||||
chapterType := r.URL.Query().Get("type")
|
||||
|
||||
volume := 0
|
||||
if v, e := strconv.Atoi(volumeStr); e == nil {
|
||||
volume = v
|
||||
}
|
||||
|
||||
var typePtr *string
|
||||
if chapterType != "" {
|
||||
typePtr = &chapterType
|
||||
}
|
||||
|
||||
docs, err := h.repo.GetProseChapters(r.Context(), code, volume, typePtr)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch chapters")
|
||||
return
|
||||
}
|
||||
|
||||
// Strip paragraphs from list response — only return sidebar info
|
||||
type ChapterSummary struct {
|
||||
ID interface{} `json:"id"`
|
||||
Book string `json:"book"`
|
||||
Volume int `json:"volume"`
|
||||
Chapter *int `json:"chapter,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Type string `json:"type"`
|
||||
Slug string `json:"slug"`
|
||||
Order int `json:"order"`
|
||||
Mantra *int `json:"mantra,omitempty"`
|
||||
Text *int `json:"text,omitempty"`
|
||||
}
|
||||
summaries := make([]ChapterSummary, 0, len(docs))
|
||||
for _, d := range docs {
|
||||
summaries = append(summaries, ChapterSummary{
|
||||
ID: d.ID,
|
||||
Book: d.Book,
|
||||
Volume: d.Volume,
|
||||
Chapter: d.Chapter,
|
||||
Title: d.Title,
|
||||
Type: d.Type,
|
||||
Slug: d.Slug,
|
||||
Order: d.Order,
|
||||
})
|
||||
}
|
||||
|
||||
// For verse+prose books (ISO, BS, NOI), append verse entries from book meta
|
||||
// so the sidebar includes mantras/texts alongside intro segments.
|
||||
if chapterType == "" || chapterType == "verse" {
|
||||
meta, metaErr := h.repo.GetBookMeta(r.Context(), code)
|
||||
if metaErr == nil && meta != nil {
|
||||
if dataMap, ok := meta.Data.(bson.D); ok {
|
||||
for _, elem := range dataMap {
|
||||
if elem.Key == "mantras" {
|
||||
// ISO: mantras array with {number, slug, title}
|
||||
if arr, ok := elem.Value.(bson.A); ok {
|
||||
baseOrder := len(summaries) + 1
|
||||
for i, item := range arr {
|
||||
if m, ok := item.(bson.D); ok {
|
||||
num := 0
|
||||
slug := ""
|
||||
title := ""
|
||||
for _, f := range m {
|
||||
switch f.Key {
|
||||
case "number":
|
||||
if n, ok := f.Value.(int32); ok {
|
||||
num = int(n)
|
||||
} else if n, ok := f.Value.(int64); ok {
|
||||
num = int(n)
|
||||
}
|
||||
case "slug":
|
||||
if s, ok := f.Value.(string); ok {
|
||||
slug = s
|
||||
}
|
||||
case "title":
|
||||
if s, ok := f.Value.(string); ok {
|
||||
title = s
|
||||
}
|
||||
}
|
||||
}
|
||||
if slug == "" {
|
||||
slug = strconv.Itoa(num)
|
||||
}
|
||||
mantraNum := num
|
||||
summaries = append(summaries, ChapterSummary{
|
||||
Book: code,
|
||||
Title: title,
|
||||
Type: "verse",
|
||||
Slug: slug,
|
||||
Order: baseOrder + i,
|
||||
Mantra: &mantraNum,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if elem.Key == "texts" {
|
||||
// BS, NOI: texts array with {number, slug, title}
|
||||
if arr, ok := elem.Value.(bson.A); ok {
|
||||
baseOrder := len(summaries) + 1
|
||||
for i, item := range arr {
|
||||
if m, ok := item.(bson.D); ok {
|
||||
num := 0
|
||||
slug := ""
|
||||
title := ""
|
||||
for _, f := range m {
|
||||
switch f.Key {
|
||||
case "number":
|
||||
if n, ok := f.Value.(int32); ok {
|
||||
num = int(n)
|
||||
} else if n, ok := f.Value.(int64); ok {
|
||||
num = int(n)
|
||||
}
|
||||
case "slug":
|
||||
if s, ok := f.Value.(string); ok {
|
||||
slug = s
|
||||
}
|
||||
case "title":
|
||||
if s, ok := f.Value.(string); ok {
|
||||
title = s
|
||||
}
|
||||
}
|
||||
}
|
||||
if slug == "" {
|
||||
slug = strconv.Itoa(num)
|
||||
}
|
||||
textNum := num
|
||||
summaries = append(summaries, ChapterSummary{
|
||||
Book: code,
|
||||
Title: title,
|
||||
Type: "verse",
|
||||
Slug: slug,
|
||||
Order: baseOrder + i,
|
||||
Text: &textNum,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response.JSON(w, http.StatusOK, summaries)
|
||||
}
|
||||
|
||||
// GetProseChapter returns a single prose chapter with full content.
|
||||
// GET /books/{code}/chapters/{slug}?volume=
|
||||
func (h *BookHandler) GetProseChapter(w http.ResponseWriter, r *http.Request) {
|
||||
code := chi.URLParam(r, "code")
|
||||
slug := chi.URLParam(r, "slug")
|
||||
volumeStr := r.URL.Query().Get("volume")
|
||||
|
||||
volume := 0
|
||||
if v, e := strconv.Atoi(volumeStr); e == nil {
|
||||
volume = v
|
||||
}
|
||||
|
||||
doc, err := h.repo.GetProseChapter(r.Context(), code, volume, slug)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch chapter")
|
||||
return
|
||||
}
|
||||
if doc == nil {
|
||||
response.Error(w, http.StatusNotFound, "chapter not found")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, doc)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// VOLUME-BASED PROSE BOOKS (KB)
|
||||
//
|
||||
// GET /books/{code}/volumes/{volume} → chapter list for volume
|
||||
// GET /books/{code}/volumes/{volume}/{slug} → single chapter content
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ListVolumeChapters returns chapters for a specific volume.
|
||||
// GET /books/{code}/volumes/{volume}
|
||||
func (h *BookHandler) ListVolumeChapters(w http.ResponseWriter, r *http.Request) {
|
||||
code := chi.URLParam(r, "code")
|
||||
volumeStr := chi.URLParam(r, "volume")
|
||||
|
||||
volume, err := strconv.Atoi(volumeStr)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusBadRequest, "invalid volume number")
|
||||
return
|
||||
}
|
||||
|
||||
docs, err := h.repo.GetProseChapters(r.Context(), code, volume, nil)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch chapters")
|
||||
return
|
||||
}
|
||||
|
||||
// Strip paragraphs from list response
|
||||
type ChapterSummary struct {
|
||||
ID interface{} `json:"id"`
|
||||
Book string `json:"book"`
|
||||
Volume int `json:"volume"`
|
||||
Chapter *int `json:"chapter,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Type string `json:"type"`
|
||||
Slug string `json:"slug"`
|
||||
Order int `json:"order"`
|
||||
}
|
||||
summaries := make([]ChapterSummary, len(docs))
|
||||
for i, d := range docs {
|
||||
summaries[i] = ChapterSummary{
|
||||
ID: d.ID,
|
||||
Book: d.Book,
|
||||
Volume: d.Volume,
|
||||
Chapter: d.Chapter,
|
||||
Title: d.Title,
|
||||
Type: d.Type,
|
||||
Slug: d.Slug,
|
||||
Order: d.Order,
|
||||
}
|
||||
}
|
||||
response.JSON(w, http.StatusOK, summaries)
|
||||
}
|
||||
|
||||
// GetVolumeChapter returns a single chapter within a volume.
|
||||
// GET /books/{code}/volumes/{volume}/{slug}
|
||||
func (h *BookHandler) GetVolumeChapter(w http.ResponseWriter, r *http.Request) {
|
||||
code := chi.URLParam(r, "code")
|
||||
volumeStr := chi.URLParam(r, "volume")
|
||||
slug := chi.URLParam(r, "slug")
|
||||
|
||||
volume, err := strconv.Atoi(volumeStr)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusBadRequest, "invalid volume number")
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := h.repo.GetProseChapter(r.Context(), code, volume, slug)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch chapter")
|
||||
return
|
||||
}
|
||||
if doc == nil {
|
||||
response.Error(w, http.StatusNotFound, "chapter not found")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, doc)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func parsePagination(r *http.Request) (page, limit int) {
|
||||
page = 1
|
||||
limit = 20
|
||||
if p, err := strconv.Atoi(r.URL.Query().Get("page")); err == nil && p > 0 {
|
||||
page = p
|
||||
}
|
||||
if l, err := strconv.Atoi(r.URL.Query().Get("limit")); err == nil && l > 0 && l <= 100 {
|
||||
limit = l
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// extractDivisionFromMeta pulls the relevant division (canto/līlā) from the
|
||||
// book_meta data blob and returns it as a map, or nil if not found.
|
||||
func extractDivisionFromMeta(code, division string, data interface{}) map[string]interface{} {
|
||||
dataMap, ok := data.(bson.D)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
divNum, numErr := strconv.Atoi(division)
|
||||
|
||||
switch code {
|
||||
case "sb":
|
||||
// SB: data.cantos is an array, division is canto number (1-indexed)
|
||||
for _, elem := range dataMap {
|
||||
if elem.Key != "cantos" {
|
||||
continue
|
||||
}
|
||||
cantos, ok := elem.Value.(bson.A)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if numErr != nil || divNum < 1 || divNum > len(cantos) {
|
||||
return nil
|
||||
}
|
||||
canto, ok := cantos[divNum-1].(bson.D)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return bsonDToMap(canto)
|
||||
}
|
||||
|
||||
case "cc":
|
||||
// CC: data.lilas is an array, division is līlā key ("adi", "madhya", "antya")
|
||||
for _, elem := range dataMap {
|
||||
if elem.Key != "lilas" {
|
||||
continue
|
||||
}
|
||||
lilas, ok := elem.Value.(bson.A)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
for _, l := range lilas {
|
||||
lila, ok := l.(bson.D)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, field := range lila {
|
||||
if field.Key == "key" && field.Value == division {
|
||||
return bsonDToMap(lila)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// bsonDToMap converts a bson.D to a map[string]interface{} for JSON serialization.
|
||||
func bsonDToMap(d bson.D) map[string]interface{} {
|
||||
m := make(map[string]interface{}, len(d))
|
||||
for _, elem := range d {
|
||||
switch v := elem.Value.(type) {
|
||||
case bson.D:
|
||||
m[elem.Key] = bsonDToMap(v)
|
||||
case bson.A:
|
||||
arr := make([]interface{}, len(v))
|
||||
for i, item := range v {
|
||||
if sub, ok := item.(bson.D); ok {
|
||||
arr[i] = bsonDToMap(sub)
|
||||
} else {
|
||||
arr[i] = item
|
||||
}
|
||||
}
|
||||
m[elem.Key] = arr
|
||||
default:
|
||||
m[elem.Key] = v
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/prabhupadasays/spsays-bff/internal/repository"
|
||||
"github.com/prabhupadasays/spsays-bff/pkg/response"
|
||||
)
|
||||
|
||||
type DiscipleHandler struct {
|
||||
repo *repository.DiscipleRepo
|
||||
}
|
||||
|
||||
func NewDiscipleHandler(repo *repository.DiscipleRepo) *DiscipleHandler {
|
||||
return &DiscipleHandler{repo: repo}
|
||||
}
|
||||
|
||||
func (h *DiscipleHandler) List(w http.ResponseWriter, r *http.Request) {
|
||||
letter := r.URL.Query().Get("letter")
|
||||
suffix := r.URL.Query().Get("suffix")
|
||||
|
||||
var letterPtr, suffixPtr *string
|
||||
if letter != "" && letter != "All" {
|
||||
letterPtr = &letter
|
||||
}
|
||||
if suffix != "" {
|
||||
suffixPtr = &suffix
|
||||
}
|
||||
|
||||
docs, err := h.repo.GetByLetterAndSuffix(r.Context(), letterPtr, suffixPtr)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch disciples")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *DiscipleHandler) Count(w http.ResponseWriter, r *http.Request) {
|
||||
letter := r.URL.Query().Get("letter")
|
||||
suffix := r.URL.Query().Get("suffix")
|
||||
|
||||
var letterPtr, suffixPtr *string
|
||||
if letter != "" && letter != "All" {
|
||||
letterPtr = &letter
|
||||
}
|
||||
if suffix != "" {
|
||||
suffixPtr = &suffix
|
||||
}
|
||||
|
||||
total, err := h.repo.Count(r.Context())
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to count disciples")
|
||||
return
|
||||
}
|
||||
filtered, err := h.repo.CountFiltered(r.Context(), letterPtr, suffixPtr)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to count filtered disciples")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, map[string]int64{"total": total, "filtered": filtered})
|
||||
}
|
||||
|
||||
func (h *DiscipleHandler) LetterCounts(w http.ResponseWriter, r *http.Request) {
|
||||
docs, err := h.repo.GetLetterCounts(r.Context())
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch letter counts")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *DiscipleHandler) SuffixCounts(w http.ResponseWriter, r *http.Request) {
|
||||
docs, err := h.repo.GetSuffixCounts(r.Context())
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch suffix counts")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *DiscipleHandler) Search(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query().Get("q")
|
||||
if query == "" {
|
||||
response.Error(w, http.StatusBadRequest, "search query is required")
|
||||
return
|
||||
}
|
||||
docs, err := h.repo.Search(r.Context(), query, 100)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to search disciples")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/prabhupadasays/spsays-bff/internal/repository"
|
||||
"github.com/prabhupadasays/spsays-bff/pkg/response"
|
||||
)
|
||||
|
||||
type EssayHandler struct {
|
||||
repo *repository.EssayRepo
|
||||
}
|
||||
|
||||
func NewEssayHandler(repo *repository.EssayRepo) *EssayHandler {
|
||||
return &EssayHandler{repo: repo}
|
||||
}
|
||||
|
||||
func (h *EssayHandler) List(w http.ResponseWriter, r *http.Request) {
|
||||
docs, err := h.repo.GetAll(r.Context())
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch essays")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *EssayHandler) GetBySlug(w http.ResponseWriter, r *http.Request) {
|
||||
slug := chi.URLParam(r, "slug")
|
||||
doc, err := h.repo.GetBySlug(r.Context(), slug)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch essay")
|
||||
return
|
||||
}
|
||||
if doc == nil {
|
||||
response.Error(w, http.StatusNotFound, "essay not found")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, doc)
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/prabhupadasays/spsays-bff/internal/repository"
|
||||
"github.com/prabhupadasays/spsays-bff/pkg/response"
|
||||
)
|
||||
|
||||
type GlossaryHandler struct {
|
||||
repo *repository.GlossaryRepo
|
||||
}
|
||||
|
||||
func NewGlossaryHandler(repo *repository.GlossaryRepo) *GlossaryHandler {
|
||||
return &GlossaryHandler{repo: repo}
|
||||
}
|
||||
|
||||
// List returns all glossary entries sorted by term.
|
||||
// GET /glossary
|
||||
func (h *GlossaryHandler) List(w http.ResponseWriter, r *http.Request) {
|
||||
docs, err := h.repo.GetAll(r.Context())
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch glossary")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
// GetByTerm returns a single glossary entry by exact term match.
|
||||
// GET /glossary/term/{term}
|
||||
func (h *GlossaryHandler) GetByTerm(w http.ResponseWriter, r *http.Request) {
|
||||
term := chi.URLParam(r, "term")
|
||||
doc, err := h.repo.GetByTerm(r.Context(), term)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch term")
|
||||
return
|
||||
}
|
||||
if doc == nil {
|
||||
response.Error(w, http.StatusNotFound, "term not found")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, doc)
|
||||
}
|
||||
|
||||
// GetByLetter returns all glossary entries whose term starts with the given
|
||||
// base letter (including diacritical variants like Ā→A, Ś→S, etc.).
|
||||
// GET /glossary/letter/{letter}
|
||||
func (h *GlossaryHandler) GetByLetter(w http.ResponseWriter, r *http.Request) {
|
||||
letter := chi.URLParam(r, "letter")
|
||||
if len(letter) != 1 {
|
||||
response.Error(w, http.StatusBadRequest, "letter must be a single character")
|
||||
return
|
||||
}
|
||||
|
||||
docs, err := h.repo.GetByLetter(r.Context(), letter)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch glossary entries")
|
||||
return
|
||||
}
|
||||
if docs == nil {
|
||||
docs = []repository.GlossaryEntryDoc{}
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
// GetTermsByLetter returns lightweight term summaries (no definitions) for a
|
||||
// given base letter. Used for sidebar listing.
|
||||
// GET /glossary/letter/{letter}/terms
|
||||
func (h *GlossaryHandler) GetTermsByLetter(w http.ResponseWriter, r *http.Request) {
|
||||
letter := chi.URLParam(r, "letter")
|
||||
if len(letter) != 1 {
|
||||
response.Error(w, http.StatusBadRequest, "letter must be a single character")
|
||||
return
|
||||
}
|
||||
|
||||
docs, err := h.repo.GetTermsByLetter(r.Context(), letter)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch terms")
|
||||
return
|
||||
}
|
||||
if docs == nil {
|
||||
docs = []repository.GlossaryTermSummary{}
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
// GetLetterCounts returns the count of glossary entries per base letter.
|
||||
// GET /glossary/letters
|
||||
func (h *GlossaryHandler) GetLetterCounts(w http.ResponseWriter, r *http.Request) {
|
||||
counts, err := h.repo.GetLetterCounts(r.Context())
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch letter counts")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, counts)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/prabhupadasays/spsays-bff/pkg/response"
|
||||
)
|
||||
|
||||
// Health handles the health check endpoint.
|
||||
type Health struct{}
|
||||
|
||||
// NewHealth creates a new Health handler.
|
||||
func NewHealth() *Health {
|
||||
return &Health{}
|
||||
}
|
||||
|
||||
// Check returns the server health status.
|
||||
func (h *Health) Check(w http.ResponseWriter, r *http.Request) {
|
||||
response.JSON(w, http.StatusOK, map[string]string{
|
||||
"status": "ok",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/prabhupadasays/spsays-bff/internal/repository"
|
||||
"github.com/prabhupadasays/spsays-bff/pkg/response"
|
||||
)
|
||||
|
||||
type JournalHandler struct {
|
||||
repo *repository.JournalRepo
|
||||
}
|
||||
|
||||
func NewJournalHandler(repo *repository.JournalRepo) *JournalHandler {
|
||||
return &JournalHandler{repo: repo}
|
||||
}
|
||||
|
||||
func (h *JournalHandler) ListJournals(w http.ResponseWriter, r *http.Request) {
|
||||
docs, err := h.repo.GetAllMetas(r.Context())
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch journals")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *JournalHandler) GetJournal(w http.ResponseWriter, r *http.Request) {
|
||||
book := chi.URLParam(r, "book")
|
||||
doc, err := h.repo.GetMeta(r.Context(), book)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch journal")
|
||||
return
|
||||
}
|
||||
if doc == nil {
|
||||
response.Error(w, http.StatusNotFound, "journal not found")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, doc)
|
||||
}
|
||||
|
||||
func (h *JournalHandler) ListChapters(w http.ResponseWriter, r *http.Request) {
|
||||
book := chi.URLParam(r, "book")
|
||||
docs, err := h.repo.GetChapters(r.Context(), book)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch chapters")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *JournalHandler) GetChapter(w http.ResponseWriter, r *http.Request) {
|
||||
book := chi.URLParam(r, "book")
|
||||
slug := chi.URLParam(r, "slug")
|
||||
doc, err := h.repo.GetChapter(r.Context(), book, slug)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch chapter")
|
||||
return
|
||||
}
|
||||
if doc == nil {
|
||||
response.Error(w, http.StatusNotFound, "chapter not found")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, doc)
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/prabhupadasays/spsays-bff/internal/repository"
|
||||
"github.com/prabhupadasays/spsays-bff/pkg/response"
|
||||
)
|
||||
|
||||
type LetterHandler struct {
|
||||
repo *repository.LetterRepo
|
||||
}
|
||||
|
||||
func NewLetterHandler(repo *repository.LetterRepo) *LetterHandler {
|
||||
return &LetterHandler{repo: repo}
|
||||
}
|
||||
|
||||
func (h *LetterHandler) GetBySlug(w http.ResponseWriter, r *http.Request) {
|
||||
slug := chi.URLParam(r, "slug")
|
||||
doc, err := h.repo.GetBySlug(r.Context(), slug)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch letter")
|
||||
return
|
||||
}
|
||||
if doc == nil {
|
||||
response.Error(w, http.StatusNotFound, "letter not found")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, doc)
|
||||
}
|
||||
|
||||
func (h *LetterHandler) GetByYear(w http.ResponseWriter, r *http.Request) {
|
||||
yearStr := chi.URLParam(r, "year")
|
||||
year, err := strconv.Atoi(yearStr)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusBadRequest, "invalid year")
|
||||
return
|
||||
}
|
||||
docs, err := h.repo.GetByYear(r.Context(), year)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch letters")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *LetterHandler) GetByRecipient(w http.ResponseWriter, r *http.Request) {
|
||||
name := chi.URLParam(r, "name")
|
||||
docs, err := h.repo.GetByRecipient(r.Context(), name)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch letters")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *LetterHandler) GetByLocation(w http.ResponseWriter, r *http.Request) {
|
||||
name := chi.URLParam(r, "name")
|
||||
docs, err := h.repo.GetByLocation(r.Context(), name)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch letters")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *LetterHandler) GetYears(w http.ResponseWriter, r *http.Request) {
|
||||
docs, err := h.repo.GetYears(r.Context())
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch years")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *LetterHandler) GetRecipients(w http.ResponseWriter, r *http.Request) {
|
||||
docs, err := h.repo.GetRecipients(r.Context())
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch recipients")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *LetterHandler) GetLocations(w http.ResponseWriter, r *http.Request) {
|
||||
docs, err := h.repo.GetLocations(r.Context())
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch locations")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *LetterHandler) Count(w http.ResponseWriter, r *http.Request) {
|
||||
count, err := h.repo.Count(r.Context())
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to count letters")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, map[string]int64{"count": count})
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/prabhupadasays/spsays-bff/internal/repository"
|
||||
"github.com/prabhupadasays/spsays-bff/pkg/response"
|
||||
)
|
||||
|
||||
type MagazineHandler struct {
|
||||
repo *repository.MagazineRepo
|
||||
}
|
||||
|
||||
func NewMagazineHandler(repo *repository.MagazineRepo) *MagazineHandler {
|
||||
return &MagazineHandler{repo: repo}
|
||||
}
|
||||
|
||||
func (h *MagazineHandler) ListMagazines(w http.ResponseWriter, r *http.Request) {
|
||||
docs, err := h.repo.GetAllMetas(r.Context())
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch magazines")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *MagazineHandler) GetMagazine(w http.ResponseWriter, r *http.Request) {
|
||||
book := chi.URLParam(r, "book")
|
||||
doc, err := h.repo.GetMeta(r.Context(), book)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch magazine")
|
||||
return
|
||||
}
|
||||
if doc == nil {
|
||||
response.Error(w, http.StatusNotFound, "magazine not found")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, doc)
|
||||
}
|
||||
|
||||
func (h *MagazineHandler) ListIssues(w http.ResponseWriter, r *http.Request) {
|
||||
book := chi.URLParam(r, "book")
|
||||
docs, err := h.repo.GetIssues(r.Context(), book)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch issues")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *MagazineHandler) GetIssue(w http.ResponseWriter, r *http.Request) {
|
||||
book := chi.URLParam(r, "book")
|
||||
slug := chi.URLParam(r, "slug")
|
||||
doc, err := h.repo.GetIssue(r.Context(), book, slug)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch issue")
|
||||
return
|
||||
}
|
||||
if doc == nil {
|
||||
response.Error(w, http.StatusNotFound, "issue not found")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, doc)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/prabhupadasays/spsays-bff/internal/repository"
|
||||
"github.com/prabhupadasays/spsays-bff/pkg/response"
|
||||
)
|
||||
|
||||
type OtherDocumentHandler struct {
|
||||
repo *repository.OtherDocumentRepo
|
||||
}
|
||||
|
||||
func NewOtherDocumentHandler(repo *repository.OtherDocumentRepo) *OtherDocumentHandler {
|
||||
return &OtherDocumentHandler{repo: repo}
|
||||
}
|
||||
|
||||
func (h *OtherDocumentHandler) ListDocuments(w http.ResponseWriter, r *http.Request) {
|
||||
docs, err := h.repo.GetAllMetas(r.Context())
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch documents")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *OtherDocumentHandler) GetDocument(w http.ResponseWriter, r *http.Request) {
|
||||
book := chi.URLParam(r, "book")
|
||||
doc, err := h.repo.GetMeta(r.Context(), book)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch document")
|
||||
return
|
||||
}
|
||||
if doc == nil {
|
||||
response.Error(w, http.StatusNotFound, "document not found")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, doc)
|
||||
}
|
||||
|
||||
func (h *OtherDocumentHandler) ListChapters(w http.ResponseWriter, r *http.Request) {
|
||||
book := chi.URLParam(r, "book")
|
||||
docs, err := h.repo.GetChapters(r.Context(), book)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch chapters")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *OtherDocumentHandler) GetChapter(w http.ResponseWriter, r *http.Request) {
|
||||
book := chi.URLParam(r, "book")
|
||||
slug := chi.URLParam(r, "slug")
|
||||
doc, err := h.repo.GetChapter(r.Context(), book, slug)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch chapter")
|
||||
return
|
||||
}
|
||||
if doc == nil {
|
||||
response.Error(w, http.StatusNotFound, "chapter not found")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, doc)
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/prabhupadasays/spsays-bff/internal/repository"
|
||||
"github.com/prabhupadasays/spsays-bff/pkg/response"
|
||||
)
|
||||
|
||||
type TranscriptionHandler struct {
|
||||
repo *repository.TranscriptionRepo
|
||||
}
|
||||
|
||||
func NewTranscriptionHandler(repo *repository.TranscriptionRepo) *TranscriptionHandler {
|
||||
return &TranscriptionHandler{repo: repo}
|
||||
}
|
||||
|
||||
func (h *TranscriptionHandler) GetBySlug(w http.ResponseWriter, r *http.Request) {
|
||||
slug := chi.URLParam(r, "slug")
|
||||
doc, err := h.repo.GetBySlug(r.Context(), slug)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch transcription")
|
||||
return
|
||||
}
|
||||
if doc == nil {
|
||||
response.Error(w, http.StatusNotFound, "transcription not found")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, doc)
|
||||
}
|
||||
|
||||
func (h *TranscriptionHandler) GetByYear(w http.ResponseWriter, r *http.Request) {
|
||||
yearStr := chi.URLParam(r, "year")
|
||||
year, err := strconv.Atoi(yearStr)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusBadRequest, "invalid year")
|
||||
return
|
||||
}
|
||||
docs, err := h.repo.GetByYear(r.Context(), year)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch transcriptions")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *TranscriptionHandler) GetByPlace(w http.ResponseWriter, r *http.Request) {
|
||||
place := chi.URLParam(r, "place")
|
||||
docs, err := h.repo.GetByPlace(r.Context(), place)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch transcriptions")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *TranscriptionHandler) GetByType(w http.ResponseWriter, r *http.Request) {
|
||||
docType := chi.URLParam(r, "type")
|
||||
if docType != "lecture" && docType != "conversation" {
|
||||
response.Error(w, http.StatusBadRequest, "invalid type: must be 'lecture' or 'conversation'")
|
||||
return
|
||||
}
|
||||
docs, err := h.repo.GetByType(r.Context(), docType)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch transcriptions")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *TranscriptionHandler) GetYears(w http.ResponseWriter, r *http.Request) {
|
||||
docs, err := h.repo.GetYears(r.Context())
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch years")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *TranscriptionHandler) GetPlaces(w http.ResponseWriter, r *http.Request) {
|
||||
docs, err := h.repo.GetPlaces(r.Context())
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch places")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, docs)
|
||||
}
|
||||
|
||||
func (h *TranscriptionHandler) GetTypeDistribution(w http.ResponseWriter, r *http.Request) {
|
||||
lectures, conversations, err := h.repo.GetTypeDistribution(r.Context())
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch type distribution")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, map[string]int64{
|
||||
"lectures": lectures,
|
||||
"conversations": conversations,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *TranscriptionHandler) Count(w http.ResponseWriter, r *http.Request) {
|
||||
count, err := h.repo.Count(r.Context())
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to count transcriptions")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, map[string]int64{"count": count})
|
||||
}
|
||||
|
||||
func (h *TranscriptionHandler) GetAdjacent(w http.ResponseWriter, r *http.Request) {
|
||||
slug := chi.URLParam(r, "slug")
|
||||
yearStr := r.URL.Query().Get("year")
|
||||
year, err := strconv.Atoi(yearStr)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusBadRequest, "invalid year")
|
||||
return
|
||||
}
|
||||
prev, next, err := h.repo.GetAdjacent(r.Context(), slug, year)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "failed to fetch adjacent transcriptions")
|
||||
return
|
||||
}
|
||||
response.JSON(w, http.StatusOK, map[string]interface{}{
|
||||
"prev": prev,
|
||||
"next": next,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user