package handler import ( "encoding/json" "net/http" "strings" "github.com/prabhupadasays/spsays-bff/internal/middleware" "github.com/prabhupadasays/spsays-bff/internal/repository" "github.com/prabhupadasays/spsays-bff/internal/service" "github.com/prabhupadasays/spsays-bff/pkg/response" ) // PushHandler serves Web Push subscription management. The VAPID public key // is handed to the browser so it can subscribe; subscriptions are stored per // user. All routes require auth. type PushHandler struct { push *service.PushSender // optional — nil when VAPID isn't configured repo *repository.PushRepo } func NewPushHandler(push *service.PushSender, repo *repository.PushRepo) *PushHandler { return &PushHandler{push: push, repo: repo} } // Key — GET /push/key. Returns the VAPID public key + whether push is on. func (h *PushHandler) Key(w http.ResponseWriter, r *http.Request) { response.JSON(w, http.StatusOK, struct { PublicKey string `json:"public_key"` Enabled bool `json:"enabled"` }{ PublicKey: h.push.PublicKey(), Enabled: h.push.Enabled(), }) } type subscribeReq struct { Endpoint string `json:"endpoint"` Keys struct { P256dh string `json:"p256dh"` Auth string `json:"auth"` } `json:"keys"` } // Subscribe — POST /push/subscribe. Stores (or refreshes) the caller's push // subscription. Body is the browser PushSubscription JSON. func (h *PushHandler) Subscribe(w http.ResponseWriter, r *http.Request) { if h.repo == nil || !h.push.Enabled() { response.Error(w, http.StatusServiceUnavailable, "push not configured") return } uid := middleware.UserID(r.Context()) var req subscribeReq if err := json.NewDecoder(r.Body).Decode(&req); err != nil { response.Error(w, http.StatusBadRequest, "invalid JSON body") return } if req.Endpoint == "" || req.Keys.P256dh == "" || req.Keys.Auth == "" { response.Error(w, http.StatusBadRequest, "missing subscription fields") return } if err := h.repo.Upsert(r.Context(), uid, req.Endpoint, req.Keys.P256dh, req.Keys.Auth, r.UserAgent()); err != nil { response.Error(w, http.StatusInternalServerError, "could not save subscription") return } response.NoContent(w) } type unsubscribeReq struct { Endpoint string `json:"endpoint"` } // Unsubscribe — POST /push/unsubscribe. Removes a subscription by endpoint. func (h *PushHandler) Unsubscribe(w http.ResponseWriter, r *http.Request) { if h.repo == nil { response.NoContent(w) return } var req unsubscribeReq if err := json.NewDecoder(r.Body).Decode(&req); err != nil || strings.TrimSpace(req.Endpoint) == "" { response.Error(w, http.StatusBadRequest, "missing endpoint") return } if err := h.repo.DeleteByEndpoint(r.Context(), req.Endpoint); err != nil { response.Error(w, http.StatusInternalServerError, "could not remove subscription") return } response.NoContent(w) }