197 lines
5.1 KiB
Go
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,
|
|
})
|
|
}
|