package api import ( "database/sql" "net/http" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" ) // NewRouter builds the HTTP surface. notify is passed through to the webhook, // the only handler that has to decide where a new incident's page goes; a zero // value disables notifications. func NewRouter(db *sql.DB, notify NotifyConfig) http.Handler { r := chi.NewRouter() r.Use(middleware.Logger) r.Use(middleware.Recoverer) r.Get("/healthz", func(w http.ResponseWriter, r *http.Request) { respond(w, http.StatusOK, map[string]string{"status": "ok"}) }) // Unauthenticated: bootstrap, the Alertmanager webhook receiver, and the // Acknowledge button in a push notification. The last one is authorised by // the single-use token in its path rather than an API key, and has to stay // reachable from outside the cluster for the button to work. r.Post("/api/bootstrap", handleBootstrap(db)) r.Post("/api/alertmanager/webhook", handleAlertmanagerWebhook(db, notify)) r.Post("/api/notify/ack/{token}", handleNotifyAck(db)) // All other /api routes require a valid API key. r.Group(func(r chi.Router) { r.Use(AuthMiddleware(db)) r.Get("/api/users", handleListUsers(db)) r.Post("/api/users", handleCreateUser(db)) r.Delete("/api/users/{id}", handleDeleteUser(db)) r.Put("/api/users/{id}/notify", handleSetNotifyTarget(db)) r.Post("/api/users/{id}/api-keys", handleCreateAPIKey(db)) r.Delete("/api/users/{id}/api-keys/{keyID}", handleDeleteAPIKey(db)) // Alerts are read-only: they are Alertmanager's record, not a work // queue. Everything a person does happens on the incident instead. r.Get("/api/alerts", handleListAlerts(db)) r.Get("/api/alerts/{id}", handleGetAlert(db)) r.Get("/api/incidents", handleListIncidents(db)) r.Get("/api/incidents/{id}", handleGetIncident(db)) r.Get("/api/incidents/{id}/alerts", handleIncidentAlerts(db)) r.Get("/api/incidents/{id}/timeline", handleIncidentTimeline(db)) r.Post("/api/incidents/{id}/acknowledge", handleIncidentAcknowledge(db)) r.Delete("/api/incidents/{id}/acknowledge", handleIncidentUnacknowledge(db)) r.Post("/api/incidents/{id}/resolve", handleIncidentResolve(db)) r.Post("/api/incidents/{id}/assign", handleIncidentAssign(db)) r.Post("/api/incidents/{id}/snooze", handleIncidentSnooze(db)) r.Delete("/api/incidents/{id}/snooze", handleIncidentUnsnooze(db)) r.Post("/api/incidents/{id}/archive", handleIncidentArchive(db)) r.Delete("/api/incidents/{id}/archive", handleIncidentUnarchive(db)) r.Post("/api/incidents/{id}/notes", handleCreateNote(db)) r.Delete("/api/incidents/{id}/notes/{eventID}", handleDeleteNote(db)) r.Post("/api/schedule", handleCreateSchedule(db)) r.Get("/api/schedule/current", handleCurrentSchedule(db)) // must be before /{id} r.Get("/api/schedule", handleListSchedule(db)) r.Delete("/api/schedule/{id}", handleDeleteSchedule(db)) r.Get("/api/stats/incidents", handleStatsIncidents(db)) r.Get("/api/stats/alerts", handleStatsAlerts(db)) r.Get("/api/stats/alerts/top", handleStatsTop(db)) r.Get("/api/stats/alerts/by-hour", handleStatsByHour(db)) r.Get("/api/stats/alerts/by-day", handleStatsByDay(db)) }) return r }