b667634b32
This would happen in the issue and pull request dashboards, while the per repository lists worked fine. Use OR instead of AND for repo IDs.
185 lines
4.2 KiB
Go
185 lines
4.2 KiB
Go
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package issues
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/meilisearch/meilisearch-go"
|
|
)
|
|
|
|
var _ Indexer = &MeilisearchIndexer{}
|
|
|
|
// MeilisearchIndexer implements Indexer interface
|
|
type MeilisearchIndexer struct {
|
|
client *meilisearch.Client
|
|
indexerName string
|
|
available bool
|
|
availabilityCallback func(bool)
|
|
stopTimer chan struct{}
|
|
lock sync.RWMutex
|
|
}
|
|
|
|
// MeilisearchIndexer creates a new meilisearch indexer
|
|
func NewMeilisearchIndexer(url, apiKey, indexerName string) (*MeilisearchIndexer, error) {
|
|
client := meilisearch.NewClient(meilisearch.ClientConfig{
|
|
Host: url,
|
|
APIKey: apiKey,
|
|
})
|
|
|
|
indexer := &MeilisearchIndexer{
|
|
client: client,
|
|
indexerName: indexerName,
|
|
available: true,
|
|
stopTimer: make(chan struct{}),
|
|
}
|
|
|
|
ticker := time.NewTicker(10 * time.Second)
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
indexer.checkAvailability()
|
|
case <-indexer.stopTimer:
|
|
ticker.Stop()
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return indexer, nil
|
|
}
|
|
|
|
// Init will initialize the indexer
|
|
func (b *MeilisearchIndexer) Init() (bool, error) {
|
|
_, err := b.client.GetIndex(b.indexerName)
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
_, err = b.client.CreateIndex(&meilisearch.IndexConfig{
|
|
Uid: b.indexerName,
|
|
PrimaryKey: "id",
|
|
})
|
|
if err != nil {
|
|
return false, b.checkError(err)
|
|
}
|
|
|
|
_, err = b.client.Index(b.indexerName).UpdateFilterableAttributes(&[]string{"repo_id"})
|
|
return false, b.checkError(err)
|
|
}
|
|
|
|
// SetAvailabilityChangeCallback sets callback that will be triggered when availability changes
|
|
func (b *MeilisearchIndexer) SetAvailabilityChangeCallback(callback func(bool)) {
|
|
b.lock.Lock()
|
|
defer b.lock.Unlock()
|
|
b.availabilityCallback = callback
|
|
}
|
|
|
|
// Ping checks if meilisearch is available
|
|
func (b *MeilisearchIndexer) Ping() bool {
|
|
b.lock.RLock()
|
|
defer b.lock.RUnlock()
|
|
return b.available
|
|
}
|
|
|
|
// Index will save the index data
|
|
func (b *MeilisearchIndexer) Index(issues []*IndexerData) error {
|
|
if len(issues) == 0 {
|
|
return nil
|
|
}
|
|
for _, issue := range issues {
|
|
_, err := b.client.Index(b.indexerName).AddDocuments(issue)
|
|
if err != nil {
|
|
return b.checkError(err)
|
|
}
|
|
}
|
|
// TODO: bulk send index data
|
|
return nil
|
|
}
|
|
|
|
// Delete deletes indexes by ids
|
|
func (b *MeilisearchIndexer) Delete(ids ...int64) error {
|
|
if len(ids) == 0 {
|
|
return nil
|
|
}
|
|
|
|
for _, id := range ids {
|
|
_, err := b.client.Index(b.indexerName).DeleteDocument(strconv.FormatInt(id, 10))
|
|
if err != nil {
|
|
return b.checkError(err)
|
|
}
|
|
}
|
|
// TODO: bulk send deletes
|
|
return nil
|
|
}
|
|
|
|
// Search searches for issues by given conditions.
|
|
// Returns the matching issue IDs
|
|
func (b *MeilisearchIndexer) Search(ctx context.Context, keyword string, repoIDs []int64, limit, start int) (*SearchResult, error) {
|
|
repoFilters := make([]string, 0, len(repoIDs))
|
|
for _, repoID := range repoIDs {
|
|
repoFilters = append(repoFilters, "repo_id = "+strconv.FormatInt(repoID, 10))
|
|
}
|
|
filter := strings.Join(repoFilters, " OR ")
|
|
searchRes, err := b.client.Index(b.indexerName).Search(keyword, &meilisearch.SearchRequest{
|
|
Filter: filter,
|
|
Limit: int64(limit),
|
|
Offset: int64(start),
|
|
})
|
|
if err != nil {
|
|
return nil, b.checkError(err)
|
|
}
|
|
|
|
hits := make([]Match, 0, len(searchRes.Hits))
|
|
for _, hit := range searchRes.Hits {
|
|
hits = append(hits, Match{
|
|
ID: int64(hit.(map[string]interface{})["id"].(float64)),
|
|
})
|
|
}
|
|
return &SearchResult{
|
|
Total: searchRes.TotalHits,
|
|
Hits: hits,
|
|
}, nil
|
|
}
|
|
|
|
// Close implements indexer
|
|
func (b *MeilisearchIndexer) Close() {
|
|
select {
|
|
case <-b.stopTimer:
|
|
default:
|
|
close(b.stopTimer)
|
|
}
|
|
}
|
|
|
|
func (b *MeilisearchIndexer) checkError(err error) error {
|
|
return err
|
|
}
|
|
|
|
func (b *MeilisearchIndexer) checkAvailability() {
|
|
_, err := b.client.Health()
|
|
if err != nil {
|
|
b.setAvailability(false)
|
|
return
|
|
}
|
|
b.setAvailability(true)
|
|
}
|
|
|
|
func (b *MeilisearchIndexer) setAvailability(available bool) {
|
|
b.lock.Lock()
|
|
defer b.lock.Unlock()
|
|
|
|
if b.available == available {
|
|
return
|
|
}
|
|
|
|
b.available = available
|
|
if b.availabilityCallback != nil {
|
|
// Call the callback from within the lock to ensure that the ordering remains correct
|
|
b.availabilityCallback(b.available)
|
|
}
|
|
}
|