package handler import ( "encoding/json" "errors" "net/http" "github.com/prabhupadasays/spsays-bff/internal/middleware" "github.com/prabhupadasays/spsays-bff/internal/service" "github.com/prabhupadasays/spsays-bff/pkg/response" ) // NotificationHandler serves the signed-in user's notification feed + // unread badge count, and marks them read. All routes sit behind // RequireAuth, so middleware.UserID is always populated. type NotificationHandler struct{ svc *service.NotificationService } func NewNotificationHandler(svc *service.NotificationService) *NotificationHandler { return &NotificationHandler{svc: svc} } // List returns the caller's notifications, newest first. func (h *NotificationHandler) List(w http.ResponseWriter, r *http.Request) { uid := middleware.UserID(r.Context()) limit, offset := parseLimitOffset(r) items, err := h.svc.List(r.Context(), uid, limit, offset) if err != nil { response.Error(w, http.StatusInternalServerError, "could not load notifications") return } response.JSON(w, http.StatusOK, items) } // UnreadCount returns {"count": N} for the bell badge. func (h *NotificationHandler) UnreadCount(w http.ResponseWriter, r *http.Request) { uid := middleware.UserID(r.Context()) n, err := h.svc.UnreadCount(r.Context(), uid) if err != nil { response.Error(w, http.StatusInternalServerError, "could not load unread count") return } response.JSON(w, http.StatusOK, map[string]int{"count": n}) } // MarkAllRead marks every notification read (called when the page opens). func (h *NotificationHandler) MarkAllRead(w http.ResponseWriter, r *http.Request) { uid := middleware.UserID(r.Context()) if err := h.svc.MarkAllRead(r.Context(), uid); err != nil { response.Error(w, http.StatusInternalServerError, "could not mark notifications read") return } response.NoContent(w) } // GetPrefs — GET /notifications/prefs. The resolved Settings matrix: // {"prefs": {"follows": {"in_app": true, "email": true, "push": true}, …}}. func (h *NotificationHandler) GetPrefs(w http.ResponseWriter, r *http.Request) { uid := middleware.UserID(r.Context()) matrix, err := h.svc.PrefMatrix(r.Context(), uid) if err != nil { response.Error(w, http.StatusInternalServerError, "could not load notification preferences") return } response.JSON(w, http.StatusOK, map[string]any{"prefs": matrix}) } // UpdatePrefs — PUT /notifications/prefs { event, channel, enabled }. Writes // one matrix cell; returns the full re-resolved matrix so the client can // render authoritative state. func (h *NotificationHandler) UpdatePrefs(w http.ResponseWriter, r *http.Request) { uid := middleware.UserID(r.Context()) var req struct { Event string `json:"event"` Channel string `json:"channel"` Enabled bool `json:"enabled"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { response.Error(w, http.StatusBadRequest, "invalid JSON body") return } if err := h.svc.SetPref(r.Context(), uid, req.Event, req.Channel, req.Enabled); err != nil { if errors.Is(err, service.ErrUnknownPref) { response.Error(w, http.StatusBadRequest, "unknown event or channel") return } response.Error(w, http.StatusInternalServerError, "could not update notification preferences") return } matrix, err := h.svc.PrefMatrix(r.Context(), uid) if err != nil { response.Error(w, http.StatusInternalServerError, "could not load notification preferences") return } response.JSON(w, http.StatusOK, map[string]any{"prefs": matrix}) } type emailPrefsResp struct { LikeEmails bool `json:"like_emails"` } // GetEmailPrefs — GET /notifications/email-prefs. The caller's email-notification // preferences (currently just aggregated like emails). func (h *NotificationHandler) GetEmailPrefs(w http.ResponseWriter, r *http.Request) { uid := middleware.UserID(r.Context()) enabled, err := h.svc.LikeEmailPref(r.Context(), uid) if err != nil { response.Error(w, http.StatusInternalServerError, "could not load email preferences") return } response.JSON(w, http.StatusOK, emailPrefsResp{LikeEmails: enabled}) } // UpdateEmailPrefs — PUT /notifications/email-prefs { like_emails }. func (h *NotificationHandler) UpdateEmailPrefs(w http.ResponseWriter, r *http.Request) { uid := middleware.UserID(r.Context()) var req struct { LikeEmails bool `json:"like_emails"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { response.Error(w, http.StatusBadRequest, "invalid JSON body") return } if err := h.svc.SetLikeEmailPref(r.Context(), uid, req.LikeEmails); err != nil { response.Error(w, http.StatusInternalServerError, "could not update email preferences") return } response.JSON(w, http.StatusOK, emailPrefsResp{LikeEmails: req.LikeEmails}) }