package server import ( "io/fs" "net/http" "time" "git.eeqj.de/sneak/chat/web" 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" "github.com/spf13/viper" ) const routeTimeout = 60 * time.Second // SetupRoutes configures the HTTP routes and middleware. func (srv *Server) SetupRoutes() { srv.router = chi.NewRouter() srv.router.Use(middleware.Recoverer) srv.router.Use(middleware.RequestID) srv.router.Use(srv.mw.Logging()) if viper.GetString("METRICS_USERNAME") != "" { srv.router.Use(srv.mw.Metrics()) } srv.router.Use(srv.mw.CORS()) srv.router.Use(middleware.Timeout(routeTimeout)) if srv.sentryEnabled { sentryHandler := sentryhttp.New( sentryhttp.Options{ //nolint:exhaustruct // optional fields Repanic: true, }, ) srv.router.Use(sentryHandler.Handle) } // Health check. srv.router.Get( "/.well-known/healthcheck.json", srv.handlers.HandleHealthCheck(), ) // Protected metrics endpoint. if viper.GetString("METRICS_USERNAME") != "" { srv.router.Group(func(router chi.Router) { router.Use(srv.mw.MetricsAuth()) router.Get("/metrics", http.HandlerFunc( promhttp.Handler().ServeHTTP, )) }) } // API v1. srv.router.Route("/api/v1", srv.setupAPIv1) // Serve embedded SPA. srv.setupSPA() } func (srv *Server) setupAPIv1(router chi.Router) { router.Get( "/server", srv.handlers.HandleServerInfo(), ) router.Post( "/session", srv.handlers.HandleCreateSession(), ) router.Post( "/register", srv.handlers.HandleRegister(), ) router.Post( "/login", srv.handlers.HandleLogin(), ) router.Get( "/state", srv.handlers.HandleState(), ) router.Post( "/logout", srv.handlers.HandleLogout(), ) router.Get( "/users/me", srv.handlers.HandleUsersMe(), ) router.Get( "/messages", srv.handlers.HandleGetMessages(), ) router.Post( "/messages", srv.handlers.HandleSendCommand(), ) router.Get( "/history", srv.handlers.HandleGetHistory(), ) router.Get( "/channels", srv.handlers.HandleListAllChannels(), ) router.Get( "/channels/{channel}/members", srv.handlers.HandleChannelMembers(), ) } func (srv *Server) setupSPA() { distFS, err := fs.Sub(web.Dist, "dist") if err != nil { srv.log.Error( "failed to get web dist filesystem", "error", err, ) return } fileServer := http.FileServer(http.FS(distFS)) srv.router.Get("/*", func( writer http.ResponseWriter, request *http.Request, ) { readFS, ok := distFS.(fs.ReadFileFS) if !ok { fileServer.ServeHTTP(writer, request) return } fileData, readErr := readFS.ReadFile( request.URL.Path[1:], ) if readErr != nil || len(fileData) == 0 { indexHTML, indexErr := readFS.ReadFile( "index.html", ) if indexErr != nil { http.NotFound(writer, request) return } writer.Header().Set( "Content-Type", "text/html; charset=utf-8", ) writer.WriteHeader(http.StatusOK) _, _ = writer.Write(indexHTML) return } fileServer.ServeHTTP(writer, request) }) }