Files
Strix/internal/api/handlers/search.go
T
eduard256 86a8fb36d5 Fix all linter issues: errcheck, staticcheck, and unused code
- Fix critical scanner.go bug: ineffective break in select (SA4011)
  Use labeled break to properly exit loop on context cancellation

- Add error checking for all file.Close() and resp.Body.Close()
  Prevent resource leaks in loader, onvif_simple, and tester

- Add error checking for fmt.Sscanf() calls in tester.go
  Prevent silent parse failures for FPS and bitrate extraction

- Add error checking for all SSE streamWriter calls
  Explicit ignore with _ = for SendJSON and SendError

- Remove unused sync.RWMutex field from SearchEngine

- Refactor if/else to switch for CodecType (staticcheck QF1003)
  More idiomatic Go code in stream tester

All 20 linter issues resolved. Code compiles and runs correctly.
2025-11-12 11:17:12 +03:00

99 lines
2.6 KiB
Go

package handlers
import (
"encoding/json"
"net/http"
"github.com/go-playground/validator/v10"
"github.com/eduard256/Strix/internal/camera/database"
"github.com/eduard256/Strix/internal/models"
)
// SearchHandler handles camera search requests
type SearchHandler struct {
searchEngine *database.SearchEngine
validator *validator.Validate
logger interface{ Debug(string, ...any); Error(string, error, ...any); Info(string, ...any) }
}
// NewSearchHandler creates a new search handler
func NewSearchHandler(
searchEngine *database.SearchEngine,
logger interface{ Debug(string, ...any); Error(string, error, ...any); Info(string, ...any) },
) *SearchHandler {
return &SearchHandler{
searchEngine: searchEngine,
validator: validator.New(),
logger: logger,
}
}
// ServeHTTP handles search requests
func (h *SearchHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Parse request body
var req models.CameraSearchRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
h.logger.Error("failed to decode search request", err)
h.sendErrorResponse(w, "Invalid request body", http.StatusBadRequest)
return
}
// Set default limit if not provided
if req.Limit <= 0 {
req.Limit = 10
}
// Validate request
if err := h.validator.Struct(req); err != nil {
h.logger.Error("search request validation failed", err)
h.sendErrorResponse(w, "Validation failed: "+err.Error(), http.StatusBadRequest)
return
}
h.logger.Info("camera search requested",
"query", req.Query,
"limit", req.Limit,
"remote_addr", r.RemoteAddr,
)
// Perform search
response, err := h.searchEngine.Search(req.Query, req.Limit)
if err != nil {
h.logger.Error("search failed", err)
h.sendErrorResponse(w, "Search failed", http.StatusInternalServerError)
return
}
// Send response
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(response); err != nil {
h.logger.Error("failed to encode search response", err)
}
h.logger.Info("search completed",
"query", req.Query,
"returned", response.Returned,
"total", response.Total,
)
}
// sendErrorResponse sends an error response
func (h *SearchHandler) sendErrorResponse(w http.ResponseWriter, message string, statusCode int) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
response := map[string]interface{}{
"error": true,
"message": message,
"code": statusCode,
}
_ = json.NewEncoder(w).Encode(response)
}