+362
backend/backend.go
+362
backend/backend.go
···
···
1
+
package backend
2
+
3
+
import (
4
+
"context"
5
+
"errors"
6
+
"fmt"
7
+
"strings"
8
+
"sync"
9
+
10
+
"github.com/bluesky-social/indigo/atproto/syntax"
11
+
"github.com/bluesky-social/indigo/util"
12
+
lru "github.com/hashicorp/golang-lru/v2"
13
+
"github.com/jackc/pgx/v5"
14
+
"github.com/jackc/pgx/v5/pgconn"
15
+
"github.com/jackc/pgx/v5/pgxpool"
16
+
. "github.com/whyrusleeping/konbini/models"
17
+
"github.com/whyrusleeping/market/models"
18
+
"gorm.io/gorm"
19
+
"gorm.io/gorm/logger"
20
+
)
21
+
22
+
// PostgresBackend handles database operations
23
+
type PostgresBackend struct {
24
+
db *gorm.DB
25
+
pgx *pgxpool.Pool
26
+
tracker RecordTracker
27
+
28
+
relevantDids map[string]bool
29
+
rdLk sync.Mutex
30
+
31
+
revCache *lru.TwoQueueCache[uint, string]
32
+
33
+
repoCache *lru.TwoQueueCache[string, *Repo]
34
+
reposLk sync.Mutex
35
+
36
+
postInfoCache *lru.TwoQueueCache[string, cachedPostInfo]
37
+
}
38
+
39
+
type cachedPostInfo struct {
40
+
ID uint
41
+
Author uint
42
+
}
43
+
44
+
// NewPostgresBackend creates a new PostgresBackend
45
+
func NewPostgresBackend(db *gorm.DB, pgx *pgxpool.Pool, tracker RecordTracker) (*PostgresBackend, error) {
46
+
rc, err := lru.New2Q[string, *Repo](1_000_000)
47
+
if err != nil {
48
+
return nil, err
49
+
}
50
+
pc, err := lru.New2Q[string, cachedPostInfo](1_000_000)
51
+
if err != nil {
52
+
return nil, err
53
+
}
54
+
revc, err := lru.New2Q[uint, string](1_000_000)
55
+
if err != nil {
56
+
return nil, err
57
+
}
58
+
59
+
return &PostgresBackend{
60
+
db: db,
61
+
pgx: pgx,
62
+
tracker: tracker,
63
+
relevantDids: make(map[string]bool),
64
+
repoCache: rc,
65
+
postInfoCache: pc,
66
+
revCache: revc,
67
+
}, nil
68
+
}
69
+
70
+
// TrackMissingRecord implements the RecordTracker interface
71
+
func (b *PostgresBackend) TrackMissingRecord(identifier string, wait bool) {
72
+
if b.tracker != nil {
73
+
b.tracker.TrackMissingRecord(identifier, wait)
74
+
}
75
+
}
76
+
77
+
// DidToID converts a DID to a database ID
78
+
func (b *PostgresBackend) DidToID(ctx context.Context, did string) (uint, error) {
79
+
r, err := b.getOrCreateRepo(ctx, did)
80
+
if err != nil {
81
+
return 0, err
82
+
}
83
+
return r.ID, nil
84
+
}
85
+
86
+
func (b *PostgresBackend) getOrCreateRepo(ctx context.Context, did string) (*Repo, error) {
87
+
r, ok := b.repoCache.Get(did)
88
+
if !ok {
89
+
b.reposLk.Lock()
90
+
91
+
r, ok = b.repoCache.Get(did)
92
+
if !ok {
93
+
r = &Repo{}
94
+
r.Did = did
95
+
b.repoCache.Add(did, r)
96
+
}
97
+
98
+
b.reposLk.Unlock()
99
+
}
100
+
101
+
r.Lk.Lock()
102
+
defer r.Lk.Unlock()
103
+
if r.Setup {
104
+
return r, nil
105
+
}
106
+
107
+
row := b.pgx.QueryRow(ctx, "SELECT id, created_at, did FROM repos WHERE did = $1", did)
108
+
109
+
err := row.Scan(&r.ID, &r.CreatedAt, &r.Did)
110
+
if err == nil {
111
+
// found it!
112
+
r.Setup = true
113
+
return r, nil
114
+
}
115
+
116
+
if err != pgx.ErrNoRows {
117
+
return nil, err
118
+
}
119
+
120
+
r.Did = did
121
+
if err := b.db.Create(r).Error; err != nil {
122
+
return nil, err
123
+
}
124
+
125
+
r.Setup = true
126
+
127
+
return r, nil
128
+
}
129
+
130
+
func (b *PostgresBackend) getOrCreateList(ctx context.Context, uri string) (*List, error) {
131
+
puri, err := util.ParseAtUri(uri)
132
+
if err != nil {
133
+
return nil, err
134
+
}
135
+
136
+
r, err := b.getOrCreateRepo(ctx, puri.Did)
137
+
if err != nil {
138
+
return nil, err
139
+
}
140
+
141
+
// TODO: needs upsert treatment when we actually find the list
142
+
var list List
143
+
if err := b.db.FirstOrCreate(&list, map[string]any{
144
+
"author": r.ID,
145
+
"rkey": puri.Rkey,
146
+
}).Error; err != nil {
147
+
return nil, err
148
+
}
149
+
return &list, nil
150
+
}
151
+
152
+
func (b *PostgresBackend) postIDForUri(ctx context.Context, uri string) (uint, error) {
153
+
// getPostByUri implicitly fills the cache
154
+
p, err := b.postInfoForUri(ctx, uri)
155
+
if err != nil {
156
+
return 0, err
157
+
}
158
+
159
+
return p.ID, nil
160
+
}
161
+
162
+
func (b *PostgresBackend) postInfoForUri(ctx context.Context, uri string) (cachedPostInfo, error) {
163
+
v, ok := b.postInfoCache.Get(uri)
164
+
if ok {
165
+
return v, nil
166
+
}
167
+
168
+
// getPostByUri implicitly fills the cache
169
+
p, err := b.getOrCreatePostBare(ctx, uri)
170
+
if err != nil {
171
+
return cachedPostInfo{}, err
172
+
}
173
+
174
+
return cachedPostInfo{ID: p.ID, Author: p.Author}, nil
175
+
}
176
+
177
+
func (b *PostgresBackend) tryLoadPostInfo(ctx context.Context, uid uint, rkey string) (*Post, error) {
178
+
var p Post
179
+
q := "SELECT id, author FROM posts WHERE author = $1 AND rkey = $2"
180
+
if err := b.pgx.QueryRow(ctx, q, uid, rkey).Scan(&p.ID, &p.Author); err != nil {
181
+
if errors.Is(err, pgx.ErrNoRows) {
182
+
return nil, nil
183
+
}
184
+
return nil, err
185
+
}
186
+
187
+
return &p, nil
188
+
}
189
+
190
+
func (b *PostgresBackend) getOrCreatePostBare(ctx context.Context, uri string) (*Post, error) {
191
+
puri, err := util.ParseAtUri(uri)
192
+
if err != nil {
193
+
return nil, err
194
+
}
195
+
196
+
r, err := b.getOrCreateRepo(ctx, puri.Did)
197
+
if err != nil {
198
+
return nil, err
199
+
}
200
+
201
+
post, err := b.tryLoadPostInfo(ctx, r.ID, puri.Rkey)
202
+
if err != nil {
203
+
return nil, err
204
+
}
205
+
206
+
if post == nil {
207
+
post = &Post{
208
+
Rkey: puri.Rkey,
209
+
Author: r.ID,
210
+
NotFound: true,
211
+
}
212
+
213
+
err := b.pgx.QueryRow(ctx, "INSERT INTO posts (rkey, author, not_found) VALUES ($1, $2, $3) RETURNING id", puri.Rkey, r.ID, true).Scan(&post.ID)
214
+
if err != nil {
215
+
pgErr, ok := err.(*pgconn.PgError)
216
+
if !ok || pgErr.Code != "23505" {
217
+
return nil, err
218
+
}
219
+
220
+
out, err := b.tryLoadPostInfo(ctx, r.ID, puri.Rkey)
221
+
if err != nil {
222
+
return nil, fmt.Errorf("got duplicate post and still couldnt find it: %w", err)
223
+
}
224
+
if out == nil {
225
+
return nil, fmt.Errorf("postgres is lying to us: %d %s", r.ID, puri.Rkey)
226
+
}
227
+
228
+
post = out
229
+
}
230
+
231
+
}
232
+
233
+
b.postInfoCache.Add(uri, cachedPostInfo{
234
+
ID: post.ID,
235
+
Author: post.Author,
236
+
})
237
+
238
+
return post, nil
239
+
}
240
+
241
+
func (b *PostgresBackend) getPostByUri(ctx context.Context, uri string, fields string) (*Post, error) {
242
+
puri, err := util.ParseAtUri(uri)
243
+
if err != nil {
244
+
return nil, err
245
+
}
246
+
247
+
r, err := b.getOrCreateRepo(ctx, puri.Did)
248
+
if err != nil {
249
+
return nil, err
250
+
}
251
+
252
+
q := "SELECT " + fields + " FROM posts WHERE author = ? AND rkey = ?"
253
+
254
+
var post Post
255
+
if err := b.db.Raw(q, r.ID, puri.Rkey).Scan(&post).Error; err != nil {
256
+
return nil, err
257
+
}
258
+
259
+
if post.ID == 0 {
260
+
post.Rkey = puri.Rkey
261
+
post.Author = r.ID
262
+
post.NotFound = true
263
+
264
+
if err := b.db.Session(&gorm.Session{
265
+
Logger: logger.Default.LogMode(logger.Silent),
266
+
}).Create(&post).Error; err != nil {
267
+
if !errors.Is(err, gorm.ErrDuplicatedKey) {
268
+
return nil, err
269
+
}
270
+
if err := b.db.Find(&post, "author = ? AND rkey = ?", r.ID, puri.Rkey).Error; err != nil {
271
+
return nil, fmt.Errorf("got duplicate post and still couldnt find it: %w", err)
272
+
}
273
+
}
274
+
275
+
}
276
+
277
+
b.postInfoCache.Add(uri, cachedPostInfo{
278
+
ID: post.ID,
279
+
Author: post.Author,
280
+
})
281
+
282
+
return &post, nil
283
+
}
284
+
285
+
func (b *PostgresBackend) revForRepo(rr *Repo) (string, error) {
286
+
lrev, ok := b.revCache.Get(rr.ID)
287
+
if ok {
288
+
return lrev, nil
289
+
}
290
+
291
+
var rev string
292
+
if err := b.pgx.QueryRow(context.TODO(), "SELECT COALESCE(rev, '') FROM sync_infos WHERE repo = $1", rr.ID).Scan(&rev); err != nil {
293
+
if errors.Is(err, pgx.ErrNoRows) {
294
+
return "", nil
295
+
}
296
+
return "", err
297
+
}
298
+
299
+
if rev != "" {
300
+
b.revCache.Add(rr.ID, rev)
301
+
}
302
+
return rev, nil
303
+
}
304
+
305
+
func (b *PostgresBackend) addRelevantDid(did string) {
306
+
b.rdLk.Lock()
307
+
defer b.rdLk.Unlock()
308
+
b.relevantDids[did] = true
309
+
}
310
+
311
+
func (b *PostgresBackend) didIsRelevant(did string) bool {
312
+
b.rdLk.Lock()
313
+
defer b.rdLk.Unlock()
314
+
return b.relevantDids[did]
315
+
}
316
+
317
+
func (b *PostgresBackend) anyRelevantIdents(idents ...string) bool {
318
+
for _, id := range idents {
319
+
if strings.HasPrefix(id, "did:") {
320
+
if b.didIsRelevant(id) {
321
+
return true
322
+
}
323
+
} else if strings.HasPrefix(id, "at://") {
324
+
puri, err := syntax.ParseATURI(id)
325
+
if err != nil {
326
+
continue
327
+
}
328
+
329
+
if b.didIsRelevant(puri.Authority().String()) {
330
+
return true
331
+
}
332
+
}
333
+
}
334
+
335
+
return false
336
+
}
337
+
338
+
func (b *PostgresBackend) getRepoByID(ctx context.Context, id uint) (*models.Repo, error) {
339
+
var r models.Repo
340
+
if err := b.db.Find(&r, "id = ?", id).Error; err != nil {
341
+
return nil, err
342
+
}
343
+
344
+
return &r, nil
345
+
}
346
+
347
+
func (b *PostgresBackend) checkPostExists(ctx context.Context, repo *Repo, rkey string) (bool, error) {
348
+
var id uint
349
+
var notfound bool
350
+
if err := b.pgx.QueryRow(ctx, "SELECT id, not_found FROM posts WHERE author = $1 AND rkey = $2", repo.ID, rkey).Scan(&id, ¬found); err != nil {
351
+
if errors.Is(err, pgx.ErrNoRows) {
352
+
return false, nil
353
+
}
354
+
return false, err
355
+
}
356
+
357
+
if id != 0 && !notfound {
358
+
return true, nil
359
+
}
360
+
361
+
return false, nil
362
+
}
+12
backend/interface.go
+12
backend/interface.go
···
···
1
+
package backend
2
+
3
+
// RecordTracker is an interface for tracking missing records that need to be fetched
4
+
type RecordTracker interface {
5
+
// TrackMissingRecord queues a missing record for fetching
6
+
// identifier can be:
7
+
// - A DID (e.g., "did:plc:...") for actors/profiles
8
+
// - An AT-URI (e.g., "at://did:plc:.../app.bsky.feed.post/...") for posts
9
+
// - An AT-URI (e.g., "at://did:plc:.../app.bsky.feed.generator/...") for feed generators
10
+
// wait: if true, blocks until the record is fetched
11
+
TrackMissingRecord(identifier string, wait bool)
12
+
}
+13
-5
go.mod
+13
-5
go.mod
···
4
5
require (
6
github.com/bluesky-social/indigo v0.0.0-20250909204019-c5eaa30f683f
7
-
github.com/golang-jwt/jwt/v5 v5.2.2
8
github.com/gorilla/websocket v1.5.1
9
github.com/hashicorp/golang-lru/v2 v2.0.7
10
github.com/ipfs/go-cid v0.4.1
11
github.com/jackc/pgx/v5 v5.6.0
12
github.com/labstack/echo/v4 v4.11.3
13
github.com/labstack/gommon v0.4.1
14
github.com/prometheus/client_golang v1.19.1
15
github.com/urfave/cli/v2 v2.27.7
16
github.com/whyrusleeping/market v0.0.0-20250711215409-cc684a207f15
17
gorm.io/gorm v1.31.0
18
)
19
···
25
github.com/cespare/xxhash/v2 v2.3.0 // indirect
26
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
27
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
28
github.com/felixge/httpsnoop v1.0.4 // indirect
29
github.com/go-logr/logr v1.4.2 // indirect
30
github.com/go-logr/stdr v1.2.2 // indirect
31
github.com/goccy/go-json v0.10.5 // indirect
32
github.com/gogo/protobuf v1.3.2 // indirect
33
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
···
62
github.com/jbenet/goprocess v0.1.4 // indirect
63
github.com/jinzhu/inflection v1.0.0 // indirect
64
github.com/jinzhu/now v1.1.5 // indirect
65
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
66
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
67
github.com/lestrrat-go/httpcc v1.0.1 // indirect
68
github.com/lestrrat-go/httprc v1.0.4 // indirect
69
github.com/lestrrat-go/iter v1.0.2 // indirect
70
-
github.com/lestrrat-go/jwx/v2 v2.0.12 // indirect
71
github.com/lestrrat-go/option v1.0.1 // indirect
72
github.com/libp2p/go-libp2p v0.25.1 // indirect
73
github.com/mattn/go-colorable v0.1.13 // indirect
···
79
github.com/multiformats/go-base36 v0.2.0 // indirect
80
github.com/multiformats/go-multiaddr v0.8.0 // indirect
81
github.com/multiformats/go-multibase v0.2.0 // indirect
82
-
github.com/multiformats/go-multihash v0.2.3 // indirect
83
github.com/multiformats/go-varint v0.0.7 // indirect
84
github.com/opentracing/opentracing-go v1.2.0 // indirect
85
github.com/orandin/slog-gorm v1.3.2 // indirect
···
87
github.com/prometheus/client_model v0.6.1 // indirect
88
github.com/prometheus/common v0.48.0 // indirect
89
github.com/prometheus/procfs v0.12.0 // indirect
90
github.com/russross/blackfriday/v2 v2.1.0 // indirect
91
github.com/segmentio/asm v1.2.0 // indirect
92
github.com/spaolacci/murmur3 v1.1.0 // indirect
93
github.com/valyala/bytebufferpool v1.0.0 // indirect
94
github.com/valyala/fasttemplate v1.2.2 // indirect
95
-
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e // indirect
96
github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6 // indirect
97
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
98
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect
99
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
100
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
101
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
102
-
go.opentelemetry.io/otel v1.34.0 // indirect
103
go.opentelemetry.io/otel/metric v1.34.0 // indirect
104
go.opentelemetry.io/otel/trace v1.34.0 // indirect
105
go.uber.org/atomic v1.11.0 // indirect
···
4
5
require (
6
github.com/bluesky-social/indigo v0.0.0-20250909204019-c5eaa30f683f
7
github.com/gorilla/websocket v1.5.1
8
github.com/hashicorp/golang-lru/v2 v2.0.7
9
github.com/ipfs/go-cid v0.4.1
10
github.com/jackc/pgx/v5 v5.6.0
11
github.com/labstack/echo/v4 v4.11.3
12
github.com/labstack/gommon v0.4.1
13
+
github.com/lestrrat-go/jwx/v2 v2.0.12
14
+
github.com/multiformats/go-multihash v0.2.3
15
github.com/prometheus/client_golang v1.19.1
16
github.com/urfave/cli/v2 v2.27.7
17
+
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e
18
github.com/whyrusleeping/market v0.0.0-20250711215409-cc684a207f15
19
+
go.opentelemetry.io/otel v1.34.0
20
+
go.opentelemetry.io/otel/exporters/jaeger v1.17.0
21
+
go.opentelemetry.io/otel/sdk v1.34.0
22
gorm.io/gorm v1.31.0
23
)
24
···
30
github.com/cespare/xxhash/v2 v2.3.0 // indirect
31
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
32
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
33
+
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
34
github.com/felixge/httpsnoop v1.0.4 // indirect
35
github.com/go-logr/logr v1.4.2 // indirect
36
github.com/go-logr/stdr v1.2.2 // indirect
37
+
github.com/go-redis/cache/v9 v9.0.0 // indirect
38
github.com/goccy/go-json v0.10.5 // indirect
39
github.com/gogo/protobuf v1.3.2 // indirect
40
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
···
69
github.com/jbenet/goprocess v0.1.4 // indirect
70
github.com/jinzhu/inflection v1.0.0 // indirect
71
github.com/jinzhu/now v1.1.5 // indirect
72
+
github.com/klauspost/compress v1.17.3 // indirect
73
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
74
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
75
github.com/lestrrat-go/httpcc v1.0.1 // indirect
76
github.com/lestrrat-go/httprc v1.0.4 // indirect
77
github.com/lestrrat-go/iter v1.0.2 // indirect
78
github.com/lestrrat-go/option v1.0.1 // indirect
79
github.com/libp2p/go-libp2p v0.25.1 // indirect
80
github.com/mattn/go-colorable v0.1.13 // indirect
···
86
github.com/multiformats/go-base36 v0.2.0 // indirect
87
github.com/multiformats/go-multiaddr v0.8.0 // indirect
88
github.com/multiformats/go-multibase v0.2.0 // indirect
89
github.com/multiformats/go-varint v0.0.7 // indirect
90
github.com/opentracing/opentracing-go v1.2.0 // indirect
91
github.com/orandin/slog-gorm v1.3.2 // indirect
···
93
github.com/prometheus/client_model v0.6.1 // indirect
94
github.com/prometheus/common v0.48.0 // indirect
95
github.com/prometheus/procfs v0.12.0 // indirect
96
+
github.com/redis/go-redis/v9 v9.3.0 // indirect
97
github.com/russross/blackfriday/v2 v2.1.0 // indirect
98
github.com/segmentio/asm v1.2.0 // indirect
99
github.com/spaolacci/murmur3 v1.1.0 // indirect
100
github.com/valyala/bytebufferpool v1.0.0 // indirect
101
github.com/valyala/fasttemplate v1.2.2 // indirect
102
+
github.com/vmihailenco/go-tinylfu v0.2.2 // indirect
103
+
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
104
+
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
105
github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6 // indirect
106
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
107
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect
108
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
109
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
110
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
111
go.opentelemetry.io/otel/metric v1.34.0 // indirect
112
go.opentelemetry.io/otel/trace v1.34.0 // indirect
113
go.uber.org/atomic v1.11.0 // indirect
+145
-2
go.sum
+145
-2
go.sum
···
10
github.com/bluesky-social/indigo v0.0.0-20250909204019-c5eaa30f683f/go.mod h1:n6QE1NDPFoi7PRbMUZmc2y7FibCqiVU4ePpsvhHUBR8=
11
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous=
12
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
13
github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc=
14
github.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8=
15
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
16
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
17
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
18
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
19
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
20
github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0=
21
github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TIRis=
22
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
···
25
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
26
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
27
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
28
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
29
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
30
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
31
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
32
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
33
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
34
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
35
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
36
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
37
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
38
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
39
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
40
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
41
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
···
44
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
45
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
46
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
47
-
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
48
-
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
49
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
50
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
51
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
52
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
53
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
54
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
55
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
···
69
github.com/hashicorp/golang-lru/arc/v2 v2.0.6/go.mod h1:cfdDIX05DWvYV6/shsxDfa/OVcRieOt+q4FnM8x+Xno=
70
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
71
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
72
github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ=
73
github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y=
74
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
75
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
76
github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ=
···
151
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
152
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
153
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
154
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
155
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
156
github.com/koron/go-ssdp v0.0.3 h1:JivLMY45N76b4p/vsWGOKewBQu6uf39y8l+AQ7sDKx8=
157
github.com/koron/go-ssdp v0.0.3/go.mod h1:b2MxI6yh02pKrsyNoQUsk4+YNikaGhe4894J+Q5lDvA=
158
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
159
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
160
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
161
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
···
231
github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q=
232
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
233
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
234
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
235
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
236
github.com/orandin/slog-gorm v1.3.2 h1:C0lKDQPAx/pF+8K2HL7bdShPwOEJpPM0Bn80zTzxU1g=
···
250
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
251
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
252
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
253
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
254
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
255
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
256
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
···
267
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
268
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
269
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
270
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
271
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
272
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
273
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
274
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
275
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
276
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
277
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
278
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
279
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
280
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
···
285
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
286
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
287
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
288
github.com/warpfork/go-testmark v0.12.1 h1:rMgCpJfwy1sJ50x0M0NgyphxYYPMOODIJHhsXyEHU0s=
289
github.com/warpfork/go-testmark v0.12.1/go.mod h1:kHwy7wfvGSPh1rQJYKayD4AbtNaeyZdcGi9tNJTaa5Y=
290
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=
···
302
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
303
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
304
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
305
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
306
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA=
307
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8=
···
313
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
314
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
315
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
316
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
317
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
318
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
319
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
320
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
···
338
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
339
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
340
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
341
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
342
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
343
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
···
348
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
349
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
350
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
351
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
352
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
353
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
354
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
355
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
356
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
357
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
358
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
359
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
360
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
361
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
362
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
363
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
364
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
365
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
366
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
367
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
368
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
369
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
···
372
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
373
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
374
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
375
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
376
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
377
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
378
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
379
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
380
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
381
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
382
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
383
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
384
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
385
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
386
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
387
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
388
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
389
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
···
392
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
393
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
394
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
395
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
396
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
397
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
398
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
399
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
400
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
401
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
402
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
403
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
···
413
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
414
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
415
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
416
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
417
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
418
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
419
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
420
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
421
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
···
425
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
426
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
427
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
428
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
429
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
430
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
431
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
432
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
433
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
434
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
435
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
436
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
437
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
438
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
439
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
···
10
github.com/bluesky-social/indigo v0.0.0-20250909204019-c5eaa30f683f/go.mod h1:n6QE1NDPFoi7PRbMUZmc2y7FibCqiVU4ePpsvhHUBR8=
11
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous=
12
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
13
+
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
14
+
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
15
+
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
16
+
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
17
github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc=
18
github.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8=
19
+
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
20
+
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
21
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
22
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
23
+
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
24
+
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
25
+
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
26
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
27
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
28
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
29
+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
30
github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0=
31
github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TIRis=
32
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
···
35
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
36
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
37
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
38
+
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
39
+
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
40
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
41
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
42
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
43
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
44
+
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
45
+
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
46
+
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
47
+
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
48
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
49
+
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
50
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
51
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
52
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
53
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
54
+
github.com/go-redis/cache/v9 v9.0.0 h1:0thdtFo0xJi0/WXbRVu8B066z8OvVymXTJGaXrVWnN0=
55
+
github.com/go-redis/cache/v9 v9.0.0/go.mod h1:cMwi1N8ASBOufbIvk7cdXe2PbPjK/WMRL95FFHWsSgI=
56
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
57
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
58
+
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
59
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
60
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
61
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
···
64
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
65
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
66
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
67
+
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
68
+
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
69
+
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
70
+
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
71
+
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
72
+
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
73
+
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
74
+
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
75
+
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
76
+
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
77
+
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
78
+
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
79
+
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
80
+
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
81
+
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
82
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
83
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
84
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
85
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
86
+
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
87
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
88
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
89
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
···
103
github.com/hashicorp/golang-lru/arc/v2 v2.0.6/go.mod h1:cfdDIX05DWvYV6/shsxDfa/OVcRieOt+q4FnM8x+Xno=
104
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
105
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
106
+
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
107
github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ=
108
github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y=
109
+
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
110
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
111
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
112
github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ=
···
187
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
188
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
189
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
190
+
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
191
+
github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA=
192
+
github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
193
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
194
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
195
github.com/koron/go-ssdp v0.0.3 h1:JivLMY45N76b4p/vsWGOKewBQu6uf39y8l+AQ7sDKx8=
196
github.com/koron/go-ssdp v0.0.3/go.mod h1:b2MxI6yh02pKrsyNoQUsk4+YNikaGhe4894J+Q5lDvA=
197
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
198
+
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
199
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
200
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
201
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
···
271
github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q=
272
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
273
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
274
+
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
275
+
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
276
+
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
277
+
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
278
+
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
279
+
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
280
+
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
281
+
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
282
+
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
283
+
github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=
284
+
github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
285
+
github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0=
286
+
github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=
287
+
github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw=
288
+
github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo=
289
+
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
290
+
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
291
+
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
292
+
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
293
+
github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
294
+
github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc=
295
+
github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM=
296
+
github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
297
+
github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM=
298
+
github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y=
299
+
github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
300
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
301
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
302
github.com/orandin/slog-gorm v1.3.2 h1:C0lKDQPAx/pF+8K2HL7bdShPwOEJpPM0Bn80zTzxU1g=
···
316
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
317
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
318
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
319
+
github.com/redis/go-redis/v9 v9.0.0-rc.4/go.mod h1:Vo3EsyWnicKnSKCA7HhgnvnyA74wOA69Cd2Meli5mmA=
320
+
github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0=
321
+
github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
322
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
323
+
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
324
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
325
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
326
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
···
337
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
338
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
339
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
340
+
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
341
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
342
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
343
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
344
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
345
+
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
346
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
347
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
348
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
349
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
350
+
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
351
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
352
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
353
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
···
358
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
359
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
360
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
361
+
github.com/vmihailenco/go-tinylfu v0.2.2 h1:H1eiG6HM36iniK6+21n9LLpzx1G9R3DJa2UjUjbynsI=
362
+
github.com/vmihailenco/go-tinylfu v0.2.2/go.mod h1:CutYi2Q9puTxfcolkliPq4npPuofg9N9t8JVrjzwa3Q=
363
+
github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
364
+
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
365
+
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
366
+
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
367
+
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
368
github.com/warpfork/go-testmark v0.12.1 h1:rMgCpJfwy1sJ50x0M0NgyphxYYPMOODIJHhsXyEHU0s=
369
github.com/warpfork/go-testmark v0.12.1/go.mod h1:kHwy7wfvGSPh1rQJYKayD4AbtNaeyZdcGi9tNJTaa5Y=
370
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=
···
382
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
383
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
384
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
385
+
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
386
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
387
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA=
388
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8=
···
394
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
395
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
396
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
397
+
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
398
+
go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI=
399
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
400
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
401
+
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
402
+
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
403
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
404
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
405
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
···
423
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
424
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
425
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
426
+
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
427
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
428
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
429
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
···
434
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
435
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
436
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
437
+
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
438
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
439
+
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
440
+
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
441
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
442
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
443
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
444
+
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
445
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
446
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
447
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
448
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
449
+
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
450
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
451
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
452
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
453
+
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
454
+
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
455
+
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
456
+
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
457
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
458
+
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
459
+
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
460
+
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
461
+
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
462
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
463
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
464
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
465
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
466
+
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
467
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
468
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
469
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
···
472
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
473
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
474
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
475
+
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
476
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
477
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
478
+
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
479
+
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
480
+
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
481
+
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
482
+
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
483
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
484
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
485
+
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
486
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
487
+
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
488
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
489
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
490
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
491
+
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
492
+
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
493
+
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
494
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
495
+
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
496
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
497
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
498
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
499
+
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
500
+
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
501
+
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
502
+
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
503
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
504
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
505
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
···
508
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
509
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
510
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
511
+
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
512
+
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
513
+
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
514
+
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
515
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
516
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
517
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
518
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
519
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
520
+
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
521
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
522
+
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
523
+
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
524
+
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
525
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
526
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
527
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
···
537
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
538
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
539
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
540
+
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
541
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
542
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
543
+
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
544
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
545
+
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
546
+
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
547
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
548
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
549
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
···
553
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
554
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
555
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
556
+
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
557
+
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
558
+
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
559
+
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
560
+
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
561
+
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
562
+
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
563
+
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
564
+
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
565
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
566
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
567
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
568
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
569
+
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
570
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
571
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
572
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
573
+
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
574
+
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
575
+
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
576
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
577
+
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
578
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
579
+
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
580
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
581
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
582
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+3
hydration/actor.go
+3
hydration/actor.go
···
22
23
// HydrateActor hydrates full actor information
24
func (h *Hydrator) HydrateActor(ctx context.Context, did string) (*ActorInfo, error) {
25
+
ctx, span := tracer.Start(ctx, "hydrateActor")
26
+
defer span.End()
27
+
28
// Look up handle
29
resp, err := h.dir.LookupDID(ctx, syntax.DID(did))
30
if err != nil {
+10
-18
hydration/hydrator.go
+10
-18
hydration/hydrator.go
···
2
3
import (
4
"github.com/bluesky-social/indigo/atproto/identity"
5
"gorm.io/gorm"
6
)
7
8
// Hydrator handles data hydration from the database
9
type Hydrator struct {
10
-
db *gorm.DB
11
-
dir identity.Directory
12
-
13
-
missingRecordCallback func(string, bool)
14
}
15
16
// NewHydrator creates a new Hydrator
17
-
func NewHydrator(db *gorm.DB, dir identity.Directory) *Hydrator {
18
return &Hydrator{
19
-
db: db,
20
-
dir: dir,
21
}
22
}
23
24
-
// SetMissingRecordCallback sets the callback for when a record is missing
25
-
// The callback receives an identifier which can be:
26
-
// - A DID (e.g., "did:plc:...") for actors/profiles
27
-
// - An AT-URI (e.g., "at://did:plc:.../app.bsky.feed.post/...") for posts
28
-
// - An AT-URI (e.g., "at://did:plc:.../app.bsky.feed.generator/...") for feed generators
29
-
func (h *Hydrator) SetMissingRecordCallback(fn func(string, bool)) {
30
-
h.missingRecordCallback = fn
31
-
}
32
-
33
// AddMissingRecord reports a missing record that needs to be fetched
34
func (h *Hydrator) AddMissingRecord(identifier string, wait bool) {
35
-
if h.missingRecordCallback != nil {
36
-
h.missingRecordCallback(identifier, wait)
37
}
38
}
39
···
2
3
import (
4
"github.com/bluesky-social/indigo/atproto/identity"
5
+
"github.com/whyrusleeping/konbini/backend"
6
"gorm.io/gorm"
7
)
8
9
// Hydrator handles data hydration from the database
10
type Hydrator struct {
11
+
db *gorm.DB
12
+
dir identity.Directory
13
+
backend backend.RecordTracker
14
}
15
16
// NewHydrator creates a new Hydrator
17
+
func NewHydrator(db *gorm.DB, dir identity.Directory, backend backend.RecordTracker) *Hydrator {
18
return &Hydrator{
19
+
db: db,
20
+
dir: dir,
21
+
backend: backend,
22
}
23
}
24
25
// AddMissingRecord reports a missing record that needs to be fetched
26
func (h *Hydrator) AddMissingRecord(identifier string, wait bool) {
27
+
if h.backend != nil {
28
+
h.backend.TrackMissingRecord(identifier, wait)
29
}
30
}
31
+334
-5
hydration/post.go
+334
-5
hydration/post.go
···
5
"context"
6
"fmt"
7
"log/slog"
8
9
"github.com/bluesky-social/indigo/api/bsky"
10
)
11
12
// PostInfo contains hydrated post information
13
type PostInfo struct {
···
22
RepostCount int
23
ReplyCount int
24
ViewerLike string // URI of viewer's like, if any
25
}
26
27
const fakeCid = "bafyreiapw4hagb5ehqgoeho4v23vf7fhlqey4b7xvjpy76krgkqx7xlolu"
28
29
// HydratePost hydrates a single post by URI
30
func (h *Hydrator) HydratePost(ctx context.Context, uri string, viewerDID string) (*PostInfo, error) {
31
// Query post from database
32
var dbPost struct {
33
ID uint
···
64
return nil, fmt.Errorf("failed to unmarshal post: %w", err)
65
}
66
67
// Get author DID
68
var authorDID string
69
-
h.db.Raw("SELECT did FROM repos WHERE id = ?", dbPost.AuthorID).Scan(&authorDID)
70
71
// Get engagement counts
72
-
var likes, reposts, replies int
73
-
h.db.Raw("SELECT COUNT(*) FROM likes WHERE subject = ?", dbPost.ID).Scan(&likes)
74
-
h.db.Raw("SELECT COUNT(*) FROM reposts WHERE subject = ?", dbPost.ID).Scan(&reposts)
75
-
h.db.Raw("SELECT COUNT(*) FROM posts WHERE reply_to = ?", dbPost.ID).Scan(&replies)
76
77
info := &PostInfo{
78
URI: uri,
···
105
}
106
}
107
108
return info, nil
109
}
110
···
150
}
151
return ""
152
}
···
5
"context"
6
"fmt"
7
"log/slog"
8
+
"sync"
9
10
"github.com/bluesky-social/indigo/api/bsky"
11
+
"github.com/bluesky-social/indigo/lex/util"
12
+
"go.opentelemetry.io/otel"
13
)
14
+
15
+
var tracer = otel.Tracer("hydrator")
16
17
// PostInfo contains hydrated post information
18
type PostInfo struct {
···
27
RepostCount int
28
ReplyCount int
29
ViewerLike string // URI of viewer's like, if any
30
+
31
+
EmbedInfo *bsky.FeedDefs_PostView_Embed
32
}
33
34
const fakeCid = "bafyreiapw4hagb5ehqgoeho4v23vf7fhlqey4b7xvjpy76krgkqx7xlolu"
35
36
// HydratePost hydrates a single post by URI
37
func (h *Hydrator) HydratePost(ctx context.Context, uri string, viewerDID string) (*PostInfo, error) {
38
+
ctx, span := tracer.Start(ctx, "hydratePost")
39
+
defer span.End()
40
+
41
// Query post from database
42
var dbPost struct {
43
ID uint
···
74
return nil, fmt.Errorf("failed to unmarshal post: %w", err)
75
}
76
77
+
var wg sync.WaitGroup
78
+
79
// Get author DID
80
var authorDID string
81
+
var likes, reposts, replies int
82
+
83
+
wg.Go(func() {
84
+
h.db.Raw("SELECT did FROM repos WHERE id = ?", dbPost.AuthorID).Scan(&authorDID)
85
+
})
86
87
// Get engagement counts
88
+
wg.Go(func() {
89
+
h.db.Raw("SELECT COUNT(*) FROM likes WHERE subject = ?", dbPost.ID).Scan(&likes)
90
+
})
91
+
wg.Go(func() {
92
+
h.db.Raw("SELECT COUNT(*) FROM reposts WHERE subject = ?", dbPost.ID).Scan(&reposts)
93
+
})
94
+
wg.Go(func() {
95
+
h.db.Raw("SELECT COUNT(*) FROM posts WHERE reply_to = ?", dbPost.ID).Scan(&replies)
96
+
})
97
+
98
+
wg.Wait()
99
100
info := &PostInfo{
101
URI: uri,
···
128
}
129
}
130
131
+
// Hydrate embed
132
+
if feedPost.Embed != nil {
133
+
info.EmbedInfo = h.formatEmbed(ctx, feedPost.Embed, authorDID, viewerDID)
134
+
}
135
+
136
return info, nil
137
}
138
···
178
}
179
return ""
180
}
181
+
182
+
func (h *Hydrator) formatEmbed(ctx context.Context, embed *bsky.FeedPost_Embed, authorDID string, viewerDID string) *bsky.FeedDefs_PostView_Embed {
183
+
if embed == nil {
184
+
return nil
185
+
}
186
+
187
+
result := &bsky.FeedDefs_PostView_Embed{}
188
+
189
+
// Handle images
190
+
if embed.EmbedImages != nil {
191
+
viewImages := make([]*bsky.EmbedImages_ViewImage, len(embed.EmbedImages.Images))
192
+
for i, img := range embed.EmbedImages.Images {
193
+
// Convert blob to CDN URLs
194
+
fullsize := ""
195
+
thumb := ""
196
+
if img.Image != nil {
197
+
// CDN URL format for feed images
198
+
cid := img.Image.Ref.String()
199
+
fullsize = fmt.Sprintf("https://cdn.bsky.app/img/feed_fullsize/plain/%s/%s@jpeg", authorDID, cid)
200
+
thumb = fmt.Sprintf("https://cdn.bsky.app/img/feed_thumbnail/plain/%s/%s@jpeg", authorDID, cid)
201
+
}
202
+
203
+
viewImages[i] = &bsky.EmbedImages_ViewImage{
204
+
Alt: img.Alt,
205
+
AspectRatio: img.AspectRatio,
206
+
Fullsize: fullsize,
207
+
Thumb: thumb,
208
+
}
209
+
}
210
+
result.EmbedImages_View = &bsky.EmbedImages_View{
211
+
LexiconTypeID: "app.bsky.embed.images#view",
212
+
Images: viewImages,
213
+
}
214
+
return result
215
+
}
216
+
217
+
// Handle external links
218
+
if embed.EmbedExternal != nil && embed.EmbedExternal.External != nil {
219
+
// Convert blob thumb to CDN URL if present
220
+
var thumbURL *string
221
+
if embed.EmbedExternal.External.Thumb != nil {
222
+
// CDN URL for external link thumbnails
223
+
cid := embed.EmbedExternal.External.Thumb.Ref.String()
224
+
url := fmt.Sprintf("https://cdn.bsky.app/img/feed_thumbnail/plain/%s/%s@jpeg", authorDID, cid)
225
+
thumbURL = &url
226
+
}
227
+
228
+
result.EmbedExternal_View = &bsky.EmbedExternal_View{
229
+
LexiconTypeID: "app.bsky.embed.external#view",
230
+
External: &bsky.EmbedExternal_ViewExternal{
231
+
Uri: embed.EmbedExternal.External.Uri,
232
+
Title: embed.EmbedExternal.External.Title,
233
+
Description: embed.EmbedExternal.External.Description,
234
+
Thumb: thumbURL,
235
+
},
236
+
}
237
+
return result
238
+
}
239
+
240
+
// Handle video
241
+
if embed.EmbedVideo != nil && embed.EmbedVideo.Video != nil {
242
+
cid := embed.EmbedVideo.Video.Ref.String()
243
+
// URL-encode the DID (replace : with %3A)
244
+
encodedDID := ""
245
+
for _, ch := range authorDID {
246
+
if ch == ':' {
247
+
encodedDID += "%3A"
248
+
} else {
249
+
encodedDID += string(ch)
250
+
}
251
+
}
252
+
253
+
playlist := fmt.Sprintf("https://video.bsky.app/watch/%s/%s/playlist.m3u8", encodedDID, cid)
254
+
thumbnail := fmt.Sprintf("https://video.bsky.app/watch/%s/%s/thumbnail.jpg", encodedDID, cid)
255
+
256
+
result.EmbedVideo_View = &bsky.EmbedVideo_View{
257
+
LexiconTypeID: "app.bsky.embed.video#view",
258
+
Cid: cid,
259
+
Playlist: playlist,
260
+
Thumbnail: &thumbnail,
261
+
Alt: embed.EmbedVideo.Alt,
262
+
AspectRatio: embed.EmbedVideo.AspectRatio,
263
+
}
264
+
return result
265
+
}
266
+
267
+
// Handle record (quote posts, etc.)
268
+
if embed.EmbedRecord != nil && embed.EmbedRecord.Record != nil {
269
+
rec := embed.EmbedRecord.Record
270
+
271
+
result.EmbedRecord_View = &bsky.EmbedRecord_View{
272
+
LexiconTypeID: "app.bsky.embed.record#view",
273
+
Record: h.hydrateEmbeddedRecord(ctx, rec.Uri, viewerDID),
274
+
}
275
+
return result
276
+
}
277
+
278
+
// Handle record with media (quote post with images/external)
279
+
if embed.EmbedRecordWithMedia != nil {
280
+
recordView := &bsky.EmbedRecordWithMedia_View{
281
+
LexiconTypeID: "app.bsky.embed.recordWithMedia#view",
282
+
}
283
+
284
+
// Hydrate the record part
285
+
if embed.EmbedRecordWithMedia.Record != nil && embed.EmbedRecordWithMedia.Record.Record != nil {
286
+
recordView.Record = &bsky.EmbedRecord_View{
287
+
LexiconTypeID: "app.bsky.embed.record#view",
288
+
Record: h.hydrateEmbeddedRecord(ctx, embed.EmbedRecordWithMedia.Record.Record.Uri, viewerDID),
289
+
}
290
+
}
291
+
292
+
// Hydrate the media part (images or external)
293
+
if embed.EmbedRecordWithMedia.Media != nil {
294
+
if embed.EmbedRecordWithMedia.Media.EmbedImages != nil {
295
+
viewImages := make([]*bsky.EmbedImages_ViewImage, len(embed.EmbedRecordWithMedia.Media.EmbedImages.Images))
296
+
for i, img := range embed.EmbedRecordWithMedia.Media.EmbedImages.Images {
297
+
fullsize := ""
298
+
thumb := ""
299
+
if img.Image != nil {
300
+
cid := img.Image.Ref.String()
301
+
fullsize = fmt.Sprintf("https://cdn.bsky.app/img/feed_fullsize/plain/%s/%s@jpeg", authorDID, cid)
302
+
thumb = fmt.Sprintf("https://cdn.bsky.app/img/feed_thumbnail/plain/%s/%s@jpeg", authorDID, cid)
303
+
}
304
+
305
+
viewImages[i] = &bsky.EmbedImages_ViewImage{
306
+
Alt: img.Alt,
307
+
AspectRatio: img.AspectRatio,
308
+
Fullsize: fullsize,
309
+
Thumb: thumb,
310
+
}
311
+
}
312
+
recordView.Media = &bsky.EmbedRecordWithMedia_View_Media{
313
+
EmbedImages_View: &bsky.EmbedImages_View{
314
+
LexiconTypeID: "app.bsky.embed.images#view",
315
+
Images: viewImages,
316
+
},
317
+
}
318
+
} else if embed.EmbedRecordWithMedia.Media.EmbedExternal != nil && embed.EmbedRecordWithMedia.Media.EmbedExternal.External != nil {
319
+
var thumbURL *string
320
+
if embed.EmbedRecordWithMedia.Media.EmbedExternal.External.Thumb != nil {
321
+
cid := embed.EmbedRecordWithMedia.Media.EmbedExternal.External.Thumb.Ref.String()
322
+
url := fmt.Sprintf("https://cdn.bsky.app/img/feed_thumbnail/plain/%s/%s@jpeg", authorDID, cid)
323
+
thumbURL = &url
324
+
}
325
+
326
+
recordView.Media = &bsky.EmbedRecordWithMedia_View_Media{
327
+
EmbedExternal_View: &bsky.EmbedExternal_View{
328
+
LexiconTypeID: "app.bsky.embed.external#view",
329
+
External: &bsky.EmbedExternal_ViewExternal{
330
+
Uri: embed.EmbedRecordWithMedia.Media.EmbedExternal.External.Uri,
331
+
Title: embed.EmbedRecordWithMedia.Media.EmbedExternal.External.Title,
332
+
Description: embed.EmbedRecordWithMedia.Media.EmbedExternal.External.Description,
333
+
Thumb: thumbURL,
334
+
},
335
+
},
336
+
}
337
+
} else if embed.EmbedRecordWithMedia.Media.EmbedVideo != nil && embed.EmbedRecordWithMedia.Media.EmbedVideo.Video != nil {
338
+
cid := embed.EmbedRecordWithMedia.Media.EmbedVideo.Video.Ref.String()
339
+
// URL-encode the DID (replace : with %3A)
340
+
encodedDID := ""
341
+
for _, ch := range authorDID {
342
+
if ch == ':' {
343
+
encodedDID += "%3A"
344
+
} else {
345
+
encodedDID += string(ch)
346
+
}
347
+
}
348
+
349
+
playlist := fmt.Sprintf("https://video.bsky.app/watch/%s/%s/playlist.m3u8", encodedDID, cid)
350
+
thumbnail := fmt.Sprintf("https://video.bsky.app/watch/%s/%s/thumbnail.jpg", encodedDID, cid)
351
+
352
+
recordView.Media = &bsky.EmbedRecordWithMedia_View_Media{
353
+
EmbedVideo_View: &bsky.EmbedVideo_View{
354
+
LexiconTypeID: "app.bsky.embed.video#view",
355
+
Cid: cid,
356
+
Playlist: playlist,
357
+
Thumbnail: &thumbnail,
358
+
Alt: embed.EmbedRecordWithMedia.Media.EmbedVideo.Alt,
359
+
AspectRatio: embed.EmbedRecordWithMedia.Media.EmbedVideo.AspectRatio,
360
+
},
361
+
}
362
+
}
363
+
}
364
+
365
+
result.EmbedRecordWithMedia_View = recordView
366
+
return result
367
+
}
368
+
369
+
return nil
370
+
}
371
+
372
+
// hydrateEmbeddedRecord hydrates an embedded record (for quote posts, etc.)
373
+
func (h *Hydrator) hydrateEmbeddedRecord(ctx context.Context, uri string, viewerDID string) *bsky.EmbedRecord_View_Record {
374
+
// Check if it's a post URI
375
+
if !isPostURI(uri) {
376
+
// Could be a feed generator, list, labeler, or starter pack
377
+
// For now, return not found for non-post embeds
378
+
return &bsky.EmbedRecord_View_Record{
379
+
EmbedRecord_ViewNotFound: &bsky.EmbedRecord_ViewNotFound{
380
+
LexiconTypeID: "app.bsky.embed.record#viewNotFound",
381
+
Uri: uri,
382
+
},
383
+
}
384
+
}
385
+
386
+
// Try to hydrate the post
387
+
quotedPost, err := h.HydratePost(ctx, uri, viewerDID)
388
+
if err != nil {
389
+
// Post not found
390
+
return &bsky.EmbedRecord_View_Record{
391
+
EmbedRecord_ViewNotFound: &bsky.EmbedRecord_ViewNotFound{
392
+
LexiconTypeID: "app.bsky.embed.record#viewNotFound",
393
+
Uri: uri,
394
+
NotFound: true,
395
+
},
396
+
}
397
+
}
398
+
399
+
// Hydrate the author
400
+
authorInfo, err := h.HydrateActor(ctx, quotedPost.Author)
401
+
if err != nil {
402
+
// Author not found, treat as not found
403
+
return &bsky.EmbedRecord_View_Record{
404
+
EmbedRecord_ViewNotFound: &bsky.EmbedRecord_ViewNotFound{
405
+
LexiconTypeID: "app.bsky.embed.record#viewNotFound",
406
+
Uri: uri,
407
+
NotFound: true,
408
+
},
409
+
}
410
+
}
411
+
412
+
// TODO: Check if viewer has blocked or is blocked by the author
413
+
// For now, just return the record view
414
+
415
+
// Build the author profile view
416
+
authorView := &bsky.ActorDefs_ProfileViewBasic{
417
+
Did: authorInfo.DID,
418
+
Handle: authorInfo.Handle,
419
+
}
420
+
if authorInfo.Profile != nil {
421
+
if authorInfo.Profile.DisplayName != nil && *authorInfo.Profile.DisplayName != "" {
422
+
authorView.DisplayName = authorInfo.Profile.DisplayName
423
+
}
424
+
if authorInfo.Profile.Avatar != nil {
425
+
avatarURL := fmt.Sprintf("https://cdn.bsky.app/img/avatar_thumbnail/plain/%s/%s@jpeg", authorInfo.DID, authorInfo.Profile.Avatar.Ref.String())
426
+
authorView.Avatar = &avatarURL
427
+
}
428
+
}
429
+
430
+
// Build the embedded post view
431
+
embedView := &bsky.EmbedRecord_ViewRecord{
432
+
LexiconTypeID: "app.bsky.embed.record#viewRecord",
433
+
Uri: quotedPost.URI,
434
+
Cid: quotedPost.Cid,
435
+
Author: authorView,
436
+
Value: &util.LexiconTypeDecoder{
437
+
Val: quotedPost.Post,
438
+
},
439
+
IndexedAt: quotedPost.Post.CreatedAt,
440
+
}
441
+
442
+
// Add engagement counts
443
+
if quotedPost.LikeCount > 0 {
444
+
lc := int64(quotedPost.LikeCount)
445
+
embedView.LikeCount = &lc
446
+
}
447
+
if quotedPost.RepostCount > 0 {
448
+
rc := int64(quotedPost.RepostCount)
449
+
embedView.RepostCount = &rc
450
+
}
451
+
if quotedPost.ReplyCount > 0 {
452
+
rpc := int64(quotedPost.ReplyCount)
453
+
embedView.ReplyCount = &rpc
454
+
}
455
+
456
+
// Note: We don't recursively hydrate embeds for quoted posts to avoid deep nesting
457
+
// The official app also doesn't show embeds within quoted posts
458
+
459
+
return &bsky.EmbedRecord_View_Record{
460
+
EmbedRecord_ViewRecord: embedView,
461
+
}
462
+
}
463
+
464
+
// isPostURI checks if a URI is a post URI
465
+
func isPostURI(uri string) bool {
466
+
return len(uri) > 5 && uri[:5] == "at://" && (
467
+
// Check if it contains /app.bsky.feed.post/
468
+
len(uri) > 25 && uri[len(uri)-25:len(uri)-12] == "/app.bsky.feed.post/" ||
469
+
// More flexible check
470
+
contains(uri, "/app.bsky.feed.post/"))
471
+
}
472
+
473
+
// contains checks if a string contains a substring
474
+
func contains(s, substr string) bool {
475
+
for i := 0; i <= len(s)-len(substr); i++ {
476
+
if s[i:i+len(substr)] == substr {
477
+
return true
478
+
}
479
+
}
480
+
return false
481
+
}
+50
main.go
+50
main.go
···
17
18
"github.com/bluesky-social/indigo/api/atproto"
19
"github.com/bluesky-social/indigo/atproto/identity"
20
"github.com/bluesky-social/indigo/atproto/syntax"
21
"github.com/bluesky-social/indigo/cmd/relay/stream"
22
"github.com/bluesky-social/indigo/cmd/relay/stream/schedulers/parallel"
···
31
"github.com/prometheus/client_golang/prometheus/promauto"
32
"github.com/urfave/cli/v2"
33
"github.com/whyrusleeping/konbini/xrpc"
34
"gorm.io/gorm/logger"
35
36
. "github.com/whyrusleeping/konbini/models"
···
56
Name: "db-url",
57
EnvVars: []string{"DATABASE_URL"},
58
},
59
&cli.StringFlag{
60
Name: "handle",
61
},
62
&cli.IntFlag{
63
Name: "max-db-connections",
64
Value: runtime.NumCPU(),
65
},
66
}
67
app.Action = func(cctx *cli.Context) error {
···
77
Colorful: true,
78
})
79
80
db.AutoMigrate(Repo{})
81
db.AutoMigrate(Post{})
82
db.AutoMigrate(Follow{})
···
124
password := os.Getenv("BSKY_PASSWORD")
125
126
dir := identity.DefaultDirectory()
127
128
resp, err := dir.LookupHandle(ctx, syntax.Handle(handle))
129
if err != nil {
···
17
18
"github.com/bluesky-social/indigo/api/atproto"
19
"github.com/bluesky-social/indigo/atproto/identity"
20
+
"github.com/bluesky-social/indigo/atproto/identity/redisdir"
21
"github.com/bluesky-social/indigo/atproto/syntax"
22
"github.com/bluesky-social/indigo/cmd/relay/stream"
23
"github.com/bluesky-social/indigo/cmd/relay/stream/schedulers/parallel"
···
32
"github.com/prometheus/client_golang/prometheus/promauto"
33
"github.com/urfave/cli/v2"
34
"github.com/whyrusleeping/konbini/xrpc"
35
+
"go.opentelemetry.io/otel"
36
+
"go.opentelemetry.io/otel/attribute"
37
+
"go.opentelemetry.io/otel/exporters/jaeger"
38
+
"go.opentelemetry.io/otel/sdk/resource"
39
+
tracesdk "go.opentelemetry.io/otel/sdk/trace"
40
+
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
41
"gorm.io/gorm/logger"
42
43
. "github.com/whyrusleeping/konbini/models"
···
63
Name: "db-url",
64
EnvVars: []string{"DATABASE_URL"},
65
},
66
+
&cli.BoolFlag{
67
+
Name: "jaeger",
68
+
},
69
&cli.StringFlag{
70
Name: "handle",
71
},
72
&cli.IntFlag{
73
Name: "max-db-connections",
74
Value: runtime.NumCPU(),
75
+
},
76
+
&cli.StringFlag{
77
+
Name: "redis-url",
78
},
79
}
80
app.Action = func(cctx *cli.Context) error {
···
90
Colorful: true,
91
})
92
93
+
if cctx.Bool("jaeger") {
94
+
// Use Jaeger native exporter sending to port 14268
95
+
jaegerUrl := "http://localhost:14268/api/traces"
96
+
exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(jaegerUrl)))
97
+
if err != nil {
98
+
return err
99
+
}
100
+
101
+
env := os.Getenv("ENV")
102
+
if env == "" {
103
+
env = "development"
104
+
}
105
+
106
+
tp := tracesdk.NewTracerProvider(
107
+
// Always be sure to batch in production.
108
+
tracesdk.WithBatcher(exp),
109
+
// Record information about this application in a Resource.
110
+
tracesdk.WithResource(resource.NewWithAttributes(
111
+
semconv.SchemaURL,
112
+
semconv.ServiceNameKey.String("konbini"),
113
+
attribute.String("env", env), // DataDog
114
+
attribute.String("environment", env), // Others
115
+
attribute.Int64("ID", 1),
116
+
)),
117
+
)
118
+
119
+
otel.SetTracerProvider(tp)
120
+
}
121
+
122
db.AutoMigrate(Repo{})
123
db.AutoMigrate(Post{})
124
db.AutoMigrate(Follow{})
···
166
password := os.Getenv("BSKY_PASSWORD")
167
168
dir := identity.DefaultDirectory()
169
+
170
+
if redisURL := cctx.String("redis-url"); redisURL != "" {
171
+
rdir, err := redisdir.NewRedisDirectory(dir, redisURL, time.Minute, time.Second*10, time.Second*10, 100_000)
172
+
if err != nil {
173
+
return err
174
+
}
175
+
dir = rdir
176
+
}
177
178
resp, err := dir.LookupHandle(ctx, syntax.Handle(handle))
179
if err != nil {
+3
-86
views/feed.go
+3
-86
views/feed.go
···
42
}
43
}
44
45
-
// Add embed handling
46
-
if post.Post.Embed != nil {
47
-
view.Embed = formatEmbed(post.Post.Embed, post.Author)
48
}
49
50
return view
···
68
// For now leaving them as interface{} to be handled by handlers
69
70
return view
71
-
}
72
-
73
-
func formatEmbed(embed *bsky.FeedPost_Embed, authorDID string) *bsky.FeedDefs_PostView_Embed {
74
-
if embed == nil {
75
-
return nil
76
-
}
77
-
78
-
result := &bsky.FeedDefs_PostView_Embed{}
79
-
80
-
// Handle images
81
-
if embed.EmbedImages != nil {
82
-
viewImages := make([]*bsky.EmbedImages_ViewImage, len(embed.EmbedImages.Images))
83
-
for i, img := range embed.EmbedImages.Images {
84
-
// Convert blob to CDN URLs
85
-
fullsize := ""
86
-
thumb := ""
87
-
if img.Image != nil {
88
-
// CDN URL format for feed images
89
-
cid := img.Image.Ref.String()
90
-
fullsize = fmt.Sprintf("https://cdn.bsky.app/img/feed_fullsize/plain/%s/%s@jpeg", authorDID, cid)
91
-
thumb = fmt.Sprintf("https://cdn.bsky.app/img/feed_thumbnail/plain/%s/%s@jpeg", authorDID, cid)
92
-
}
93
-
94
-
viewImages[i] = &bsky.EmbedImages_ViewImage{
95
-
Alt: img.Alt,
96
-
AspectRatio: img.AspectRatio,
97
-
Fullsize: fullsize,
98
-
Thumb: thumb,
99
-
}
100
-
}
101
-
result.EmbedImages_View = &bsky.EmbedImages_View{
102
-
LexiconTypeID: "app.bsky.embed.images#view",
103
-
Images: viewImages,
104
-
}
105
-
return result
106
-
}
107
-
108
-
// Handle external links
109
-
if embed.EmbedExternal != nil && embed.EmbedExternal.External != nil {
110
-
// Convert blob thumb to CDN URL if present
111
-
var thumbURL *string
112
-
if embed.EmbedExternal.External.Thumb != nil {
113
-
// CDN URL for external link thumbnails
114
-
cid := embed.EmbedExternal.External.Thumb.Ref.String()
115
-
url := fmt.Sprintf("https://cdn.bsky.app/img/feed_thumbnail/plain/%s/%s@jpeg", authorDID, cid)
116
-
thumbURL = &url
117
-
}
118
-
119
-
result.EmbedExternal_View = &bsky.EmbedExternal_View{
120
-
LexiconTypeID: "app.bsky.embed.external#view",
121
-
External: &bsky.EmbedExternal_ViewExternal{
122
-
Uri: embed.EmbedExternal.External.Uri,
123
-
Title: embed.EmbedExternal.External.Title,
124
-
Description: embed.EmbedExternal.External.Description,
125
-
Thumb: thumbURL,
126
-
},
127
-
}
128
-
return result
129
-
}
130
-
131
-
// Handle video
132
-
if embed.EmbedVideo != nil {
133
-
// TODO: Implement video embed view
134
-
// This would require converting video blob to CDN URLs and playlist URLs
135
-
return nil
136
-
}
137
-
138
-
// Handle record (quote posts, etc.)
139
-
if embed.EmbedRecord != nil {
140
-
// TODO: Implement record embed view
141
-
// This requires hydrating the embedded record, which is complex
142
-
// For now, return nil to skip these embeds
143
-
return nil
144
-
}
145
-
146
-
// Handle record with media (quote post with images/external)
147
-
if embed.EmbedRecordWithMedia != nil {
148
-
// TODO: Implement record with media embed view
149
-
// This combines record hydration with media conversion
150
-
return nil
151
-
}
152
-
153
-
return nil
154
}
155
156
// GeneratorView builds a feed generator view (app.bsky.feed.defs#generatorView)
···
42
}
43
}
44
45
+
// Add embed if it was hydrated
46
+
if post.EmbedInfo != nil {
47
+
view.Embed = post.EmbedInfo
48
}
49
50
return view
···
68
// For now leaving them as interface{} to be handled by handlers
69
70
return view
71
}
72
73
// GeneratorView builds a feed generator view (app.bsky.feed.defs#generatorView)
+17
-4
xrpc/feed/getAuthorFeed.go
+17
-4
xrpc/feed/getAuthorFeed.go
···
5
"log/slog"
6
"net/http"
7
"strconv"
8
"sync"
9
"time"
10
···
126
}
127
128
func hydratePostRows(ctx context.Context, hydrator *hydration.Hydrator, viewer string, rows []postRow) []*bsky.FeedDefs_FeedViewPost {
129
// Hydrate posts
130
var wg sync.WaitGroup
131
···
138
139
postInfo, err := hydrator.HydratePost(ctx, row.URI, viewer)
140
if err != nil {
141
-
slog.Error("failed to hydrate post", "uri", row.URI, "error", err)
142
-
return
143
}
144
145
-
// Hydrate author
146
authorInfo, err := hydrator.HydrateActor(ctx, postInfo.Author)
147
if err != nil {
148
-
slog.Error("failed to hydrate actor", "actor", postInfo.Author, "error", err)
149
return
150
}
151
···
5
"log/slog"
6
"net/http"
7
"strconv"
8
+
"strings"
9
"sync"
10
"time"
11
···
127
}
128
129
func hydratePostRows(ctx context.Context, hydrator *hydration.Hydrator, viewer string, rows []postRow) []*bsky.FeedDefs_FeedViewPost {
130
+
ctx, span := tracer.Start(ctx, "hydratePostRows")
131
+
defer span.End()
132
+
133
// Hydrate posts
134
var wg sync.WaitGroup
135
···
142
143
postInfo, err := hydrator.HydratePost(ctx, row.URI, viewer)
144
if err != nil {
145
+
if strings.Contains(err.Error(), "post not found") {
146
+
hydrator.AddMissingRecord(row.URI, true)
147
+
postInfo, err = hydrator.HydratePost(ctx, row.URI, viewer)
148
+
if err != nil {
149
+
slog.Error("failed to hydrate post after fetch missing", "uri", row.URI, "error", err)
150
+
return
151
+
}
152
+
} else {
153
+
slog.Warn("failed to hydrate post", "uri", row.URI, "error", err)
154
+
return
155
+
}
156
}
157
158
authorInfo, err := hydrator.HydrateActor(ctx, postInfo.Author)
159
if err != nil {
160
+
hydrator.AddMissingRecord(postInfo.Author, false)
161
+
slog.Warn("failed to hydrate author", "did", postInfo.Author, "error", err)
162
return
163
}
164
+2
-1
xrpc/feed/getFeed.go
+2
-1
xrpc/feed/getFeed.go
···
151
}
152
153
// Hydrate the posts from the skeleton
154
-
posts := make([]*bsky.FeedDefs_FeedViewPost, 0, len(skeleton.Feed))
155
var wg sync.WaitGroup
156
for i := range skeleton.Feed {
157
wg.Add(1)
···
181
182
authorInfo, err := hydrator.HydrateActor(ctx, postInfo.Author)
183
if err != nil {
184
slog.Warn("failed to hydrate author", "did", postInfo.Author, "error", err)
185
return
186
}
···
151
}
152
153
// Hydrate the posts from the skeleton
154
+
posts := make([]*bsky.FeedDefs_FeedViewPost, len(skeleton.Feed))
155
var wg sync.WaitGroup
156
for i := range skeleton.Feed {
157
wg.Add(1)
···
181
182
authorInfo, err := hydrator.HydrateActor(ctx, postInfo.Author)
183
if err != nil {
184
+
hydrator.AddMissingRecord(postInfo.Author, false)
185
slog.Warn("failed to hydrate author", "did", postInfo.Author, "error", err)
186
return
187
}
+34
-16
xrpc/feed/getTimeline.go
+34
-16
xrpc/feed/getTimeline.go
···
1
package feed
2
3
import (
4
"net/http"
5
"strconv"
6
"time"
7
8
"github.com/labstack/echo/v4"
9
"github.com/whyrusleeping/konbini/hydration"
10
"gorm.io/gorm"
11
)
12
13
// HandleGetTimeline implements app.bsky.feed.getTimeline
14
func HandleGetTimeline(c echo.Context, db *gorm.DB, hydrator *hydration.Hydrator) error {
15
viewer := getUserDID(c)
16
if viewer == "" {
17
return c.JSON(http.StatusUnauthorized, map[string]any{
···
36
}
37
}
38
39
-
ctx := c.Request().Context()
40
-
41
// Get viewer's repo ID
42
var viewerRepoID uint
43
if err := db.Raw("SELECT id FROM repos WHERE did = ?", viewer).Scan(&viewerRepoID).Error; err != nil {
···
48
}
49
50
// Query posts from followed users
51
-
var rows []postRow
52
-
err := db.Raw(`
53
-
SELECT
54
-
'at://' || r.did || '/app.bsky.feed.post/' || p.rkey as uri,
55
-
p.author as author_id
56
-
FROM posts p
57
-
JOIN repos r ON r.id = p.author
58
-
WHERE p.reply_to = 0
59
-
AND p.author IN (SELECT subject FROM follows WHERE author = ?)
60
-
AND p.created < ?
61
-
AND p.not_found = false
62
-
ORDER BY p.created DESC
63
-
LIMIT ?
64
-
`, viewerRepoID, cursor, limit).Scan(&rows).Error
65
66
if err != nil {
67
return c.JSON(http.StatusInternalServerError, map[string]any{
68
"error": "InternalError",
···
94
"cursor": nextCursor,
95
})
96
}
···
1
package feed
2
3
import (
4
+
"context"
5
"net/http"
6
"strconv"
7
"time"
8
9
"github.com/labstack/echo/v4"
10
"github.com/whyrusleeping/konbini/hydration"
11
+
"go.opentelemetry.io/otel"
12
"gorm.io/gorm"
13
)
14
+
15
+
var tracer = otel.Tracer("xrpc/feed")
16
17
// HandleGetTimeline implements app.bsky.feed.getTimeline
18
func HandleGetTimeline(c echo.Context, db *gorm.DB, hydrator *hydration.Hydrator) error {
19
+
ctx := c.Request().Context()
20
+
ctx, span := tracer.Start(ctx, "getTimeline")
21
+
defer span.End()
22
+
23
viewer := getUserDID(c)
24
if viewer == "" {
25
return c.JSON(http.StatusUnauthorized, map[string]any{
···
44
}
45
}
46
47
// Get viewer's repo ID
48
var viewerRepoID uint
49
if err := db.Raw("SELECT id FROM repos WHERE did = ?", viewer).Scan(&viewerRepoID).Error; err != nil {
···
54
}
55
56
// Query posts from followed users
57
58
+
rows, err := getTimelinePosts(ctx, db, viewerRepoID, cursor, limit)
59
if err != nil {
60
return c.JSON(http.StatusInternalServerError, map[string]any{
61
"error": "InternalError",
···
87
"cursor": nextCursor,
88
})
89
}
90
+
91
+
func getTimelinePosts(ctx context.Context, db *gorm.DB, uid uint, cursor time.Time, limit int) ([]postRow, error) {
92
+
ctx, span := tracer.Start(ctx, "getTimelineQuery")
93
+
defer span.End()
94
+
95
+
var rows []postRow
96
+
err := db.Raw(`
97
+
SELECT
98
+
'at://' || r.did || '/app.bsky.feed.post/' || p.rkey as uri,
99
+
p.author as author_id
100
+
FROM posts p
101
+
JOIN repos r ON r.id = p.author
102
+
WHERE p.reply_to = 0
103
+
AND p.author IN (SELECT subject FROM follows WHERE author = ?)
104
+
AND p.created < ?
105
+
AND p.not_found = false
106
+
ORDER BY p.created DESC
107
+
LIMIT ?
108
+
`, uid, cursor, limit).Scan(&rows).Error
109
+
110
+
if err != nil {
111
+
return nil, err
112
+
}
113
+
return rows, nil
114
+
}
+1
-3
xrpc/server.go
+1
-3
xrpc/server.go