···
13
+
"github.com/bluesky-social/jetstream/pkg/client"
14
+
"github.com/bluesky-social/jetstream/pkg/models"
15
+
"github.com/sotangled/tangled/jetstream"
18
+
// Simple in-memory implementation of DB interface
19
+
type MemoryDB struct {
23
+
func (m *MemoryDB) GetLastTimeUs() (int64, error) {
24
+
if m.lastTimeUs == 0 {
25
+
return time.Now().UnixMicro(), nil
27
+
return m.lastTimeUs, nil
30
+
func (m *MemoryDB) SaveLastTimeUs(ts int64) error {
35
+
func (m *MemoryDB) UpdateLastTimeUs(ts int64) error {
42
+
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
43
+
Level: slog.LevelInfo,
46
+
// Create in-memory DB
49
+
// Get query URL from flag
51
+
flag.StringVar(&queryURL, "query-url", "", "Jetstream query URL containing DIDs")
55
+
logger.Error("No query URL provided, use --query-url flag")
59
+
// Extract wantedDids parameters
60
+
didParams := strings.Split(queryURL, "&wantedDids=")
61
+
dids := make([]string, 0, len(didParams)-1)
62
+
for i, param := range didParams {
64
+
// Skip the first part (the base URL with cursor)
67
+
dids = append(dids, param)
70
+
// Extract collections
71
+
collections := []string{"sh.tangled.publicKey", "sh.tangled.knot.member"}
73
+
// Create client configuration
74
+
cfg := client.DefaultClientConfig()
75
+
cfg.WebsocketURL = "wss://jetstream2.us-west.bsky.network/subscribe"
76
+
cfg.WantedCollections = collections
78
+
// Create jetstream client
79
+
jsClient, err := jetstream.NewJetstreamClient(
81
+
"tangled-jetstream",
89
+
logger.Error("Failed to create jetstream client", "error", err)
94
+
jsClient.UpdateDids(dids)
96
+
// Create a context that will be canceled on SIGINT or SIGTERM
97
+
ctx, cancel := context.WithCancel(context.Background())
100
+
// Setup signal handling with a buffered channel
101
+
sigCh := make(chan os.Signal, 1)
102
+
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
104
+
// Process function for events
105
+
processFunc := func(ctx context.Context, event *models.Event) error {
106
+
// Log the event details
107
+
logger.Info("Received event",
108
+
"collection", event.Commit.Collection,
110
+
"rkey", event.Commit.RKey,
111
+
"action", event.Kind,
112
+
"time_us", event.TimeUS,
115
+
// Save the last time_us
116
+
if err := db.UpdateLastTimeUs(event.TimeUS); err != nil {
117
+
logger.Error("Failed to update last time_us", "error", err)
124
+
if err := jsClient.StartJetstream(ctx, processFunc); err != nil {
125
+
logger.Error("Failed to start jetstream", "error", err)
129
+
// Wait for signal instead of context.Done()
131
+
logger.Info("Received signal, shutting down", "signal", sig)
132
+
cancel() // Cancel context after receiving signal
134
+
// Shutdown gracefully with a timeout
135
+
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
136
+
defer shutdownCancel()
138
+
done := make(chan struct{})
140
+
jsClient.Shutdown()
146
+
logger.Info("Jetstream client shut down gracefully")
147
+
case <-shutdownCtx.Done():
148
+
logger.Warn("Shutdown timed out, forcing exit")