feat: add eve chanting notif

This commit is contained in:
Krispire Admin
2026-06-08 16:28:15 +05:30
parent 39450c3266
commit 3902033508
5 changed files with 64 additions and 3 deletions
+3 -2
View File
@@ -32,8 +32,9 @@ const (
// Reminder kinds are delivered live (email + push + a websocket toast)
// but NOT persisted — they're ephemeral nudges, not history.
NotifChantReminder = "chant_reminder"
NotifQuoteReminder = "quote_reminder"
NotifChantReminder = "chant_reminder"
NotifQuoteReminder = "quote_reminder"
NotifChantIncomplete = "chant_incomplete_reminder"
)
type NotificationService struct {
+31
View File
@@ -15,6 +15,7 @@ package service
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"time"
@@ -37,6 +38,10 @@ const (
// the overlap.
reminderWindowMin = 20
// The evening "finish your rounds" reminder fires at 21:00 local. Kept
// before the 21:30 quote reminder so the two don't collide in one tick.
chantIncompleteTime = "21:00"
chantMsgStart = "Chant with Prabhupāda — start building your sādhana streak."
chantMsgLapsed = "Just one hour of sādhana can save you from this duḥkhālayam aśāśvatam. Come and build your streak again."
chantMsgYesterday = "Your sādhana was wonderful yesterday! Let's continue your sādhana today as well."
@@ -112,6 +117,21 @@ func (s *ReminderService) RunOnce(ctx context.Context, now time.Time) {
}
}
// Evening "incomplete chanting" reminder — the devotee started their
// rounds today (RoundsToday > 0) but hasn't reached the target by
// 21:00 local. Only nudges those who began, so non-starters get just
// the morning reminder, not a second one. Independent of the morning
// chant reminder (own time + own per-day dedupe).
if c.RoundsToday > 0 && !c.ChantDoneToday && notRemindedToday(c.LastChantIncompleteOn, utcToday) {
if t := parseHHMM(chantIncompleteTime); t >= 0 && withinWindow(localMin, t) {
s.notif.SendReminder(ctx, c.UserID, NotifChantIncomplete,
"Finish your japa 🙏", incompleteChantMessage(c.RoundsToday, c.TargetRounds), s.sadhnaURL)
if err := s.repo.MarkChantIncompleteReminded(ctx, c.UserID, utcToday); err != nil {
s.logger.Warn("reminders: mark chant-incomplete failed", zap.String("user", c.UserID), zap.Error(err))
}
}
}
// Quote reminder.
if !c.QuoteDoneToday && notRemindedToday(c.LastQuoteOn, utcToday) {
if t := parseHHMM(c.QuoteTime); t >= 0 && withinWindow(localMin, t) {
@@ -143,6 +163,17 @@ func chantMessage(lastChant *time.Time, utcToday time.Time) string {
}
}
// incompleteChantMessage nudges a devotee who began their rounds today to
// finish them (only sent when rounds > 0).
func incompleteChantMessage(rounds, target int) string {
if target <= 0 {
target = 16
}
return fmt.Sprintf(
"You've chanted %d of %d rounds today — finish your japa before the day ends. 🙏",
rounds, target)
}
// notRemindedToday is true when no reminder of this kind has gone out on the
// current UTC day.
func notRemindedToday(last *time.Time, utcToday time.Time) bool {