Files
meta-core/internal/watcher/handlers.go
T

197 lines
5.1 KiB
Go

package watcher
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
// Handlers provides HTTP handlers for watcher operations
type Handlers struct {
watcher *Watcher
dispatcher *Dispatcher
}
// NewHandlers creates new watcher handlers
func NewHandlers(watcher *Watcher, dispatcher *Dispatcher) *Handlers {
return &Handlers{
watcher: watcher,
dispatcher: dispatcher,
}
}
// RegisterRoutes registers all watcher-related routes
func (h *Handlers) RegisterRoutes(r *mux.Router) {
// Event subscriptions
r.HandleFunc("/api/events/subscribe", h.handleSSESubscribe).Methods("GET")
r.HandleFunc("/api/events/poll", h.handlePoll).Methods("GET")
r.HandleFunc("/api/events/subscribers", h.handleListSubscribers).Methods("GET")
r.HandleFunc("/api/events/subscribers", h.handleAddSubscriber).Methods("POST")
r.HandleFunc("/api/events/subscribers/{url}", h.handleRemoveSubscriber).Methods("DELETE")
// Scan management
r.HandleFunc("/api/scan/trigger", h.handleTriggerScan).Methods("POST")
r.HandleFunc("/api/scan/status", h.handleScanStatus).Methods("GET")
}
// handleSSESubscribe handles GET /api/events/subscribe (Server-Sent Events)
func (h *Handlers) handleSSESubscribe(w http.ResponseWriter, r *http.Request) {
// Set SSE headers
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("Access-Control-Allow-Origin", "*")
// Create event channel
eventChan := make(chan FileEvent, 100)
h.dispatcher.AddSSEClient(eventChan)
// Cleanup on disconnect
defer h.dispatcher.RemoveSSEClient(eventChan)
// Get flusher
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "SSE not supported", http.StatusInternalServerError)
return
}
// Send initial connected message
fmt.Fprintf(w, "event: connected\ndata: {\"status\":\"connected\"}\n\n")
flusher.Flush()
// Stream events
for {
select {
case event := <-eventChan:
data, err := json.Marshal(event)
if err != nil {
continue
}
fmt.Fprintf(w, "event: file\ndata: %s\n\n", string(data))
flusher.Flush()
case <-r.Context().Done():
return
}
}
}
// handlePoll handles GET /api/events/poll
func (h *Handlers) handlePoll(w http.ResponseWriter, r *http.Request) {
// Get since parameter
sinceStr := r.URL.Query().Get("since")
sinceMS := int64(0)
if sinceStr != "" {
if parsed, err := strconv.ParseInt(sinceStr, 10, 64); err == nil {
sinceMS = parsed
}
}
// Get limit parameter
limitStr := r.URL.Query().Get("limit")
limit := 100
if limitStr != "" {
if parsed, err := strconv.Atoi(limitStr); err == nil {
limit = parsed
}
}
events := h.watcher.GetRecentEvents(sinceMS, limit)
writeJSON(w, http.StatusOK, EventsListResponse{
Events: events,
Count: len(events),
})
}
// handleListSubscribers handles GET /api/events/subscribers
func (h *Handlers) handleListSubscribers(w http.ResponseWriter, r *http.Request) {
subscribers := h.dispatcher.ListSubscribers()
writeJSON(w, http.StatusOK, SubscribersListResponse{
Subscribers: subscribers,
Count: len(subscribers),
})
}
// handleAddSubscriber handles POST /api/events/subscribers
func (h *Handlers) handleAddSubscriber(w http.ResponseWriter, r *http.Request) {
var req SubscribeRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid JSON body")
return
}
if req.URL == "" {
writeError(w, http.StatusBadRequest, "URL is required")
return
}
if err := h.dispatcher.Subscribe(req.URL, req.EventTypes); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
writeJSON(w, http.StatusCreated, map[string]interface{}{
"status": "ok",
"message": "Subscribed",
"url": req.URL,
})
}
// handleRemoveSubscriber handles DELETE /api/events/subscribers/{url}
func (h *Handlers) handleRemoveSubscriber(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
url := vars["url"]
if url == "" {
writeError(w, http.StatusBadRequest, "URL is required")
return
}
if err := h.dispatcher.Unsubscribe(url); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"status": "ok",
"message": "Unsubscribed",
})
}
// handleTriggerScan handles POST /api/scan/trigger
func (h *Handlers) handleTriggerScan(w http.ResponseWriter, r *http.Request) {
go h.watcher.RunScan()
writeJSON(w, http.StatusOK, map[string]interface{}{
"status": "ok",
"message": "Scan triggered",
})
}
// handleScanStatus handles GET /api/scan/status
func (h *Handlers) handleScanStatus(w http.ResponseWriter, r *http.Request) {
status := h.watcher.GetStatus()
writeJSON(w, http.StatusOK, status)
}
// Helper functions
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
func writeError(w http.ResponseWriter, status int, message string) {
writeJSON(w, status, map[string]string{
"error": http.StatusText(status),
"message": message,
})
}