feat: vani of the day edit

This commit is contained in:
Krispire Admin
2026-08-31 13:39:56 +05:30
parent 44fd1d69ab
commit 0fee56d272
4 changed files with 177 additions and 3 deletions
+86 -2
View File
@@ -9,7 +9,9 @@ import (
"errors"
"fmt"
"math/rand"
"net/url"
"regexp"
"strconv"
"strings"
"time"
@@ -32,8 +34,12 @@ func NewVaniService(repo *repository.VaniRepo, logger *zap.Logger) *VaniService
// Today returns the current day's pick, choosing (and persisting) one on the
// first call of the day. Returns (nil, nil) only if there's genuinely no
// content to pick.
// ErrVaniBadKind is a kind outside verse/prose/transcript.
var ErrVaniBadKind = errors.New("kind must be verse, prose or transcript")
var (
// ErrVaniBadKind is a kind outside verse/prose/transcript.
ErrVaniBadKind = errors.New("kind must be verse, prose or transcript")
// ErrVaniBadURL is a link that isn't a verse, prose chapter or lecture.
ErrVaniBadURL = errors.New("that link isn't a verse, chapter or lecture")
)
func (s *VaniService) Today(ctx context.Context) (*model.VaniPayload, error) {
now := time.Now().UTC()
@@ -79,6 +85,84 @@ func (s *VaniService) Today(ctx context.Context) (*model.VaniPayload, error) {
// choose tries the three content kinds in random order and returns the first
// successful sample, so each day's pick varies across content types.
// ResolveURL turns an app URL (or bare path) into a pinnable pick.
//
// The intended admin workflow: paste the link to the thing you want. Search
// cannot serve this — slugs are not indexed, so searching "720720sb-london"
// matches on "london" and returns unrelated lectures.
//
// Accepts an absolute URL or a path, and understands the three shapes the
// vāṇī can point at:
//
// /transcriptions/{slug}
// /books/{book}/{chapter}/{verse} (+ /books/{book}/{division}/{c}/{v})
// /books/{book}/{slug} (prose chapter)
func (s *VaniService) ResolveURL(ctx context.Context, raw string, day time.Time) (*model.VaniPick, error) {
day = day.UTC().Truncate(24 * time.Hour)
path := strings.TrimSpace(raw)
if u, err := url.Parse(path); err == nil && u.Host != "" {
path = u.Path
}
path = strings.Trim(strings.SplitN(path, "?", 2)[0], "/")
parts := strings.Split(path, "/")
if len(parts) < 2 {
return nil, ErrVaniBadURL
}
switch parts[0] {
case "transcriptions":
t, err := s.repo.TranscriptBySlug(ctx, parts[1])
if err != nil {
return nil, err
}
return transcriptPick(t, day), nil
case "books":
book := parts[1]
nums := parts[2:]
// A trailing division segment (SB cantos, CC līlās) shifts the
// chapter/verse pair along by one.
var division *string
if len(nums) == 3 {
d := nums[0]
division, nums = &d, nums[1:]
}
if len(nums) == 2 {
chapter, err1 := strconv.Atoi(nums[0])
verse, err2 := strconv.Atoi(nums[1])
if err1 == nil && err2 == nil {
v, err := s.repo.VerseByRef(ctx, book, division, chapter, verse)
if err != nil {
return nil, err
}
return versePick(v, day), nil
}
}
// Not numeric, so it is a prose chapter's bare slug.
if len(nums) == 1 {
p, err := s.repo.ProseBySlug(ctx, book, nums[0])
if err != nil {
return nil, err
}
return prosePick(p, s.repo.BookTitle(ctx, p.Book), day), nil
}
}
return nil, ErrVaniBadURL
}
// SetByURL resolves a URL and pins the result.
func (s *VaniService) SetByURL(ctx context.Context, raw string, day time.Time) (*model.VaniPick, error) {
pick, err := s.ResolveURL(ctx, raw, day)
if err != nil {
return nil, err
}
if err := s.repo.Upsert(ctx, pick); err != nil {
return nil, err
}
return pick, nil
}
// SetForDay pins a specific piece of content as a given day's Vāṇī.
//
// Takes only a kind and a content id — never a payload. The display blob is