A locally focused bluesky appview

fixup embeds, add jaeger tracing, some optimizations

+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, &notfound); 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
···
··· 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
··· 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
··· 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
··· 22 23 // HydrateActor hydrates full actor information 24 func (h *Hydrator) HydrateActor(ctx context.Context, did string) (*ActorInfo, error) { 25 // Look up handle 26 resp, err := h.dir.LookupDID(ctx, syntax.DID(did)) 27 if err != nil {
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 56 db: db, 57 dir: dir, 58 backend: backend, 59 - hydrator: hydration.NewHydrator(db, dir), 60 } 61 - 62 - s.hydrator.SetMissingRecordCallback(backend.TrackMissingRecord) 63 64 // Register XRPC endpoints 65 s.registerEndpoints()
··· 56 db: db, 57 dir: dir, 58 backend: backend, 59 + hydrator: hydration.NewHydrator(db, dir, backend), 60 } 61 62 // Register XRPC endpoints 63 s.registerEndpoints()