ultisuite-backend/internal/websearch/client.go
R3D347HR4Y 556d5f416d Enhance API and configuration for contact discovery and public sharing
- Introduced new endpoints for contact discovery, including scanning, listing, and managing discovered contacts.
- Implemented retry logic for handling missing DAV credentials during contact operations.
- Added public share functionality for drive API, allowing users to manage public shares, including upload, delete, and rename operations.
- Updated Nextcloud configuration to support public share links and improved error handling for public share permissions.
- Enhanced logging and validation across contact and drive APIs for better error tracking and user feedback.
- Added tests for new contact matching and ranking functionalities to ensure accuracy and reliability.
2026-06-06 20:27:02 +02:00

115 lines
2.6 KiB
Go

package websearch
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
)
var braveSearchURL = "https://api.search.brave.com/res/v1/web/search"
type Result struct {
Title string `json:"title"`
URL string `json:"url"`
Description string `json:"description"`
}
type Client struct {
http *http.Client
}
func NewClient() *Client {
return &Client{http: &http.Client{Timeout: 15 * time.Second}}
}
type braveSearchResponse struct {
Web *struct {
Results []struct {
Title string `json:"title"`
URL string `json:"url"`
Description string `json:"description"`
} `json:"results"`
} `json:"web"`
}
func (c *Client) Search(ctx context.Context, provider Provider, query string, count int) ([]Result, error) {
query = strings.TrimSpace(query)
if query == "" {
return nil, fmt.Errorf("search query is required")
}
if count <= 0 {
count = 5
}
if count > 20 {
count = 20
}
switch provider.Type {
case ProviderBrave:
return c.searchBrave(ctx, provider.APIKey, query, count)
default:
return nil, fmt.Errorf("unsupported search provider type: %s", provider.Type)
}
}
func (c *Client) searchBrave(ctx context.Context, apiKey, query string, count int) ([]Result, error) {
apiKey = strings.TrimSpace(apiKey)
if apiKey == "" {
return nil, fmt.Errorf("brave api key is required")
}
u, err := url.Parse(braveSearchURL)
if err != nil {
return nil, err
}
q := u.Query()
q.Set("q", query)
q.Set("count", fmt.Sprintf("%d", count))
u.RawQuery = q.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Accept-Encoding", "gzip")
req.Header.Set("X-Subscription-Token", apiKey)
resp, err := c.http.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if err != nil {
return nil, err
}
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("brave search failed (%d): %s", resp.StatusCode, string(body))
}
var parsed braveSearchResponse
if err := json.Unmarshal(body, &parsed); err != nil {
return nil, err
}
if parsed.Web == nil || len(parsed.Web.Results) == 0 {
return []Result{}, nil
}
results := make([]Result, 0, len(parsed.Web.Results))
for _, item := range parsed.Web.Results {
results = append(results, Result{
Title: strings.TrimSpace(item.Title),
URL: strings.TrimSpace(item.URL),
Description: strings.TrimSpace(item.Description),
})
}
return results, nil
}