implement backend components for push notifications
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/flowy-live/llink/internal/billing"
|
||||
"github.com/flowy-live/llink/internal/depot"
|
||||
"github.com/flowy-live/llink/internal/human"
|
||||
"github.com/flowy-live/llink/internal/human/pushnotify"
|
||||
"github.com/flowy-live/llink/internal/livekit"
|
||||
"github.com/flowy-live/llink/internal/middleware"
|
||||
"github.com/flowy-live/llink/internal/network"
|
||||
@@ -32,6 +33,7 @@ type Handler struct {
|
||||
depotSvc depot.Service
|
||||
waitlistSvc waitlist.Service
|
||||
billingSvc billing.Service
|
||||
pushTokenSvc pushnotify.Service
|
||||
livekitClient livekit.Client
|
||||
firestoreClient *firestore.Client
|
||||
}
|
||||
@@ -44,6 +46,7 @@ func NewHandler(
|
||||
depotSvc depot.Service,
|
||||
waitlistSvc waitlist.Service,
|
||||
billingSvc billing.Service,
|
||||
pushTokenSvc pushnotify.Service,
|
||||
livekitClient livekit.Client,
|
||||
firestoreClient *firestore.Client,
|
||||
) *Handler {
|
||||
@@ -55,6 +58,7 @@ func NewHandler(
|
||||
depotSvc: depotSvc,
|
||||
waitlistSvc: waitlistSvc,
|
||||
billingSvc: billingSvc,
|
||||
pushTokenSvc: pushTokenSvc,
|
||||
livekitClient: livekitClient,
|
||||
firestoreClient: firestoreClient,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/flowy-live/llink/internal/human/pushnotify"
|
||||
"github.com/flowy-live/llink/internal/middleware"
|
||||
)
|
||||
|
||||
type RegisterPushTokenRequest struct {
|
||||
Token string `json:"token"`
|
||||
Platform string `json:"platform"`
|
||||
AppVersion string `json:"app_version"`
|
||||
}
|
||||
|
||||
type UnregisterPushTokenRequest struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// RegisterPushToken upserts an Expo push token against the authenticated human.
|
||||
// Re-binding a token to a new human (e.g., after a device-level account switch)
|
||||
// happens transparently via ON CONFLICT.
|
||||
func (h *Handler) RegisterPushToken(w http.ResponseWriter, r *http.Request) {
|
||||
humanId, ok := middleware.HumanIdFromContext(r.Context())
|
||||
if !ok {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
var req RegisterPushTokenRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err := h.pushTokenSvc.Register(r.Context(), humanId, pushnotify.RegisterInput{
|
||||
Token: req.Token,
|
||||
Platform: pushnotify.Platform(req.Platform),
|
||||
AppVersion: req.AppVersion,
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, pushnotify.ErrInvalidPlatform) || errors.Is(err, pushnotify.ErrInvalidToken) {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
slog.Error("failed to register push token", "error", err, "humanId", humanId)
|
||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// UnregisterPushToken removes a push token belonging to the authenticated human.
|
||||
// Returns 204 even if the token wasn't found — idempotent from the client's POV.
|
||||
func (h *Handler) UnregisterPushToken(w http.ResponseWriter, r *http.Request) {
|
||||
humanId, ok := middleware.HumanIdFromContext(r.Context())
|
||||
if !ok {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
var req UnregisterPushTokenRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Token == "" {
|
||||
http.Error(w, "token is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err := h.pushTokenSvc.Unregister(r.Context(), humanId, req.Token)
|
||||
if err != nil && !errors.Is(err, pushnotify.ErrNotFound) {
|
||||
slog.Error("failed to unregister push token", "error", err, "humanId", humanId)
|
||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
Reference in New Issue
Block a user