273 lines
7.2 KiB
Go
273 lines
7.2 KiB
Go
package watchers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log"
|
|
"net/http"
|
|
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
// RepublishCallback is a function type for republishing metadata events
|
|
type RepublishCallback func() (int, error)
|
|
|
|
// Handlers provides HTTP handlers for watchers management
|
|
type Handlers struct {
|
|
manager *Manager
|
|
poller *Poller
|
|
republishCallback RepublishCallback
|
|
}
|
|
|
|
// NewHandlers creates new watchers handlers
|
|
func NewHandlers(manager *Manager, poller *Poller, republishCallback RepublishCallback) *Handlers {
|
|
return &Handlers{
|
|
manager: manager,
|
|
poller: poller,
|
|
republishCallback: republishCallback,
|
|
}
|
|
}
|
|
|
|
// RegisterRoutes registers all watchers-related routes
|
|
func (h *Handlers) RegisterRoutes(r *mux.Router) {
|
|
r.HandleFunc("/api/watchers", h.handleList).Methods("GET")
|
|
r.HandleFunc("/api/watchers", h.handleCreate).Methods("POST")
|
|
r.HandleFunc("/api/watchers/scan-all", h.handleScanAll).Methods("POST")
|
|
r.HandleFunc("/api/watchers/reset-all", h.handleResetAll).Methods("POST")
|
|
r.HandleFunc("/api/watchers/{id}", h.handleGet).Methods("GET")
|
|
r.HandleFunc("/api/watchers/{id}", h.handleUpdate).Methods("PUT")
|
|
r.HandleFunc("/api/watchers/{id}", h.handleDelete).Methods("DELETE")
|
|
r.HandleFunc("/api/watchers/{id}/scan", h.handleScan).Methods("POST")
|
|
r.HandleFunc("/api/watchers/{id}/reset", h.handleReset).Methods("POST")
|
|
}
|
|
|
|
// handleList handles GET /api/watchers
|
|
func (h *Handlers) handleList(w http.ResponseWriter, r *http.Request) {
|
|
statuses := h.poller.GetWatcherStatuses()
|
|
|
|
writeJSON(w, http.StatusOK, WatchersListResponse{
|
|
Watchers: statuses,
|
|
Count: len(statuses),
|
|
})
|
|
}
|
|
|
|
// handleGet handles GET /api/watchers/{id}
|
|
func (h *Handlers) handleGet(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
id := vars["id"]
|
|
|
|
watcher, err := h.manager.Get(id)
|
|
if err != nil {
|
|
writeError(w, http.StatusNotFound, err.Error())
|
|
return
|
|
}
|
|
|
|
// Get runtime status
|
|
status := h.poller.GetStatus(id)
|
|
|
|
response := WatcherStatus{
|
|
WatcherConfig: *watcher,
|
|
Active: status.Active,
|
|
LastScan: status.LastScan,
|
|
FileCount: status.FileCount,
|
|
IsScanning: status.IsScanning,
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, response)
|
|
}
|
|
|
|
// handleCreate handles POST /api/watchers
|
|
func (h *Handlers) handleCreate(w http.ResponseWriter, r *http.Request) {
|
|
var req CreateWatcherRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid JSON body")
|
|
return
|
|
}
|
|
|
|
watcher, err := h.manager.Create(req)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusCreated, watcher)
|
|
}
|
|
|
|
// handleUpdate handles PUT /api/watchers/{id}
|
|
func (h *Handlers) handleUpdate(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
id := vars["id"]
|
|
|
|
var req UpdateWatcherRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid JSON body")
|
|
return
|
|
}
|
|
|
|
watcher, err := h.manager.Update(id, req)
|
|
if err != nil {
|
|
if err.Error() == "watcher not found: "+id {
|
|
writeError(w, http.StatusNotFound, err.Error())
|
|
} else {
|
|
writeError(w, http.StatusBadRequest, err.Error())
|
|
}
|
|
return
|
|
}
|
|
|
|
// Get runtime status
|
|
status := h.poller.GetStatus(id)
|
|
|
|
response := WatcherStatus{
|
|
WatcherConfig: *watcher,
|
|
Active: status.Active,
|
|
LastScan: status.LastScan,
|
|
FileCount: status.FileCount,
|
|
IsScanning: status.IsScanning,
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, response)
|
|
}
|
|
|
|
// handleDelete handles DELETE /api/watchers/{id}
|
|
func (h *Handlers) handleDelete(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
id := vars["id"]
|
|
|
|
if err := h.manager.Delete(id); err != nil {
|
|
if err.Error() == "watcher not found: "+id {
|
|
writeError(w, http.StatusNotFound, err.Error())
|
|
} else {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
}
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"status": "ok",
|
|
"message": "Watcher deleted",
|
|
})
|
|
}
|
|
|
|
// handleScan handles POST /api/watchers/{id}/scan
|
|
func (h *Handlers) handleScan(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
id := vars["id"]
|
|
|
|
fileCount, err := h.poller.TriggerScan(id)
|
|
if err != nil {
|
|
if err.Error() == "watcher not found: "+id {
|
|
writeError(w, http.StatusNotFound, err.Error())
|
|
} else {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
}
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"status": "ok",
|
|
"message": "Scan triggered",
|
|
"fileCount": fileCount,
|
|
})
|
|
}
|
|
|
|
// handleScanAll handles POST /api/watchers/scan-all
|
|
func (h *Handlers) handleScanAll(w http.ResponseWriter, r *http.Request) {
|
|
totalCount, err := h.poller.TriggerScanAll()
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"status": "ok",
|
|
"message": "All watchers scanned",
|
|
"totalFiles": totalCount,
|
|
})
|
|
}
|
|
|
|
// handleReset handles POST /api/watchers/{id}/reset
|
|
func (h *Handlers) handleReset(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
id := vars["id"]
|
|
|
|
fileCount, err := h.poller.TriggerReset(id)
|
|
if err != nil {
|
|
if err.Error() == "watcher not found: "+id {
|
|
writeError(w, http.StatusNotFound, err.Error())
|
|
} else {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
}
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"status": "ok",
|
|
"message": "Reset complete",
|
|
"watcherId": id,
|
|
"fileCount": fileCount,
|
|
})
|
|
}
|
|
|
|
// handleResetAll handles POST /api/watchers/reset-all
|
|
func (h *Handlers) handleResetAll(w http.ResponseWriter, r *http.Request) {
|
|
totalCount, err := h.poller.TriggerResetAll()
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
// Republish all metadata events after reset
|
|
republishedCount := 0
|
|
if h.republishCallback != nil {
|
|
count, err := h.republishCallback()
|
|
if err != nil {
|
|
log.Printf("[Handlers] Warning: failed to republish metadata after reset: %v", err)
|
|
} else {
|
|
republishedCount = count
|
|
}
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"status": "ok",
|
|
"message": "All watchers reset",
|
|
"totalFiles": totalCount,
|
|
"republishedEvents": republishedCount,
|
|
})
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// writeError writes the typed error envelope (see api/handlers.go for the
|
|
// canonical definition + slug vocabulary). Kept local to avoid an import
|
|
// cycle with the api package.
|
|
func writeError(w http.ResponseWriter, status int, message string) {
|
|
slug, retryable := slugForStatus(status)
|
|
writeJSON(w, status, map[string]interface{}{
|
|
"error": slug,
|
|
"message": message,
|
|
"retryable": retryable,
|
|
})
|
|
}
|
|
|
|
func slugForStatus(status int) (string, bool) {
|
|
switch status {
|
|
case http.StatusConflict:
|
|
return "alias_collision", false
|
|
case http.StatusNotFound:
|
|
return "unknown_root", false
|
|
case http.StatusBadRequest:
|
|
return "schema_violation", false
|
|
case http.StatusServiceUnavailable:
|
|
return "storage_unavailable", true
|
|
case http.StatusInternalServerError:
|
|
return "internal", true
|
|
default:
|
|
return "internal", status >= 500
|
|
}
|
|
}
|