···
1
+
// heavily inspired by gitea's model (basically copy-pasted)
2
+
package issues_indexer
10
+
"github.com/blevesearch/bleve/v2"
11
+
"github.com/blevesearch/bleve/v2/index/upsidedown"
12
+
"github.com/blevesearch/bleve/v2/search/query"
13
+
"tangled.org/core/appview/db"
14
+
"tangled.org/core/appview/indexer/base36"
15
+
"tangled.org/core/appview/indexer/bleve"
16
+
"tangled.org/core/appview/models"
17
+
"tangled.org/core/appview/pagination"
18
+
tlog "tangled.org/core/log"
21
+
type Indexer struct {
26
+
func NewIndexer(indexDir string) *Indexer {
32
+
// Init initializes the indexer
33
+
func (ix *Indexer) Init(ctx context.Context, e db.Execer) {
34
+
l := tlog.FromContext(ctx)
35
+
existed, err := ix.intialize(ctx)
37
+
log.Fatalln("failed to initialize issue indexer", err)
40
+
l.Debug("Populating the issue indexer")
41
+
err := PopulateIndexer(ctx, ix, e)
43
+
log.Fatalln("failed to populate issue indexer", err)
46
+
l.Info("Initialized the issue indexer")
49
+
func (ix *Indexer) intialize(ctx context.Context) (bool, error) {
50
+
if ix.indexer != nil {
51
+
return false, errors.New("indexer is already initialized")
54
+
indexer, err := openIndexer(ctx, ix.path)
59
+
ix.indexer = indexer
63
+
mapping := bleve.NewIndexMapping()
64
+
indexer, err = bleve.New(ix.path, mapping)
69
+
ix.indexer = indexer
74
+
func openIndexer(ctx context.Context, path string) (bleve.Index, error) {
75
+
l := tlog.FromContext(ctx)
76
+
indexer, err := bleve.Open(path)
78
+
if errors.Is(err, upsidedown.IncompatibleVersion) {
79
+
l.Info("Indexer was built with a previous version of bleve, deleting and rebuilding")
80
+
return nil, os.RemoveAll(path)
87
+
func PopulateIndexer(ctx context.Context, ix *Indexer, e db.Execer) error {
88
+
l := tlog.FromContext(ctx)
90
+
err := pagination.IterateAll(
91
+
func(page pagination.Page) ([]models.Issue, error) {
92
+
return db.GetIssuesPaginated(e, page)
94
+
func(issues []models.Issue) error {
95
+
count += len(issues)
96
+
return ix.Index(ctx, issues...)
99
+
l.Info("issues indexed", "count", count)
103
+
// issueData data stored and will be indexed
104
+
type issueData struct {
105
+
ID int64 `json:"id"`
106
+
RepoAt string `json:"repo_at"`
107
+
IssueID int `json:"issue_id"`
108
+
Title string `json:"title"`
109
+
Body string `json:"body"`
111
+
IsOpen bool `json:"is_open"`
112
+
Comments []IssueCommentData `json:"comments"`
115
+
func makeIssueData(issue *models.Issue) *issueData {
118
+
RepoAt: issue.RepoAt.String(),
119
+
IssueID: issue.IssueId,
120
+
Title: issue.Title,
122
+
IsOpen: issue.Open,
126
+
type IssueCommentData struct {
127
+
Body string `json:"body"`
130
+
type SearchResult struct {
135
+
const maxBatchSize = 20
137
+
func (ix *Indexer) Index(ctx context.Context, issues ...models.Issue) error {
138
+
batch := bleveutil.NewFlushingBatch(ix.indexer, maxBatchSize)
139
+
for _, issue := range issues {
140
+
issueData := makeIssueData(&issue)
141
+
if err := batch.Index(base36.Encode(issue.Id), issueData); err != nil {
145
+
return batch.Flush()
148
+
// Search searches for issues
149
+
func (ix *Indexer) Search(ctx context.Context, opts models.IssueSearchOptions) (*SearchResult, error) {
150
+
var queries []query.Query
152
+
if opts.Keyword != "" {
153
+
queries = append(queries, bleve.NewDisjunctionQuery(
154
+
bleveutil.MatchAndQuery("title", opts.Keyword),
155
+
bleveutil.MatchAndQuery("body", opts.Keyword),
158
+
queries = append(queries, bleveutil.KeywordFieldQuery("repo_at", opts.RepoAt))
159
+
queries = append(queries, bleveutil.BoolFieldQuery("is_open", opts.IsOpen))
160
+
// TODO: append more queries
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)
168
+
ret := &SearchResult{
170
+
Hits: make([]int64, len(res.Hits)),
172
+
for i, hit := range res.Hits {
173
+
id, err := base36.Decode(hit.ID)