Feat/Search support (#25)
* new: feat: add search support with meilisearch * new: feat: add search interface * new: feat: add new audio mappings * chg: fix: add meilisearch docs * chg: fix: lint issues * chg: feat: add br flag * chg: fix: use the same user agent * chg: fix: bludv (again) * chg: fix: lint issue
This commit is contained in:
147
search/meilisearch.go
Normal file
147
search/meilisearch.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package meilisearch
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/felipemarinho97/torrent-indexer/schema"
|
||||
)
|
||||
|
||||
// SearchIndexer integrates with Meilisearch to index and search torrent items.
|
||||
type SearchIndexer struct {
|
||||
Client *http.Client
|
||||
BaseURL string
|
||||
APIKey string
|
||||
IndexName string
|
||||
}
|
||||
|
||||
// NewSearchIndexer creates a new instance of SearchIndexer.
|
||||
func NewSearchIndexer(baseURL, apiKey, indexName string) *SearchIndexer {
|
||||
return &SearchIndexer{
|
||||
Client: &http.Client{Timeout: 10 * time.Second},
|
||||
BaseURL: baseURL,
|
||||
APIKey: apiKey,
|
||||
IndexName: indexName,
|
||||
}
|
||||
}
|
||||
|
||||
// IndexTorrent indexes a single torrent item in Meilisearch.
|
||||
func (t *SearchIndexer) IndexTorrent(torrent schema.IndexedTorrent) error {
|
||||
url := fmt.Sprintf("%s/indexes/%s/documents", t.BaseURL, t.IndexName)
|
||||
|
||||
torrentWithKey := struct {
|
||||
Hash string `json:"id"`
|
||||
schema.IndexedTorrent
|
||||
}{
|
||||
Hash: torrent.InfoHash,
|
||||
IndexedTorrent: torrent,
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(torrentWithKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal torrent data: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if t.APIKey != "" {
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", t.APIKey))
|
||||
}
|
||||
|
||||
resp, err := t.Client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *SearchIndexer) IndexTorrents(torrents []schema.IndexedTorrent) error {
|
||||
url := fmt.Sprintf("%s/indexes/%s/documents", t.BaseURL, t.IndexName)
|
||||
|
||||
torrentsWithKey := make([]struct {
|
||||
Hash string `json:"id"`
|
||||
schema.IndexedTorrent
|
||||
}, 0, len(torrents))
|
||||
for _, torrent := range torrents {
|
||||
torrentWithKey := struct {
|
||||
Hash string `json:"id"`
|
||||
schema.IndexedTorrent
|
||||
}{
|
||||
Hash: torrent.InfoHash,
|
||||
IndexedTorrent: torrent,
|
||||
}
|
||||
torrentsWithKey = append(torrentsWithKey, torrentWithKey)
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(torrentsWithKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal torrent data: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if t.APIKey != "" {
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", t.APIKey))
|
||||
}
|
||||
|
||||
resp, err := t.Client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SearchTorrent searches indexed torrents in Meilisearch based on the query.
|
||||
func (t *SearchIndexer) SearchTorrent(query string, limit int) ([]schema.IndexedTorrent, error) {
|
||||
url := fmt.Sprintf("%s/indexes/%s/search", t.BaseURL, t.IndexName)
|
||||
requestBody := map[string]string{
|
||||
"q": query,
|
||||
}
|
||||
jsonData, err := json.Marshal(requestBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal search query: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if t.APIKey != "" {
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", t.APIKey))
|
||||
}
|
||||
|
||||
resp, err := t.Client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("search failed: %s", body)
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Hits []schema.IndexedTorrent `json:"hits"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse search response: %w", err)
|
||||
}
|
||||
|
||||
return result.Hits, nil
|
||||
}
|
||||
Reference in New Issue
Block a user