package main import ( "bytes" "encoding/json" "errors" "fmt" "io" "net/http" "os" "strings" "sync" "time" "github.com/joho/godotenv" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/labstack/gommon/log" ) const TimeFormat = "2006-01-02 15:04:05" var token string // the condition is: distance >= threshold (is door open) var opened bool var openedChange = make(chan any) // is door locked var locked bool = false // alerts the user when door locked and but open? var alerts bool = false var gotifyToken string var gotifyURL string var mut sync.Mutex type ChangeOpenReq struct { Opened bool `json:"opened"` } type ChangeLockReq struct { Locked bool `json:"locked"` } type ChangeAlertReq struct { Alerts bool `json:"alerts"` For int `json:"for"` } func main() { _ = godotenv.Load() token = os.Getenv("TOKEN") alertsRaw := strings.ToLower(os.Getenv("USE_ALERTS")) alerts = alertsRaw == "true" || alertsRaw == "t" || alertsRaw == "1" || alertsRaw == "y" || alertsRaw == "yes" gotifyToken = os.Getenv("GOTIFY_TOKEN") gotifyURL = os.Getenv("GOTIFY_URL") if alerts && gotifyToken == "" { log.Fatal("GOTIFY_TOKEN can't be empty when alerts are enabled") } if alerts && gotifyURL == "" { log.Fatal("GOTIFY_URL can't be empty when alerts are enabled") } app := echo.New() app.Logger.SetLevel(log.INFO) log.SetLevel(log.INFO) app.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{ StackSize: 1 << 10, LogLevel: log.ERROR, })) app.Use(middleware.Secure()) app.Use(middleware.CORSWithConfig(middleware.CORSConfig{ AllowOrigins: []string{"*"}, AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization}, })) app.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ Format: "${time_custom} ${method} ${uri} ---> ${status} in ${latency_human} (${bytes_out} bytes)\n", CustomTimeFormat: TimeFormat, })) log.SetHeader("${time_rfc3339} ${level}") app.Use(middleware.RemoveTrailingSlash()) app.OnAddRouteHandler = func(host string, route echo.Route, handler echo.HandlerFunc, middleware []echo.MiddlewareFunc) { now := time.Now() fmt.Printf("%s registered %-6s %s\n", now.Format(TimeFormat), route.Method, route.Path) } app.HTTPErrorHandler = func(err error, c echo.Context) { if c.Response().Committed { log.Error(err) return } code := http.StatusInternalServerError var response any = err.Error() var httpErr *echo.HTTPError if errors.As(err, &httpErr) { code = httpErr.Code response = httpErr } if code >= 500 { log.Error(err) } err2 := c.JSON(code, response) if err2 != nil { log.Error(err2) } } app.GET("/", helloWorld) app.GET("/opened", authed(getOpened)) app.GET("/opened/ws", authed(getOpenedWs)) app.POST("/opened", authed(setOpened)) app.GET("/locked", authed(getLocked)) app.POST("/locked", authed(setLocked)) app.GET("/alerts", authed(getAlerts)) app.POST("/alerts", authed(setAlerts)) log.Fatal(app.Start(":1323")) } func authed(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { provided := c.Request().Header.Get("Authorization") if provided != fmt.Sprintf("Bearer %s", token) { return c.NoContent(http.StatusUnauthorized) } return next(c) } } func sendAlert(action string) { to := fmt.Sprintf("%s/message?token=%s", gotifyURL, gotifyToken) what := echo.Map{ "title": fmt.Sprintf("Your door has been %s", action), "priority": 5, "message": fmt.Sprintf("Your locked door has been %s at %s", action, time.Now().Format(TimeFormat)), } b, err := json.Marshal(&what) if err != nil { log.Error(err) return } res, err := http.Post(to, "application/json", bytes.NewBuffer(b)) if err != nil { log.Error(err) return } body, err := io.ReadAll(res.Body) if err != nil { log.Error(err) return } if res.StatusCode != 200 { log.Errorf("sendAlert response status code is not 200: %d, body:\n%s", res.StatusCode, body) } else { log.Infof("sent alert (%s) to gotify", action) } }