feat: add web push and sadhna tracker

This commit is contained in:
Krispire Admin
2026-06-08 15:47:44 +05:30
parent e352948fb1
commit f598770b32
31 changed files with 2267 additions and 2 deletions
+67 -1
View File
@@ -33,6 +33,7 @@ type Server struct {
// Worker lifecycle
workerCancel context.CancelFunc
workerCtx context.Context // derived in initShortener; cancelled on Close
// Handlers
bookH *handler.BookHandler
@@ -55,6 +56,9 @@ type Server struct {
profileH *handler.ProfileHandler
relationH *handler.RelationshipHandler
notificationH *handler.NotificationHandler
sadhnaH *handler.SadhnaHandler
pushH *handler.PushHandler
reminderH *handler.ReminderHandler
feedbackH *handler.FeedbackHandler
statsH *handler.StatsHandler
@@ -202,13 +206,42 @@ func (s *Server) initAuth(cfg *config.Config, logger *zap.Logger) {
cfg.SoketiAppID, cfg.SoketiAppKey, cfg.SoketiAppSecret,
cfg.SoketiHost, cfg.SoketiPort, cfg.SoketiUseTLS, logger,
)
notifSvc := service.NewNotificationService(notifRepo, userRepo, mail, rtClient, logger, cfg.FrontendURL)
pushRepo := repository.NewPushRepo(s.pg)
pushSender := service.NewPushSender(pushRepo, cfg.VAPIDPublicKey, cfg.VAPIDPrivateKey, cfg.VAPIDSubject, logger)
s.pushH = handler.NewPushHandler(pushSender, pushRepo)
notifSvc := service.NewNotificationService(notifRepo, userRepo, mail, rtClient, pushSender, logger, cfg.FrontendURL)
// Sādhana reminders: a background worker nudges users (email + push +
// websocket) to chant / post a quote at their local time. Shares the
// notification seam above so all three channels fire.
reminderRepo := repository.NewReminderRepo(s.pg)
reminderSvc := service.NewReminderService(reminderRepo, notifSvc, logger, cfg.FrontendURL)
s.reminderH = handler.NewReminderHandler(reminderSvc)
wctx := s.workerCtx
if wctx == nil {
wctx = context.Background()
}
worker.NewReminderWorker(reminderSvc, logger).Run(wctx)
graphSvc := service.NewSocialGraphService(followRepo, blockRepo, reportRepo, postRepo, userRepo, notifSvc, mail, cfg.FeedbackAdminEmails, logger)
s.relationH = handler.NewRelationshipHandler(graphSvc)
s.notificationH = handler.NewNotificationHandler(notifSvc)
s.postH = handler.NewPostHandler(postSvc, graphSvc)
s.profileH = handler.NewProfileHandler(userRepo, postRepo, collectionRepo, graphSvc)
// Sādhana streak: daily chanting log + the growing-plant state. Reads
// the chanting half from sadhna_logs and the posting half from the
// existing posts table, so it only needs its own repo plus PostRepo +
// UserRepo (for the rounds target).
sadhnaRepo := repository.NewSadhnaRepo(s.pg)
rewardRepo := repository.NewRewardRepo(s.pg)
// Sync the reward catalog from the embedded rewards.json (idempotent).
if err := rewardRepo.Seed(context.Background()); err != nil {
logger.Warn("sadhna: could not seed reward catalog", zap.Error(err))
}
sadhnaSvc := service.NewSadhnaService(sadhnaRepo, postRepo, userRepo, rewardRepo, logger)
s.sadhnaH = handler.NewSadhnaHandler(sadhnaSvc)
// Feedback + stats — share Postgres with the rest of /api/v1 and
// the existing mailer instance.
feedbackRepo := repository.NewFeedbackRepo(s.pg)
@@ -224,6 +257,7 @@ func (s *Server) initAuth(cfg *config.Config, logger *zap.Logger) {
func (s *Server) initShortener(cfg *config.Config, logger *zap.Logger) {
ctx, cancel := context.WithCancel(context.Background())
s.workerCancel = cancel
s.workerCtx = ctx
pg, err := repository.NewPostgres(ctx, cfg.PostgresURI, logger)
if err != nil {
@@ -740,6 +774,38 @@ func (s *Server) setupRouter() {
r.Get("/unread-count", s.notificationH.UnreadCount)
r.Post("/read", s.notificationH.MarkAllRead)
})
// --- Web Push --- VAPID key + per-user subscription management.
r.Route("/push", func(r chi.Router) {
if s.pushH == nil {
r.HandleFunc("/*", s.authUnavailable)
return
}
r.Use(middleware.RequireAuth(s.authIssuer, s.userRepo))
r.Get("/key", s.pushH.Key)
r.Post("/subscribe", s.pushH.Subscribe)
r.Post("/unsubscribe", s.pushH.Unsubscribe)
})
// --- Sādhana streak --- daily chanting log + growing-plant state.
// All endpoints require a signed-in user.
r.Route("/sadhna", func(r chi.Router) {
if s.sadhnaH == nil {
r.HandleFunc("/*", s.authUnavailable)
return
}
r.Use(middleware.RequireAuth(s.authIssuer, s.userRepo))
r.Get("/dashboard", s.sadhnaH.Dashboard)
r.Get("/history", s.sadhnaH.History)
r.Post("/chant", s.sadhnaH.SaveChant)
r.Get("/rewards", s.sadhnaH.Rewards)
r.Post("/rewards/ack", s.sadhnaH.AckRewards)
if s.reminderH != nil {
r.Get("/reminders", s.reminderH.GetPrefs)
r.Put("/reminders", s.reminderH.UpdatePrefs)
r.Post("/timezone", s.reminderH.SetTimezone)
}
})
})
s.router = r