lockserver/httpserver/server.go

129 lines
3.4 KiB
Go

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))
}
}