mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:01:09 +02:00
feat(sync): Phase 2 — server-side billing gate, cron charging, email notifications
Server-side gating (mana-sync Go):
- New billing.Checker with 5-minute cache per user
- Middleware wraps POST/GET /sync/{appId} endpoints
- Returns 402 Payment Required when sync subscription inactive
- Fail-open: if mana-credits is unreachable, sync is allowed
- Config: MANA_CREDITS_URL + MANA_SERVICE_KEY env vars
Recurring charge cron (mana-credits):
- Hourly setInterval checks for due sync subscriptions
- Calls chargeRecurring() which debits credits and advances nextChargeAt
- On insufficient credits: pauses subscription, sends email via mana-notify
Email notifications:
- Sends "Cloud Sync pausiert" email via mana-notify when subscription paused
- Uses POST /api/v1/notifications/send with X-Service-Key auth
Client-side 402 handling:
- sync.ts detects 402 from push/pull, fires onBillingRequired callback
- Layout wires callback to reload syncBilling store → shows pause banner
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7102063afc
commit
ed76f53b00
7 changed files with 209 additions and 13 deletions
|
|
@ -13,6 +13,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/mana/mana-sync/internal/auth"
|
||||
"github.com/mana/mana-sync/internal/billing"
|
||||
"github.com/mana/mana-sync/internal/config"
|
||||
"github.com/mana/mana-sync/internal/store"
|
||||
syncHandler "github.com/mana/mana-sync/internal/sync"
|
||||
|
|
@ -49,16 +50,20 @@ func main() {
|
|||
// Initialize WebSocket hub (with JWT validator for auth)
|
||||
hub := ws.NewHub(validator)
|
||||
|
||||
// Initialize billing checker (verifies sync subscription via mana-credits)
|
||||
billingChecker := billing.NewChecker(cfg.ManaCreditsURL, cfg.ServiceKey)
|
||||
billingMiddleware := billingChecker.Middleware(validator)
|
||||
|
||||
// Initialize sync handler
|
||||
handler := syncHandler.NewHandler(db, validator, hub)
|
||||
|
||||
// Set up routes
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// Sync endpoints (Go 1.22+ routing patterns)
|
||||
mux.HandleFunc("POST /sync/{appId}", handler.HandleSync)
|
||||
mux.HandleFunc("GET /sync/{appId}/pull", handler.HandlePull)
|
||||
mux.HandleFunc("GET /sync/{appId}/stream", handler.HandleStream)
|
||||
// Sync endpoints (Go 1.22+ routing patterns) — gated by billing check
|
||||
mux.Handle("POST /sync/{appId}", billingMiddleware(http.HandlerFunc(handler.HandleSync)))
|
||||
mux.Handle("GET /sync/{appId}/pull", billingMiddleware(http.HandlerFunc(handler.HandlePull)))
|
||||
mux.Handle("GET /sync/{appId}/stream", billingMiddleware(http.HandlerFunc(handler.HandleStream)))
|
||||
|
||||
// WebSocket endpoints
|
||||
// Unified: one connection per user, receives all app notifications with appId in payload
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue