2024-10-23 22:01:10 +02:00
|
|
|
package nats
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2024-11-03 18:43:07 +01:00
|
|
|
"fmt"
|
2024-10-23 22:01:10 +02:00
|
|
|
|
|
|
|
nats "github.com/nats-io/nats.go"
|
|
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
|
|
geEvent "github.com/grassrootseconomics/eth-tracker/pkg/event"
|
2024-11-03 01:34:28 +01:00
|
|
|
"git.defalsify.org/vise.git/logging"
|
2024-11-02 16:38:23 +01:00
|
|
|
"git.defalsify.org/vise.git/db"
|
|
|
|
"git.grassecon.net/urdt/ussd/common"
|
2024-10-23 22:01:10 +02:00
|
|
|
"git.grassecon.net/term/event"
|
2024-11-03 18:43:07 +01:00
|
|
|
"git.grassecon.net/term/config"
|
2024-10-23 22:01:10 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2024-11-03 01:34:28 +01:00
|
|
|
logg = logging.NewVanilla().WithDomain("term-nats")
|
2024-10-23 22:01:10 +02:00
|
|
|
)
|
|
|
|
|
2024-11-03 20:04:44 +01:00
|
|
|
// NatsSubscription encapsulates the jetstream session providing events.
|
|
|
|
//
|
|
|
|
// Extends Router.
|
2024-10-23 22:01:10 +02:00
|
|
|
type NatsSubscription struct {
|
2024-10-24 17:17:04 +02:00
|
|
|
event.Router
|
2024-10-23 22:01:10 +02:00
|
|
|
ctx context.Context
|
|
|
|
conn *nats.Conn
|
|
|
|
js jetstream.JetStream
|
|
|
|
cs jetstream.Consumer
|
|
|
|
cctx jetstream.ConsumeContext
|
|
|
|
}
|
|
|
|
|
2024-11-03 20:04:44 +01:00
|
|
|
// NewNatsSubscription creates a new NatsSubscription with the given user store.
|
2024-11-02 16:38:23 +01:00
|
|
|
func NewNatsSubscription(store db.Db) *NatsSubscription {
|
|
|
|
return &NatsSubscription{
|
|
|
|
Router: event.Router{
|
2024-11-02 17:08:05 +01:00
|
|
|
Store: &common.UserDataStore{
|
2024-11-02 16:38:23 +01:00
|
|
|
Db: store,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2024-10-23 22:01:10 +02:00
|
|
|
}
|
|
|
|
|
2024-11-03 20:04:44 +01:00
|
|
|
// Connect sets up the connection to the nats server and a consumer for the
|
|
|
|
// "Jetstream".
|
|
|
|
//
|
|
|
|
// Fails if connection fails or the "Jetstream" consumer cannot be set up.
|
|
|
|
//
|
|
|
|
// Once connected, it will attempt to reconnect if disconnected.
|
2024-10-23 22:01:10 +02:00
|
|
|
func(n *NatsSubscription) Connect(ctx context.Context, connStr string) error {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
n.conn, err = nats.Connect(connStr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-11-03 18:43:07 +01:00
|
|
|
n.conn.SetDisconnectErrHandler(disconnectHandler)
|
|
|
|
n.conn.SetReconnectHandler(reconnectHandler)
|
2024-10-23 22:01:10 +02:00
|
|
|
n.js, err = jetstream.New(n.conn)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-10-24 17:30:24 +02:00
|
|
|
n.cs, err = n.js.CreateConsumer(ctx, "TRACKER", jetstream.ConsumerConfig{
|
2024-11-03 18:43:07 +01:00
|
|
|
Name: config.JetstreamClientName,
|
|
|
|
Durable: config.JetstreamClientName,
|
2024-10-24 17:30:24 +02:00
|
|
|
FilterSubjects: []string{"TRACKER.*"},
|
2024-10-23 22:01:10 +02:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-11-03 18:43:07 +01:00
|
|
|
serverInfo := toServerInfo(n.conn)
|
|
|
|
logg.DebugCtxf(ctx, "nats connected, starting consumer", "status", n.conn.Status(), "server", serverInfo)
|
2024-10-23 22:01:10 +02:00
|
|
|
n.ctx = ctx
|
|
|
|
n.cctx, err = n.cs.Consume(n.handleEvent)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-11-03 20:04:44 +01:00
|
|
|
// Close cleanly brings down the nats and jetstream connection.
|
2024-10-23 22:01:10 +02:00
|
|
|
func(n *NatsSubscription) Close() error {
|
|
|
|
n.cctx.Stop()
|
|
|
|
select {
|
|
|
|
case <-n.cctx.Closed():
|
|
|
|
n.conn.Close()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-11-03 20:04:44 +01:00
|
|
|
// jetstream message handler and acknowledger.
|
2024-10-23 22:01:10 +02:00
|
|
|
func(n *NatsSubscription) handleEvent(m jetstream.Msg) {
|
|
|
|
var ev geEvent.Event
|
|
|
|
|
2024-11-03 01:34:28 +01:00
|
|
|
logg.DebugCtxf(n.ctx, "have msg", "err", m)
|
2024-10-23 22:01:10 +02:00
|
|
|
b := m.Data()
|
|
|
|
err := json.Unmarshal(b, &ev)
|
|
|
|
if err != nil {
|
2024-11-03 01:34:28 +01:00
|
|
|
logg.ErrorCtxf(n.ctx, "nats msg deserialize fail", "err", err)
|
2024-10-24 17:00:46 +02:00
|
|
|
//fail(m)
|
|
|
|
} else {
|
2024-11-02 16:38:23 +01:00
|
|
|
err = n.Route(n.ctx, &ev)
|
2024-10-24 17:00:46 +02:00
|
|
|
if err != nil {
|
2024-11-03 01:34:28 +01:00
|
|
|
logg.ErrorCtxf(n.ctx, "handler route fail", "err", err)
|
2024-10-24 17:30:24 +02:00
|
|
|
//fail(m)
|
2024-10-24 17:00:46 +02:00
|
|
|
}
|
2024-10-23 22:01:10 +02:00
|
|
|
}
|
2024-10-24 17:30:24 +02:00
|
|
|
err = m.Ack()
|
2024-10-23 22:01:10 +02:00
|
|
|
if err != nil {
|
2024-11-03 01:34:28 +01:00
|
|
|
logg.ErrorCtxf(n.ctx, "ack fail", "err", err)
|
2024-10-24 17:30:24 +02:00
|
|
|
panic("ack fail")
|
2024-10-23 22:01:10 +02:00
|
|
|
}
|
2024-11-03 01:34:28 +01:00
|
|
|
logg.DebugCtxf(n.ctx, "handle msg complete")
|
2024-10-23 22:01:10 +02:00
|
|
|
}
|
2024-11-03 20:04:44 +01:00
|
|
|
|
|
|
|
// used if message should be retried.
|
|
|
|
func fail(m jetstream.Msg) {
|
|
|
|
err := m.Nak()
|
|
|
|
if err != nil {
|
|
|
|
logg.Errorf("nats nak fail", "err", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// server info string for debug.
|
|
|
|
func toServerInfo(conn *nats.Conn) string {
|
|
|
|
return fmt.Sprintf("%s@%s (v%s)", conn.ConnectedServerName(), conn.ConnectedUrlRedacted(), conn.ConnectedServerVersion())
|
|
|
|
}
|
|
|
|
|
|
|
|
// on nats disconnection.
|
|
|
|
func disconnectHandler(conn *nats.Conn, err error) {
|
|
|
|
logg.Errorf("nats disconnected", "status", conn.Status(), "reconnects", conn.Stats().Reconnects, "err", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// on nats reconnection.
|
|
|
|
func reconnectHandler(conn *nats.Conn) {
|
|
|
|
serverInfo := toServerInfo(conn)
|
|
|
|
logg.Errorf("nats reconnected", "status", conn.Status(), "reconnects", conn.Stats().Reconnects, "server", serverInfo)
|
|
|
|
}
|