From 000c12845c4ec2ff7a3bcba294c416959b5a73e4 Mon Sep 17 00:00:00 2001 From: Daniel Svitan Date: Sat, 7 Jun 2025 08:59:34 +0200 Subject: [PATCH] :sparkles: Adds fetching and toggling alerts --- server/go.mod | 1 + server/go.sum | 2 + server/main.go | 119 +++++++------------------------------------- server/routes.go | 110 ++++++++++++++++++++++++++++++++++++++++ web/Dockerfile | 4 +- web/pages/index.vue | 28 ++++++++++- 6 files changed, 159 insertions(+), 105 deletions(-) create mode 100644 server/routes.go diff --git a/server/go.mod b/server/go.mod index 8dddd6a..ef03ae0 100644 --- a/server/go.mod +++ b/server/go.mod @@ -9,6 +9,7 @@ require ( ) require ( + github.com/gorilla/websocket v1.5.3 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect diff --git a/server/go.sum b/server/go.sum index aacf720..edce4ad 100644 --- a/server/go.sum +++ b/server/go.sum @@ -1,5 +1,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= diff --git a/server/main.go b/server/main.go index 22b4c9f..f1325d0 100644 --- a/server/main.go +++ b/server/main.go @@ -24,12 +24,13 @@ var token string // the condition is: distance >= threshold (is door open) var opened bool +var openedChange = make(chan bool) // is door locked var locked bool = false -// alert the user when door locked and but open? -var alert bool = false +// alerts the user when door locked and but open? +var alerts bool = false var gotifyToken string var gotifyURL string @@ -44,23 +45,23 @@ type ChangeLockReq struct { } type ChangeAlertReq struct { - Alert bool `json:"alert"` - For int `json:"for"` + Alerts bool `json:"alerts"` + For int `json:"for"` } func main() { _ = godotenv.Load() token = os.Getenv("TOKEN") - alertRaw := strings.ToLower(os.Getenv("USE_ALERTS")) - alert = alertRaw == "true" || alertRaw == "t" || alertRaw == "1" || alertRaw == "y" || alertRaw == "yes" + 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 alert && gotifyToken == "" { + if alerts && gotifyToken == "" { log.Fatal("GOTIFY_TOKEN can't be empty when alerts are enabled") } - if alert && gotifyURL == "" { + if alerts && gotifyURL == "" { log.Fatal("GOTIFY_URL can't be empty when alerts are enabled") } @@ -114,103 +115,17 @@ func main() { } } - app.GET("/", func(c echo.Context) error { - return c.JSON(http.StatusOK, "Hello world!") - }) + app.GET("/", helloWorld) - app.GET("/open", authed(func(c echo.Context) error { - mut.Lock() - o := opened - mut.Unlock() + app.GET("/opened", authed(getOpened)) + app.GET("/opened/ws", authed(getOpenedWs)) + app.POST("/opened", authed(setOpened)) - return c.JSON(http.StatusOK, o) - })) + app.GET("/locked", authed(getLocked)) + app.POST("/locked", authed(setLocked)) - app.POST("/open", authed(func(c echo.Context) error { - var data ChangeOpenReq - err := c.Bind(&data) - if err != nil { - return c.NoContent(http.StatusBadRequest) - } - - if data.Opened == opened { - return c.NoContent(http.StatusOK) - } - - mut.Lock() - opened = data.Opened - if locked && alert { - var action string - if opened { - action = "opened" - } else { - action = "closed" - } - - go sendAlert(action) - } - - mut.Unlock() - return c.NoContent(http.StatusOK) - })) - - app.GET("/lock", authed(func(c echo.Context) error { - mut.Lock() - l := locked - mut.Unlock() - - return c.JSON(http.StatusOK, l) - })) - - app.POST("/lock", authed(func(c echo.Context) error { - var data ChangeLockReq - err := c.Bind(&data) - if err != nil { - return err - } - - if data.Locked == locked { - return c.NoContent(http.StatusOK) - } - - mut.Lock() - locked = data.Locked - mut.Unlock() - - return c.NoContent(http.StatusOK) - })) - - app.GET("/alert", authed(func(c echo.Context) error { - mut.Lock() - a := alert - mut.Unlock() - - return c.JSON(http.StatusOK, a) - })) - - app.POST("/alert", authed(func(c echo.Context) error { - var data ChangeAlertReq - err := c.Bind(&data) - if err != nil { - return err - } - - if data.For > 0 { - go func() { - time.Sleep(time.Duration(data.For) * time.Second) - - mut.Lock() - alert = !data.Alert - mut.Unlock() - }() - } - - mut.Lock() - alert = data.Alert - mut.Unlock() - - return c.NoContent(http.StatusOK) - })) + app.GET("/alerts", authed(getAlerts)) + app.POST("/alerts", authed(setAlerts)) log.Fatal(app.Start(":1323")) } diff --git a/server/routes.go b/server/routes.go new file mode 100644 index 0000000..14ae606 --- /dev/null +++ b/server/routes.go @@ -0,0 +1,110 @@ +package main + +import ( + "github.com/labstack/echo/v4" + "net/http" + "time" +) + +func helloWorld(c echo.Context) error { + return c.JSON(http.StatusOK, "Hello world!") +} + +func getOpened(c echo.Context) error { + mut.Lock() + o := opened + mut.Unlock() + + return c.JSON(http.StatusOK, o) +} + +func getOpenedWs(c echo.Context) error { + return nil +} + +func setOpened(c echo.Context) error { + var data ChangeOpenReq + err := c.Bind(&data) + if err != nil { + return c.NoContent(http.StatusBadRequest) + } + + if data.Opened == opened { + return c.NoContent(http.StatusOK) + } + + mut.Lock() + opened = data.Opened + if locked && alerts { + var action string + if opened { + action = "opened" + } else { + action = "closed" + } + + go sendAlert(action) + } + + openedChange <- true + mut.Unlock() + return c.NoContent(http.StatusOK) +} + +func getLocked(c echo.Context) error { + mut.Lock() + l := locked + mut.Unlock() + + return c.JSON(http.StatusOK, l) +} + +func setLocked(c echo.Context) error { + var data ChangeLockReq + err := c.Bind(&data) + if err != nil { + return err + } + + if data.Locked == locked { + return c.NoContent(http.StatusOK) + } + + mut.Lock() + locked = data.Locked + mut.Unlock() + + return c.NoContent(http.StatusOK) +} + +func getAlerts(c echo.Context) error { + mut.Lock() + a := alerts + mut.Unlock() + + return c.JSON(http.StatusOK, a) +} + +func setAlerts(c echo.Context) error { + var data ChangeAlertReq + err := c.Bind(&data) + if err != nil { + return err + } + + if data.For > 0 { + go func() { + time.Sleep(time.Duration(data.For) * time.Second) + + mut.Lock() + alerts = !data.Alerts + mut.Unlock() + }() + } + + mut.Lock() + alerts = data.Alerts + mut.Unlock() + + return c.NoContent(http.StatusOK) +} diff --git a/web/Dockerfile b/web/Dockerfile index 70acc9f..48c1280 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -13,8 +13,8 @@ FROM oven/bun:1 WORKDIR /app COPY --from=build /app/.output . -ENV PORT 3000 -ENV HOST 0.0.0.0 +ENV PORT=3000 +ENV HOST=0.0.0.0 EXPOSE 3000 CMD ["bun", "/app/server/index.mjs"] diff --git a/web/pages/index.vue b/web/pages/index.vue index 664f807..3819ca9 100644 --- a/web/pages/index.vue +++ b/web/pages/index.vue @@ -77,7 +77,7 @@ :color="alert ? 'primary' : 'secondary'" variant="solid" class="flex items-center justify-center w-full h-10" - @click="() => (alert = !alert)" + @click="toggleAlert" > {{ alert ? "Turn off" : "Turn on" }} @@ -119,6 +119,21 @@ async function toggleLock() { .catch(handleRequestError) } +async function toggleAlert() { + const desired = !alert.value + axios + .post(useAPI("/alert"), { alert: desired }, { headers: useHeaders() }) + .then((res) => { + handleResponse(res, () => { + toast.add({ + title: `Alerts were turned ${desired ? "on" : "off"}`, + }) + alert.value = desired + }) + }) + .catch(handleRequestError) +} + onMounted(() => { if (!token.value) { return navigateTo("/token") @@ -134,5 +149,16 @@ onMounted(() => { }) }) .catch(handleRequestError) + + axios + .get(useAPI("/alert"), { + headers: useHeaders(), + }) + .then((res) => { + handleResponse(res, (response) => { + alert.value = response.data + }) + }) + .catch(handleRequestError) })