package server import ( "net/http" "time" sentryhttp "github.com/getsentry/sentry-go/http" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" "github.com/prometheus/client_golang/prometheus/promhttp" "sneak.berlin/go/webhooker/static" ) func (s *Server) SetupRoutes() { s.router = chi.NewRouter() // Global middleware stack — applied to every request. s.router.Use(middleware.Recoverer) s.router.Use(middleware.RequestID) s.router.Use(s.mw.Logging()) // Metrics middleware (only if credentials are configured) if s.params.Config.MetricsUsername != "" { s.router.Use(s.mw.Metrics()) } s.router.Use(s.mw.CORS()) s.router.Use(middleware.Timeout(60 * time.Second)) // Sentry error reporting (if SENTRY_DSN is set). Repanic is true // so panics still bubble up to the Recoverer middleware above. if s.sentryEnabled { sentryHandler := sentryhttp.New(sentryhttp.Options{ Repanic: true, }) s.router.Use(sentryHandler.Handle) } // Routes s.router.Get("/", s.h.HandleIndex()) s.router.Mount("/s", http.StripPrefix("/s", http.FileServer(http.FS(static.Static)))) s.router.Route("/api/v1", func(_ chi.Router) { // TODO: Add API routes here }) s.router.Get( "/.well-known/healthcheck", s.h.HandleHealthCheck(), ) // set up authenticated /metrics route: if s.params.Config.MetricsUsername != "" { s.router.Group(func(r chi.Router) { r.Use(s.mw.MetricsAuth()) r.Get("/metrics", http.HandlerFunc(promhttp.Handler().ServeHTTP)) }) } // pages that are rendered server-side s.router.Route("/pages", func(r chi.Router) { // Login page (no auth required) r.Get("/login", s.h.HandleLoginPage()) r.Post("/login", s.h.HandleLoginSubmit()) // Logout (auth required) r.Post("/logout", s.h.HandleLogout()) }) // User profile routes s.router.Route("/user/{username}", func(r chi.Router) { r.Get("/", s.h.HandleProfile()) }) // Webhook management routes (require authentication) s.router.Route("/sources", func(r chi.Router) { r.Use(s.mw.RequireAuth()) r.Get("/", s.h.HandleSourceList()) // List all webhooks r.Get("/new", s.h.HandleSourceCreate()) // Show create form r.Post("/new", s.h.HandleSourceCreateSubmit()) // Handle create submission }) s.router.Route("/source/{sourceID}", func(r chi.Router) { r.Use(s.mw.RequireAuth()) r.Get("/", s.h.HandleSourceDetail()) // View webhook details r.Get("/edit", s.h.HandleSourceEdit()) // Show edit form r.Post("/edit", s.h.HandleSourceEditSubmit()) // Handle edit submission r.Post("/delete", s.h.HandleSourceDelete()) // Delete webhook r.Get("/logs", s.h.HandleSourceLogs()) // View webhook logs r.Post("/entrypoints", s.h.HandleEntrypointCreate()) // Add entrypoint r.Post("/entrypoints/{entrypointID}/delete", s.h.HandleEntrypointDelete()) // Delete entrypoint r.Post("/entrypoints/{entrypointID}/toggle", s.h.HandleEntrypointToggle()) // Toggle entrypoint active r.Post("/targets", s.h.HandleTargetCreate()) // Add target r.Post("/targets/{targetID}/delete", s.h.HandleTargetDelete()) // Delete target r.Post("/targets/{targetID}/toggle", s.h.HandleTargetToggle()) // Toggle target active }) // Entrypoint endpoint — accepts incoming webhook POST requests only. // Using HandleFunc so the handler itself can return 405 for non-POST // methods (chi's Method routing returns 405 without Allow header). s.router.HandleFunc("/webhook/{uuid}", s.h.HandleWebhook()) }