Files
torrent-indexer/api/search.go

159 lines
4.6 KiB
Go

package handler
import (
"encoding/json"
"net/http"
"strconv"
"time"
meilisearch "github.com/felipemarinho97/torrent-indexer/search"
)
// MeilisearchHandler handles HTTP requests for Meilisearch integration.
type MeilisearchHandler struct {
Module *meilisearch.SearchIndexer
}
// HealthResponse represents the health check response
type HealthResponse struct {
Status string `json:"status"`
Service string `json:"service"`
Details map[string]interface{} `json:"details,omitempty"`
Timestamp string `json:"timestamp"`
}
// StatsResponse represents the stats endpoint response
type StatsResponse struct {
Status string `json:"status"`
NumberOfDocuments int64 `json:"numberOfDocuments"`
IsIndexing bool `json:"isIndexing"`
FieldDistribution map[string]int64 `json:"fieldDistribution"`
Service string `json:"service"`
}
// NewMeilisearchHandler creates a new instance of MeilisearchHandler.
func NewMeilisearchHandler(module *meilisearch.SearchIndexer) *MeilisearchHandler {
return &MeilisearchHandler{Module: module}
}
// SearchTorrentHandler handles the searching of torrent items.
func (h *MeilisearchHandler) SearchTorrentHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
query := r.URL.Query().Get("q")
if query == "" {
http.Error(w, "Query parameter 'q' is required", http.StatusBadRequest)
return
}
limitStr := r.URL.Query().Get("limit")
limit := 10 // Default limit
if limitStr != "" {
var err error
limit, err = strconv.Atoi(limitStr)
if err != nil || limit <= 0 {
http.Error(w, "Invalid limit parameter", http.StatusBadRequest)
return
}
}
results, err := h.Module.SearchTorrent(query, limit)
if err != nil {
http.Error(w, "Failed to search torrents", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(results); err != nil {
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
}
}
// HealthHandler provides a health check endpoint for Meilisearch.
func (h *MeilisearchHandler) HealthHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "application/json")
// Check if Meilisearch is healthy
isHealthy := h.Module.IsHealthy()
response := HealthResponse{
Service: "meilisearch",
Timestamp: getCurrentTimestamp(),
}
if isHealthy {
// Try to get additional stats for more detailed health info
stats, err := h.Module.GetStats()
if err == nil {
response.Status = "healthy"
response.Details = map[string]interface{}{
"documents": stats.NumberOfDocuments,
"indexing": stats.IsIndexing,
}
w.WriteHeader(http.StatusOK)
} else {
// Service is up but can't get stats
response.Status = "degraded"
response.Details = map[string]interface{}{
"error": "Could not retrieve stats",
}
w.WriteHeader(http.StatusOK)
}
} else {
// Service is down
response.Status = "unhealthy"
w.WriteHeader(http.StatusServiceUnavailable)
}
if err := json.NewEncoder(w).Encode(response); err != nil {
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
}
}
// StatsHandler provides detailed statistics about the Meilisearch index.
func (h *MeilisearchHandler) StatsHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "application/json")
// Get detailed stats from Meilisearch
stats, err := h.Module.GetStats()
if err != nil {
// Check if it's a connectivity issue
if !h.Module.IsHealthy() {
http.Error(w, "Meilisearch service is unavailable", http.StatusServiceUnavailable)
return
}
http.Error(w, "Failed to retrieve statistics", http.StatusInternalServerError)
return
}
response := StatsResponse{
Status: "healthy",
Service: "meilisearch",
NumberOfDocuments: stats.NumberOfDocuments,
IsIndexing: stats.IsIndexing,
FieldDistribution: stats.FieldDistribution,
}
if err := json.NewEncoder(w).Encode(response); err != nil {
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
}
}
// getCurrentTimestamp returns the current timestamp in RFC3339 format
func getCurrentTimestamp() string {
return time.Now().Format(time.RFC3339)
}