mirror of
https://github.com/GMWalletApp/epusdt.git
synced 2026-07-07 10:16:15 +00:00
feat: add admin panel with multi-chain support and notification system
- Add full admin REST API: auth, API keys, chains, chain tokens, wallets, orders, RPC nodes, settings, dashboard stats, notifications - Add JWT-based admin authentication and API key auth middleware - Add multi-chain listeners: BSC, ETH, Polygon, Plasma, EVM WebSocket - Add RPC node health check job with automatic failover - Add Telegram notification channel for order events - Add installer for first-run database initialization - Add new DB models: admin_user, api_key, chain, chain_token, rpc_node, settings, notification_channel - Add order statistics aggregation (daily/monthly/by-chain) - Serve built admin SPA from embedded www/ assets - Add comprehensive test coverage for all new features
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
// Package notify fans out domain events (order paid, order expired,
|
||||
// etc.) to every NotificationChannel subscribed to the event. Senders
|
||||
// are type-keyed (telegram/webhook/email/...) so adding a new push
|
||||
// method means registering one function; no callers change.
|
||||
package notify
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
|
||||
"github.com/assimon/luuu/model/data"
|
||||
"github.com/assimon/luuu/model/mdb"
|
||||
"github.com/assimon/luuu/util/log"
|
||||
)
|
||||
|
||||
// Sender delivers an already-rendered text message via a channel row.
|
||||
// Config is the channel's Config JSON string (type-specific shape).
|
||||
type Sender func(config, text string) error
|
||||
|
||||
var (
|
||||
sendersMu sync.RWMutex
|
||||
senders = map[string]Sender{}
|
||||
)
|
||||
|
||||
// RegisterSender wires a Sender for a channel type. Called once at
|
||||
// package init of each sender file (e.g. telegram_sender.go).
|
||||
func RegisterSender(channelType string, s Sender) {
|
||||
sendersMu.Lock()
|
||||
senders[channelType] = s
|
||||
sendersMu.Unlock()
|
||||
}
|
||||
|
||||
func getSender(channelType string) (Sender, bool) {
|
||||
sendersMu.RLock()
|
||||
defer sendersMu.RUnlock()
|
||||
s, ok := senders[channelType]
|
||||
return s, ok
|
||||
}
|
||||
|
||||
// Dispatch delivers text to every enabled channel subscribed to event.
|
||||
// Per-channel failures are logged but don't abort the fan-out — one
|
||||
// broken TG token shouldn't prevent webhook delivery.
|
||||
func Dispatch(event, text string) {
|
||||
channels, err := data.ListEnabledChannelsByEvent(event)
|
||||
if err != nil {
|
||||
log.Sugar.Errorf("[notify] list channels failed: %v", err)
|
||||
return
|
||||
}
|
||||
for _, ch := range channels {
|
||||
sender, ok := getSender(ch.Type)
|
||||
if !ok {
|
||||
log.Sugar.Warnf("[notify] no sender registered for type=%s (channel_id=%d)", ch.Type, ch.ID)
|
||||
continue
|
||||
}
|
||||
go func(c mdb.NotificationChannel) {
|
||||
if err := sender(c.Config, text); err != nil {
|
||||
log.Sugar.Errorf("[notify] send failed type=%s channel_id=%d: %v", c.Type, c.ID, err)
|
||||
}
|
||||
}(ch)
|
||||
}
|
||||
}
|
||||
|
||||
// ParseConfig helper for senders: unmarshal channel Config JSON into
|
||||
// an arbitrary struct, returning a typed error on invalid JSON.
|
||||
func ParseConfig(raw string, out interface{}) error {
|
||||
if raw == "" {
|
||||
return ErrEmptyConfig
|
||||
}
|
||||
return json.Unmarshal([]byte(raw), out)
|
||||
}
|
||||
Reference in New Issue
Block a user