package httpserver import ( "context" "database/sql" "errors" "net/http" "time" echo "github.com/labstack/echo/v4" "github.com/sirupsen/logrus" "git.janky.solutions/finn/lockserver/config" "git.janky.solutions/finn/lockserver/frontend" "git.janky.solutions/finn/lockserver/zwavejs" ) const contextKeyZWaveClient = "zwave-client" var server *echo.Echo func ListenAndServe(zwaveClient *zwavejs.Client) { server = echo.New() server.HideBanner = true server.HidePort = true server.HTTPErrorHandler = handleError server.Renderer = &Template{} server.Use(accessLogMiddleware) server.RouteNotFound("/*", tmpl("404.html")) server.StaticFS("/static", frontend.Static) server.GET("/", indexHandler) server.GET("/locks/:lock", lockHandler) server.GET("/locks/:lock/edit", lockEditHandler) server.POST("/locks/:lock/edit", lockEditHandler) server.GET("/locks/:lock/codes/:slot", lockCodeEditHandler) server.POST("/locks/:lock/codes/:slot", lockCodeEditHandler, addZWaveClientToContextKey(zwaveClient)) server.GET("/add-code", tmpl("add-code.html")) server.POST("/add-code", addCode, addZWaveClientToContextKey(zwaveClient)) logrus.WithField("address", config.C.HTTPBind).Info("starting http server") err := server.Start(config.C.HTTPBind) if err != http.ErrServerClosed { logrus.WithError(err).Fatal("error starting http server") } } type errorTemplateData struct { baseTemplateData Error error } func handleError(err error, c echo.Context) { if errors.Is(err, sql.ErrNoRows) { c.Response().WriteHeader(http.StatusNotFound) err = frontend.Templates.ExecuteTemplate(c.Response(), "404.html", getBaseTemplateData(c.Request().Header)) if err == nil { return } } logrus.WithFields(logrus.Fields{ "path": c.Request().URL.Path, "method": c.Request().Method, "error": err, }).Error("error handling request") frontend.Templates.ExecuteTemplate(c.Response(), "500.html", errorTemplateData{ baseTemplateData: getBaseTemplateData(c.Request().Header), Error: err, }) } func Shutdown(ctx context.Context) error { if server == nil { return nil } return server.Shutdown(ctx) } func accessLogMiddleware(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { start := time.Now() err := next(c) log := logrus.WithFields(logrus.Fields{ "method": c.Request().Method, "path": c.Request().URL.Path, "duration": time.Since(start), "status": c.Response().Status, "source": c.Request().RemoteAddr, }) if err != nil { log = log.WithError(err) } log.Debug("request handled") return err } } func addZWaveClientToContextKey(client *zwavejs.Client) func(echo.HandlerFunc) echo.HandlerFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { c.Set(contextKeyZWaveClient, client) return next(c) } } } type baseTemplateData struct { BaseURL string Username string UserDisplayName string } func getBaseTemplateData(headers http.Header) baseTemplateData { return baseTemplateData{ BaseURL: headers.Get("X-Ingress-Path"), Username: headers.Get("X-Remote-User-Name"), UserDisplayName: headers.Get("X-Remote-User-Display-Name"), } } func tmpl(filename string) echo.HandlerFunc { return func(c echo.Context) error { return frontend.Templates.ExecuteTemplate(c.Response(), filename, getBaseTemplateData(c.Request().Header)) } }