appview: add basic issue indexer (wip) #494

merged
opened by boltless.me targeting master from boltless.me/core: feat/search
  • Heavily inspired by gitea
  • add GetAllIssues which only receives a paginator and gathers all issues ignoring repoAt field

Signed-off-by: Seongmin Lee boltlessengineer@proton.me

Changed files
+504 -1
appview
db
indexer
issues
models
pages
state
+1
.gitignore
··· 15 15 .env 16 16 *.rdb 17 17 .envrc 18 + *.bleve 18 19 # Created if following hacking.md 19 20 genjwks.out 20 21 /nix/vm-data
+56
appview/db/issues.go
··· 104 104 return ownerDid, err 105 105 } 106 106 107 + func GetAllIssues(e Execer, page pagination.Page) ([]Issue, error) { 108 + var issues []Issue 109 + rows, err := e.Query( 110 + ` 111 + select 112 + i.id, 113 + i.owner_did, 114 + i.repo_at, 115 + i.issue_id, 116 + i.created, 117 + i.title, 118 + i.body, 119 + i.open, 120 + count(c.id) as comment_count 121 + from 122 + issues i 123 + left join 124 + comments c on i.repo_at = c.repo_at and i.issue_id = c.issue_id 125 + group by 126 + i.id 127 + order by i.created desc 128 + limit ? offset ?`, 129 + page.Limit, 130 + page.Offset, 131 + ) 132 + if err != nil { 133 + return nil, err 134 + } 135 + defer rows.Close() 136 + 137 + for rows.Next() { 138 + var issue Issue 139 + var createdAt string 140 + var metadata IssueMetadata 141 + err := rows.Scan(&issue.ID, &issue.OwnerDid, &issue.RepoAt, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open, &metadata.CommentCount) 142 + if err != nil { 143 + return nil, err 144 + } 145 + 146 + createdTime, err := time.Parse(time.RFC3339, createdAt) 147 + if err != nil { 148 + return nil, err 149 + } 150 + issue.Created = createdTime 151 + issue.Metadata = &metadata 152 + 153 + issues = append(issues, issue) 154 + } 155 + 156 + if err := rows.Err(); err != nil { 157 + return nil, err 158 + } 159 + 160 + return issues, nil 161 + } 162 + 107 163 func GetIssues(e Execer, repoAt syntax.ATURI, isOpen bool, page pagination.Page) ([]Issue, error) { 108 164 var issues []Issue 109 165 openValue := 0
+20
appview/indexer/base36/base36.go
··· 1 + // mostly copied from gitea/modules/indexer/internal/base32 2 + 3 + package base36 4 + 5 + import ( 6 + "fmt" 7 + "strconv" 8 + ) 9 + 10 + func Encode(i int64) string { 11 + return strconv.FormatInt(i, 36) 12 + } 13 + 14 + func Decode(s string) (int64, error) { 15 + i, err := strconv.ParseInt(s, 36, 64) 16 + if err != nil { 17 + return 0, fmt.Errorf("invalid base36 integer %q: %w", s, err) 18 + } 19 + return i, nil 20 + }
+58
appview/indexer/bleve/batch.go
··· 1 + // Copyright 2021 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package bleveutil 5 + 6 + import ( 7 + "github.com/blevesearch/bleve/v2" 8 + ) 9 + 10 + // FlushingBatch is a batch of operations that automatically flushes to the 11 + // underlying index once it reaches a certain size. 12 + type FlushingBatch struct { 13 + maxBatchSize int 14 + batch *bleve.Batch 15 + index bleve.Index 16 + } 17 + 18 + // NewFlushingBatch creates a new flushing batch for the specified index. Once 19 + // the number of operations in the batch reaches the specified limit, the batch 20 + // automatically flushes its operations to the index. 21 + func NewFlushingBatch(index bleve.Index, maxBatchSize int) *FlushingBatch { 22 + return &FlushingBatch{ 23 + maxBatchSize: maxBatchSize, 24 + batch: index.NewBatch(), 25 + index: index, 26 + } 27 + } 28 + 29 + // Index add a new index to batch 30 + func (b *FlushingBatch) Index(id string, data any) error { 31 + if err := b.batch.Index(id, data); err != nil { 32 + return err 33 + } 34 + return b.flushIfFull() 35 + } 36 + 37 + // Delete add a delete index to batch 38 + func (b *FlushingBatch) Delete(id string) error { 39 + b.batch.Delete(id) 40 + return b.flushIfFull() 41 + } 42 + 43 + func (b *FlushingBatch) flushIfFull() error { 44 + if b.batch.Size() < b.maxBatchSize { 45 + return nil 46 + } 47 + return b.Flush() 48 + } 49 + 50 + // Flush submit the batch and create a new one 51 + func (b *FlushingBatch) Flush() error { 52 + err := b.index.Batch(b.batch) 53 + if err != nil { 54 + return err 55 + } 56 + b.batch = b.index.NewBatch() 57 + return nil 58 + }
+27
appview/indexer/indexer.go
··· 1 + package indexer 2 + 3 + import ( 4 + "context" 5 + 6 + "tangled.sh/tangled.sh/core/appview/db" 7 + issues_indexer "tangled.sh/tangled.sh/core/appview/indexer/issues" 8 + "tangled.sh/tangled.sh/core/appview/notify" 9 + ) 10 + 11 + type Indexer struct { 12 + Issues *issues_indexer.Indexer 13 + notify.BaseNotifier 14 + } 15 + 16 + func New() *Indexer { 17 + return &Indexer { 18 + issues_indexer.NewIndexer("indexes.bleve"), 19 + notify.BaseNotifier{}, 20 + } 21 + } 22 + 23 + // Init initializes all indexers 24 + func (ix *Indexer) Init(ctx context.Context, db *db.DB) error { 25 + ix.Issues.Init(ctx, db) 26 + return nil 27 + }
+192
appview/indexer/issues/indexer.go
··· 1 + package issues_indexer 2 + 3 + import ( 4 + "context" 5 + "errors" 6 + "log" 7 + "os" 8 + 9 + "github.com/blevesearch/bleve/v2" 10 + "github.com/blevesearch/bleve/v2/index/upsidedown" 11 + "github.com/blevesearch/bleve/v2/search/query" 12 + "tangled.sh/tangled.sh/core/appview/db" 13 + "tangled.sh/tangled.sh/core/appview/indexer/base36" 14 + "tangled.sh/tangled.sh/core/appview/indexer/bleve" 15 + "tangled.sh/tangled.sh/core/appview/models" 16 + "tangled.sh/tangled.sh/core/appview/pagination" 17 + ) 18 + 19 + type Indexer struct { 20 + indexer bleve.Index 21 + path string 22 + } 23 + 24 + func NewIndexer(indexDir string) *Indexer { 25 + return &Indexer{ 26 + path: indexDir, 27 + } 28 + } 29 + 30 + // Init initializes the indexer 31 + func (ix *Indexer) Init(ctx context.Context, e db.Execer) { 32 + existed, err := ix.intialize(ctx) 33 + if err != nil { 34 + log.Fatalf("failed to initialize issue indexer: %v", err) 35 + } 36 + if !existed { 37 + log.Println("Populating the issue indexer") 38 + err := PopulateIndexer(ctx, ix, e) 39 + if err != nil { 40 + log.Fatalf("failed to populate issue indexer: %v", err) 41 + } 42 + } 43 + log.Println("Initialized the issue indexer") 44 + } 45 + 46 + func (ix *Indexer) intialize(_ context.Context) (bool, error) { 47 + if ix.indexer != nil { 48 + return false, errors.New("indexer is already initialized") 49 + } 50 + 51 + indexer, err := openIndexer(ix.path) 52 + if err != nil { 53 + return false, nil 54 + } 55 + if indexer != nil { 56 + ix.indexer = indexer 57 + return true, nil 58 + } 59 + 60 + mapping := bleve.NewIndexMapping() 61 + indexer, err = bleve.New(ix.path, mapping) 62 + if err != nil { 63 + return false, err 64 + } 65 + 66 + ix.indexer = indexer 67 + 68 + return false, nil 69 + } 70 + 71 + func openIndexer(path string) (bleve.Index, error) { 72 + _, err := os.Stat(path) 73 + indexer, err := bleve.Open(path) 74 + if err != nil { 75 + if errors.Is(err, upsidedown.IncompatibleVersion) { 76 + log.Println("Indexer was built with a previous version of bleve, deleting and rebuilding") 77 + return nil, os.RemoveAll(path) 78 + } 79 + return nil, nil 80 + } 81 + return indexer, nil 82 + } 83 + 84 + func PopulateIndexer(ctx context.Context, ix *Indexer, e db.Execer) error { 85 + page := pagination.FirstPage() 86 + for { 87 + issues, err := db.GetAllIssues(e, page) 88 + if err != nil { 89 + return err 90 + } 91 + var dataList []*IssueData 92 + for _, issue := range issues { 93 + issue, _, err := db.GetIssueWithComments(e, issue.RepoAt, issue.IssueId) 94 + if err != nil { 95 + return err 96 + } 97 + dataList = append(dataList, &IssueData{ 98 + ID: issue.ID, 99 + IssueID: issue.IssueId, 100 + Title: issue.Title, 101 + Body: issue.Body, 102 + IsOpen: issue.Open, 103 + }) 104 + } 105 + err = ix.Index(ctx, dataList...) 106 + if err != nil { 107 + return err 108 + } 109 + if len(issues) < page.Limit { 110 + break 111 + } 112 + page = page.Next() 113 + } 114 + return nil 115 + } 116 + 117 + // IssueData data stored and will be indexed 118 + type IssueData struct { 119 + ID int64 `json:"id"` 120 + IssueID int `json:"issue_id"` 121 + Title string `json:"title"` 122 + Body string `json:"body"` 123 + 124 + IsOpen bool `json:"is_open"` 125 + Comments []IssueCommentData `json:"comments"` 126 + } 127 + 128 + type IssueCommentData struct { 129 + Body string `json:"body"` 130 + } 131 + 132 + type SearchResult struct { 133 + Hits []int64 134 + Total uint64 135 + } 136 + 137 + const maxBatchSize = 20 138 + 139 + func (ix *Indexer) Index(ctx context.Context, issues ...*IssueData) error { 140 + batch := bleveutil.NewFlushingBatch(ix.indexer, maxBatchSize) 141 + for _, issue := range issues { 142 + if err := batch.Index(base36.Encode(issue.ID), issue); err != nil { 143 + return err 144 + } 145 + } 146 + return batch.Flush() 147 + } 148 + 149 + // Search searches for issues 150 + func (ix *Indexer) Search(ctx context.Context, opts models.IssueSearchOptions) (*SearchResult, error) { 151 + var queries []query.Query 152 + 153 + if opts.Keyword != "" { 154 + queries = append(queries, bleve.NewDisjunctionQuery( 155 + matchAndQuery(opts.Keyword, "title"), 156 + matchAndQuery(opts.Keyword, "body"), 157 + )) 158 + } 159 + queries = append(queries, boolFieldQuery(opts.IsOpen, "is_open")) 160 + // TODO: append more queries 161 + 162 + var indexerQuery query.Query = bleve.NewConjunctionQuery(queries...) 163 + searchReq := bleve.NewSearchRequestOptions(indexerQuery, opts.Page.Limit, opts.Page.Offset, false) 164 + res, err := ix.indexer.SearchInContext(ctx, searchReq) 165 + if err != nil { 166 + return nil, nil 167 + } 168 + ret := &SearchResult{ 169 + Total: res.Total, 170 + Hits: make([]int64, len(res.Hits)), 171 + } 172 + for i, hit := range res.Hits { 173 + id, err := base36.Decode(hit.ID) 174 + if err != nil { 175 + return nil, err 176 + } 177 + ret.Hits[i] = id 178 + } 179 + return ret, nil 180 + } 181 + 182 + func matchAndQuery(keyword, field string) query.Query { 183 + q := bleve.NewMatchQuery(keyword) 184 + q.FieldVal = field 185 + return q 186 + } 187 + 188 + func boolFieldQuery(val bool, field string) query.Query { 189 + q := bleve.NewBoolFieldQuery(val) 190 + q.FieldVal = field 191 + return q 192 + }
+26
appview/indexer/notifier.go
··· 1 + package indexer 2 + 3 + import ( 4 + "context" 5 + 6 + "tangled.sh/tangled.sh/core/appview/db" 7 + issues_indexer "tangled.sh/tangled.sh/core/appview/indexer/issues" 8 + "tangled.sh/tangled.sh/core/appview/notify" 9 + ) 10 + 11 + var _ notify.Notifier = &Indexer{} 12 + 13 + func (ix *Indexer) NewIssue(ctx context.Context, issue *db.Issue) { 14 + ix.Issues.Index(ctx, &issues_indexer.IssueData{ 15 + ID: issue.ID, 16 + IssueID: issue.IssueId, 17 + Title: issue.Title, 18 + Body: issue.Body, 19 + IsOpen: issue.Open, 20 + Comments: []issues_indexer.IssueCommentData{}, 21 + }) 22 + } 23 + 24 + func (ix *Indexer) NewPullComment(ctx context.Context, comment *db.PullComment) { 25 + panic("unimplemented") 26 + }
+4
appview/issues/issues.go
··· 18 18 "tangled.sh/tangled.sh/core/api/tangled" 19 19 "tangled.sh/tangled.sh/core/appview/config" 20 20 "tangled.sh/tangled.sh/core/appview/db" 21 + issues_indexer "tangled.sh/tangled.sh/core/appview/indexer/issues" 21 22 "tangled.sh/tangled.sh/core/appview/notify" 22 23 "tangled.sh/tangled.sh/core/appview/oauth" 23 24 "tangled.sh/tangled.sh/core/appview/pages" ··· 35 36 db *db.DB 36 37 config *config.Config 37 38 notifier notify.Notifier 39 + indexer *issues_indexer.Indexer 38 40 } 39 41 40 42 func New( ··· 45 47 db *db.DB, 46 48 config *config.Config, 47 49 notifier notify.Notifier, 50 + indexer *issues_indexer.Indexer, 48 51 ) *Issues { 49 52 return &Issues{ 50 53 oauth: oauth, ··· 54 57 db: db, 55 58 config: config, 56 59 notifier: notifier, 60 + indexer: indexer, 57 61 } 58 62 } 59 63
+23
appview/models/search.go
··· 1 + package models 2 + 3 + import "tangled.sh/tangled.sh/core/appview/pagination" 4 + 5 + type IssueSearchOptions struct { 6 + Keyword string 7 + RepoAt string 8 + IsOpen bool 9 + 10 + Page pagination.Page 11 + } 12 + 13 + // func (so *SearchOptions) ToFilters() []filter { 14 + // var filters []filter 15 + // if so.IsOpen != nil { 16 + // openValue := 0 17 + // if *so.IsOpen { 18 + // openValue = 1 19 + // } 20 + // filters = append(filters, FilterEq("open", openValue)) 21 + // } 22 + // return filters 23 + // }
+1
appview/pages/pages.go
··· 782 782 Issues []db.Issue 783 783 Page pagination.Page 784 784 FilteringByOpen bool 785 + FilterQuery string 785 786 } 786 787 787 788 func (p *Pages) RepoIssues(w io.Writer, params RepoIssuesParams) error {
+1 -1
appview/state/router.go
··· 222 222 } 223 223 224 224 func (s *State) IssuesRouter(mw *middleware.Middleware) http.Handler { 225 - issues := issues.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config, s.notifier) 225 + issues := issues.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config, s.notifier, s.indexer.Issues) 226 226 return issues.Router(mw) 227 227 } 228 228
+10
appview/state/state.go
··· 20 20 "tangled.sh/tangled.sh/core/appview/cache/session" 21 21 "tangled.sh/tangled.sh/core/appview/config" 22 22 "tangled.sh/tangled.sh/core/appview/db" 23 + "tangled.sh/tangled.sh/core/appview/indexer" 23 24 "tangled.sh/tangled.sh/core/appview/notify" 24 25 "tangled.sh/tangled.sh/core/appview/oauth" 25 26 "tangled.sh/tangled.sh/core/appview/pages" ··· 37 38 type State struct { 38 39 db *db.DB 39 40 notifier notify.Notifier 41 + indexer *indexer.Indexer 40 42 oauth *oauth.OAuth 41 43 enforcer *rbac.Enforcer 42 44 pages *pages.Pages ··· 56 58 return nil, fmt.Errorf("failed to create db: %w", err) 57 59 } 58 60 61 + indexer := indexer.New() 62 + err = indexer.Init(ctx, d) 63 + if err != nil { 64 + return nil, fmt.Errorf("failed to create indexer: %w", err) 65 + } 66 + 59 67 enforcer, err := rbac.NewEnforcer(config.Core.DbPath) 60 68 if err != nil { 61 69 return nil, fmt.Errorf("failed to create enforcer: %w", err) ··· 136 144 if !config.Core.Dev { 137 145 notifiers = append(notifiers, posthogService.NewPosthogNotifier(posthog)) 138 146 } 147 + notifiers = append(notifiers, indexer) 139 148 notifier := notify.NewMergedNotifier(notifiers...) 140 149 141 150 state := &State{ 142 151 d, 143 152 notifier, 153 + indexer, 144 154 oauth, 145 155 enforcer, 146 156 pgs,
+27
go.mod
··· 53 53 dario.cat/mergo v1.0.1 // indirect 54 54 github.com/Microsoft/go-winio v0.6.2 // indirect 55 55 github.com/ProtonMail/go-crypto v1.3.0 // indirect 56 + github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect 56 57 github.com/alecthomas/repr v0.4.0 // indirect 57 58 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect 58 59 github.com/aymerick/douceur v0.2.0 // indirect 59 60 github.com/beorn7/perks v1.0.1 // indirect 61 + github.com/bits-and-blooms/bitset v1.22.0 // indirect 62 + github.com/blevesearch/bleve/v2 v2.5.3 // indirect 63 + github.com/blevesearch/bleve_index_api v1.2.8 // indirect 64 + github.com/blevesearch/geo v0.2.4 // indirect 65 + github.com/blevesearch/go-faiss v1.0.25 // indirect 66 + github.com/blevesearch/go-porterstemmer v1.0.3 // indirect 67 + github.com/blevesearch/gtreap v0.1.1 // indirect 68 + github.com/blevesearch/mmap-go v1.0.4 // indirect 69 + github.com/blevesearch/scorch_segment_api/v2 v2.3.10 // indirect 70 + github.com/blevesearch/segment v0.9.1 // indirect 71 + github.com/blevesearch/snowballstem v0.9.0 // indirect 72 + github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect 73 + github.com/blevesearch/vellum v1.1.0 // indirect 74 + github.com/blevesearch/zapx/v11 v11.4.2 // indirect 75 + github.com/blevesearch/zapx/v12 v12.4.2 // indirect 76 + github.com/blevesearch/zapx/v13 v13.4.2 // indirect 77 + github.com/blevesearch/zapx/v14 v14.4.2 // indirect 78 + github.com/blevesearch/zapx/v15 v15.4.2 // indirect 79 + github.com/blevesearch/zapx/v16 v16.2.4 // indirect 60 80 github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect 61 81 github.com/casbin/govaluate v1.3.0 // indirect 62 82 github.com/cenkalti/backoff/v4 v4.3.0 // indirect ··· 88 108 github.com/golang-jwt/jwt/v5 v5.2.3 // indirect 89 109 github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect 90 110 github.com/golang/mock v1.6.0 // indirect 111 + github.com/golang/protobuf v1.5.4 // indirect 112 + github.com/golang/snappy v0.0.4 // indirect 91 113 github.com/google/go-querystring v1.1.0 // indirect 92 114 github.com/gorilla/css v1.0.1 // indirect 93 115 github.com/gorilla/securecookie v1.1.2 // indirect ··· 113 135 github.com/ipfs/go-log v1.0.5 // indirect 114 136 github.com/ipfs/go-log/v2 v2.6.0 // indirect 115 137 github.com/ipfs/go-metrics-interface v0.3.0 // indirect 138 + github.com/json-iterator/go v1.1.12 // indirect 116 139 github.com/kevinburke/ssh_config v1.2.0 // indirect 117 140 github.com/klauspost/compress v1.18.0 // indirect 118 141 github.com/klauspost/cpuid/v2 v2.3.0 // indirect ··· 127 150 github.com/moby/docker-image-spec v1.3.1 // indirect 128 151 github.com/moby/sys/atomicwriter v0.1.0 // indirect 129 152 github.com/moby/term v0.5.2 // indirect 153 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 154 + github.com/modern-go/reflect2 v1.0.2 // indirect 130 155 github.com/morikuni/aec v1.0.0 // indirect 131 156 github.com/mr-tron/base58 v1.2.0 // indirect 157 + github.com/mschoch/smat v0.2.0 // indirect 132 158 github.com/multiformats/go-base32 v0.1.0 // indirect 133 159 github.com/multiformats/go-base36 v0.2.0 // indirect 134 160 github.com/multiformats/go-multibase v0.2.0 // indirect ··· 156 182 github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 157 183 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect 158 184 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 185 + go.etcd.io/bbolt v1.4.0 // indirect 159 186 go.opentelemetry.io/auto/sdk v1.1.0 // indirect 160 187 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect 161 188 go.opentelemetry.io/otel v1.37.0 // indirect
+58
go.sum
··· 9 9 github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 10 10 github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= 11 11 github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= 12 + github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg= 13 + github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0= 12 14 github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= 13 15 github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= 14 16 github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= ··· 23 25 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 24 26 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 25 27 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 28 + github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= 29 + github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4= 30 + github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= 31 + github.com/blevesearch/bleve/v2 v2.5.3 h1:9l1xtKaETv64SZc1jc4Sy0N804laSa/LeMbYddq1YEM= 32 + github.com/blevesearch/bleve/v2 v2.5.3/go.mod h1:Z/e8aWjiq8HeX+nW8qROSxiE0830yQA071dwR3yoMzw= 33 + github.com/blevesearch/bleve_index_api v1.2.8 h1:Y98Pu5/MdlkRyLM0qDHostYo7i+Vv1cDNhqTeR4Sy6Y= 34 + github.com/blevesearch/bleve_index_api v1.2.8/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0= 35 + github.com/blevesearch/geo v0.2.4 h1:ECIGQhw+QALCZaDcogRTNSJYQXRtC8/m8IKiA706cqk= 36 + github.com/blevesearch/geo v0.2.4/go.mod h1:K56Q33AzXt2YExVHGObtmRSFYZKYGv0JEN5mdacJJR8= 37 + github.com/blevesearch/go-faiss v1.0.25 h1:lel1rkOUGbT1CJ0YgzKwC7k+XH0XVBHnCVWahdCXk4U= 38 + github.com/blevesearch/go-faiss v1.0.25/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk= 39 + github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo= 40 + github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M= 41 + github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y= 42 + github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk= 43 + github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc= 44 + github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs= 45 + github.com/blevesearch/scorch_segment_api/v2 v2.3.10 h1:Yqk0XD1mE0fDZAJXTjawJ8If/85JxnLd8v5vG/jWE/s= 46 + github.com/blevesearch/scorch_segment_api/v2 v2.3.10/go.mod h1:Z3e6ChN3qyN35yaQpl00MfI5s8AxUJbpTR/DL8QOQ+8= 47 + github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= 48 + github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= 49 + github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s= 50 + github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs= 51 + github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A= 52 + github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ= 53 + github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w= 54 + github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y= 55 + github.com/blevesearch/zapx/v11 v11.4.2 h1:l46SV+b0gFN+Rw3wUI1YdMWdSAVhskYuvxlcgpQFljs= 56 + github.com/blevesearch/zapx/v11 v11.4.2/go.mod h1:4gdeyy9oGa/lLa6D34R9daXNUvfMPZqUYjPwiLmekwc= 57 + github.com/blevesearch/zapx/v12 v12.4.2 h1:fzRbhllQmEMUuAQ7zBuMvKRlcPA5ESTgWlDEoB9uQNE= 58 + github.com/blevesearch/zapx/v12 v12.4.2/go.mod h1:TdFmr7afSz1hFh/SIBCCZvcLfzYvievIH6aEISCte58= 59 + github.com/blevesearch/zapx/v13 v13.4.2 h1:46PIZCO/ZuKZYgxI8Y7lOJqX3Irkc3N8W82QTK3MVks= 60 + github.com/blevesearch/zapx/v13 v13.4.2/go.mod h1:knK8z2NdQHlb5ot/uj8wuvOq5PhDGjNYQQy0QDnopZk= 61 + github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT7fWYz0= 62 + github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8= 63 + github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k= 64 + github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw= 65 + github.com/blevesearch/zapx/v16 v16.2.4 h1:tGgfvleXTAkwsD5mEzgM3zCS/7pgocTCnO1oyAUjlww= 66 + github.com/blevesearch/zapx/v16 v16.2.4/go.mod h1:Rti/REtuuMmzwsI8/C/qIzRaEoSK/wiFYw5e5ctUKKs= 26 67 github.com/bluesky-social/indigo v0.0.0-20250724221105-5827c8fb61bb h1:BqMNDZMfXwiRTJ6NvQotJ0qInn37JH5U8E+TF01CFHQ= 27 68 github.com/bluesky-social/indigo v0.0.0-20250724221105-5827c8fb61bb/go.mod h1:0XUyOCRtL4/OiyeqMTmr6RlVHQMDgw3LS7CfibuZR5Q= 28 69 github.com/bluesky-social/jetstream v0.0.0-20241210005130-ea96859b93d1 h1:CFvRtYNSnWRAi/98M3O466t9dYuwtesNbu6FVPymRrA= ··· 152 193 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 153 194 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 154 195 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 196 + github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 197 + github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 198 + github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 199 + github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 155 200 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 156 201 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 157 202 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= ··· 163 208 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 164 209 github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 165 210 github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 211 + github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 166 212 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 167 213 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 168 214 github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= ··· 245 291 github.com/ipfs/go-metrics-interface v0.3.0/go.mod h1:OxxQjZDGocXVdyTPocns6cOLwHieqej/jos7H4POwoY= 246 292 github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 247 293 github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 294 + github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 295 + github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 248 296 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 249 297 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 250 298 github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= ··· 296 344 github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= 297 345 github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= 298 346 github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= 347 + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 348 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 349 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 350 + github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 351 + github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 299 352 github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 300 353 github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 301 354 github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 302 355 github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 356 + github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= 357 + github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= 303 358 github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= 304 359 github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= 305 360 github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= ··· 439 494 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= 440 495 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= 441 496 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I= 497 + go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= 498 + go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= 442 499 go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 443 500 go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 444 501 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= ··· 640 697 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 641 698 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 642 699 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 700 + gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 643 701 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 644 702 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 645 703 gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=