Skip to content

Commit

Permalink
Merge pull request #173 from calvinmclean/fix/notifications
Browse files Browse the repository at this point in the history
Various fixes and improvements for Notifications
  • Loading branch information
calvinmclean committed Jul 17, 2024
2 parents 282fdb6 + 75ac031 commit cce653c
Show file tree
Hide file tree
Showing 24 changed files with 434 additions and 182 deletions.
1 change: 0 additions & 1 deletion deploy/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
# - run-local: run required services + extras like Grafana and Prometheus
# - demo: run everything, including an instance of garden-app and garden-controller

version: "3.9"
services:
grafana:
image: "grafana/grafana:latest"
Expand Down
7 changes: 3 additions & 4 deletions garden-app/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.21.3
require (
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/ajg/form v1.5.1
github.com/calvinmclean/babyapi v0.14.0
github.com/calvinmclean/babyapi v0.21.0
github.com/eclipse/paho.mqtt.golang v1.4.3
github.com/go-chi/render v1.0.3
github.com/go-co-op/gocron v1.35.2
Expand All @@ -18,7 +18,7 @@ require (
github.com/robfig/cron/v3 v3.0.1
github.com/rs/xid v1.5.0
github.com/slok/go-http-metrics v0.10.0
github.com/spf13/cobra v1.8.0
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.17.0
github.com/stretchr/testify v1.8.4
github.com/tarmac-project/hord v0.6.0
Expand Down Expand Up @@ -53,7 +53,7 @@ require (
github.com/gdamore/tcell/v2 v2.6.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.1 // indirect
github.com/go-chi/chi/v5 v5.0.12 // indirect
github.com/go-chi/chi/v5 v5.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.15.5 // indirect
Expand Down Expand Up @@ -84,7 +84,6 @@ require (
github.com/labstack/gommon v0.4.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/madflojo/hord v0.2.2 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailgun/raymond/v2 v2.0.48 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
Expand Down
16 changes: 7 additions & 9 deletions garden-app/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/calvinmclean/babyapi v0.14.0 h1:S5gelxegAdOvqMBcQKvzA9vGLIScxd9LPnMKnKL6wVw=
github.com/calvinmclean/babyapi v0.14.0/go.mod h1:KkJFZ4FUfPdKbu6xi/q3tIV7ncgvcSs66GmFwpLmF00=
github.com/calvinmclean/babyapi v0.21.0 h1:rRakzuDY1ZTz92QEZmfWMshTDMaBwgBYOp6nTF3D4A0=
github.com/calvinmclean/babyapi v0.21.0/go.mod h1:zSNiVRsL3DBPOMkXxMJOTFNtzU1ZrPFKD0LFx2JVp4I=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
Expand All @@ -90,7 +90,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
Expand Down Expand Up @@ -128,8 +128,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-co-op/gocron v1.35.2 h1:lG3rdA9TqBBC/PtT2ukQqgLm6jEepnAzz3+OQetvPTE=
Expand Down Expand Up @@ -292,8 +292,6 @@ github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/madflojo/hord v0.2.2 h1:ZUE6J6sIyrnZmxkjSIe7OkImZllhFQNRAj9EDcf8A+k=
github.com/madflojo/hord v0.2.2/go.mod h1:VX6MCau/8uOKiNCSl7igl03kh5TgBkQhRL9ypQcsCyo=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw=
Expand Down Expand Up @@ -386,8 +384,8 @@ github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY=
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI=
Expand Down
16 changes: 14 additions & 2 deletions garden-app/pkg/garden.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Garden struct {
EndDate *time.Time `json:"end_date,omitempty" yaml:"end_date,omitempty"`
LightSchedule *LightSchedule `json:"light_schedule,omitempty" yaml:"light_schedule,omitempty"`
TemperatureHumiditySensor *bool `json:"temperature_humidity_sensor,omitempty" yaml:"temperature_humidity_sensor,omitempty"`
NotificationClientID *string `json:"notification_client_id,omitempty" yaml:"notification_client_id,omitempty"`
}

func (g *Garden) GetID() string {
Expand All @@ -41,6 +42,14 @@ func (g *Garden) String() string {
return fmt.Sprintf("%+v", *g)
}

func (g *Garden) GetNotificationClientID() string {
if g.NotificationClientID == nil {
return ""
}

return *g.NotificationClientID
}

// GardenHealth holds information about the Garden controller's health status
type GardenHealth struct {
Status HealthStatus `json:"status,omitempty"`
Expand Down Expand Up @@ -117,15 +126,18 @@ func (g *Garden) Patch(newGarden *Garden) *babyapi.ErrResponse {

// If both Duration and StartTime are empty, remove the schedule
if newGarden.LightSchedule.Duration == nil &&
newGarden.LightSchedule.StartTime == nil &&
newGarden.LightSchedule.NotificationClientID == nil {
newGarden.LightSchedule.StartTime == nil {
g.LightSchedule = nil
}
}
if newGarden.TemperatureHumiditySensor != nil {
g.TemperatureHumiditySensor = newGarden.TemperatureHumiditySensor
}

if newGarden.NotificationClientID != nil {
g.NotificationClientID = newGarden.NotificationClientID
}

return nil
}

Expand Down
18 changes: 3 additions & 15 deletions garden-app/pkg/light_schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,25 +66,16 @@ func (l *LightState) unmarshal(data []byte) error {
// LightSchedule allows the user to control when the Garden light is turned on and off
// "Time" should be in the format of LightTimeFormat constant ("15:04:05-07:00")
type LightSchedule struct {
Duration *Duration `json:"duration" yaml:"duration"`
StartTime *StartTime `json:"start_time" yaml:"start_time"`
AdhocOnTime *time.Time `json:"adhoc_on_time,omitempty" yaml:"adhoc_on_time,omitempty"`
NotificationClientID *string `json:"notification_client_id,omitempty" yaml:"notification_client_id,omitempty"`
Duration *Duration `json:"duration" yaml:"duration"`
StartTime *StartTime `json:"start_time" yaml:"start_time"`
AdhocOnTime *time.Time `json:"adhoc_on_time,omitempty" yaml:"adhoc_on_time,omitempty"`
}

// String...
func (ls *LightSchedule) String() string {
return fmt.Sprintf("%+v", *ls)
}

func (ls *LightSchedule) GetNotificationClientID() string {
if ls.NotificationClientID == nil {
return ""
}

return *ls.NotificationClientID
}

// Patch allows modifying the struct in-place with values from a different instance
func (ls *LightSchedule) Patch(new *LightSchedule) {
if new.Duration != nil {
Expand All @@ -96,7 +87,4 @@ func (ls *LightSchedule) Patch(new *LightSchedule) {
if new.AdhocOnTime == nil {
ls.AdhocOnTime = nil
}
if new.NotificationClientID != nil {
ls.NotificationClientID = new.NotificationClientID
}
}
2 changes: 1 addition & 1 deletion garden-app/pkg/storage/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import (

"github.com/calvinmclean/babyapi"
"github.com/calvinmclean/babyapi/storage/kv"
"github.com/mitchellh/mapstructure"
"github.com/tarmac-project/hord"
"github.com/tarmac-project/hord/drivers/hashmap"
"github.com/tarmac-project/hord/drivers/redis"
"github.com/mitchellh/mapstructure"
)

// Config is used to identify and configure a storage client
Expand Down
3 changes: 1 addition & 2 deletions garden-app/server/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/calvinmclean/automated-garden/garden-app/worker"
"github.com/calvinmclean/babyapi"
"github.com/calvinmclean/babyapi/html"
paho "github.com/eclipse/paho.mqtt.golang"
"github.com/prometheus/client_golang/prometheus/promhttp"
prommetrics "github.com/slok/go-http-metrics/metrics/prometheus"
metrics_middleware "github.com/slok/go-http-metrics/middleware"
Expand Down Expand Up @@ -86,7 +85,7 @@ func (api *API) Setup(cfg Config, validateData bool) error {
).Info("initializing MQTT client")
mqttClient, err := mqtt.NewClient(cfg.MQTTConfig, mqtt.DefaultHandler(logger), mqtt.TopicHandler{
Topic: "+/data/water",
Handler: paho.MessageHandler(NewMQTTHandler(storageClient, logger).Handle),
Handler: NewWaterNotificationHandler(storageClient, logger).HandleMessage,
})
if err != nil {
return fmt.Errorf("unable to initialize MQTT client: %v", err)
Expand Down
20 changes: 10 additions & 10 deletions garden-app/server/garden.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func NewGardenAPI() *GardensAPI {
}
}))

api.SetBeforeDelete(func(r *http.Request) *babyapi.ErrResponse {
api.SetBeforeDelete(func(_ http.ResponseWriter, r *http.Request) *babyapi.ErrResponse {
logger := babyapi.GetLoggerFromContext(r.Context())
gardenID := api.GetIDParam(r)

Expand All @@ -93,7 +93,7 @@ func NewGardenAPI() *GardensAPI {
return nil
})

api.SetAfterDelete(func(r *http.Request) *babyapi.ErrResponse {
api.SetAfterDelete(func(_ http.ResponseWriter, r *http.Request) *babyapi.ErrResponse {
logger := babyapi.GetLoggerFromContext(r.Context())
gardenID := api.GetIDParam(r)

Expand Down Expand Up @@ -153,7 +153,7 @@ func (api *GardensAPI) setup(config Config, storageClient *storage.Client, influ
return nil
}

func (api *GardensAPI) onCreateOrUpdate(r *http.Request, garden *pkg.Garden) *babyapi.ErrResponse {
func (api *GardensAPI) onCreateOrUpdate(_ http.ResponseWriter, r *http.Request, garden *pkg.Garden) *babyapi.ErrResponse {
logger := babyapi.GetLoggerFromContext(r.Context())

numZones, err := api.numZones(r.Context(), garden.ID.String())
Expand All @@ -173,15 +173,15 @@ func (api *GardensAPI) onCreateOrUpdate(r *http.Request, garden *pkg.Garden) *ba
}
}

if garden.LightSchedule != nil {
// Validate NotificationClient exists
if garden.LightSchedule.NotificationClientID != nil {
apiErr := checkNotificationClientExists(r.Context(), api.storageClient, *garden.LightSchedule.NotificationClientID)
if apiErr != nil {
return apiErr
}
// Validate NotificationClient exists
if garden.NotificationClientID != nil {
apiErr := checkNotificationClientExists(r.Context(), api.storageClient, *garden.NotificationClientID)
if apiErr != nil {
return apiErr
}
}

if garden.LightSchedule != nil {
// Update the light schedule for the Garden (if it exists)
logger.Info("updating/resetting LightSchedule for Garden")
if err := api.worker.ResetLightSchedule(garden); err != nil {
Expand Down
6 changes: 3 additions & 3 deletions garden-app/server/garden_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,16 +437,16 @@ func TestUpdateGarden(t *testing.T) {
"AddNotificationClientIDErrorNotFound",
createExampleGarden(),
nil,
`{"light_schedule":{"notification_client_id":"NOTIFICATION_CLIENT_ID"}}`,
`{"notification_client_id":"NOTIFICATION_CLIENT_ID"}`,
`{"status":"Invalid request.","error":"error getting NotificationClient with ID \\"NOTIFICATION_CLIENT_ID\\": resource not found"}`,
http.StatusBadRequest,
},
{
"AddNotificationClientIDSuccess",
createExampleGarden(),
nil,
`{"light_schedule":{"notification_client_id":"c5cvhpcbcv45e8bp16dg"}}`,
`{"name":"test-garden","topic_prefix":"test-garden","id":"[0-9a-v]{20}","max_zones":2,"created_at":"\d{4}-\d{2}-\d\dT\d\d:\d\d:\d\d\.\d+(-07:00|Z)","light_schedule":{"duration":"15h0m0s","start_time":"22:00:01-07:00","notification_client_id":"c5cvhpcbcv45e8bp16dg"},"next_light_action":{"time":"0000-12-31T17:00:00-07:00","state":"OFF"},"health":{"status":"UP","details":"last contact from Garden was \d+(s|ms) ago","last_contact":"\d{4}-\d{2}-\d\dT\d\d:\d\d:\d\d\.\d+(-07:00|Z)"},"num_zones":1,"links":\[{"rel":"self","href":"/gardens/[0-9a-v]{20}"},{"rel":"zones","href":"/gardens/c5cvhpcbcv45e8bp16dg/zones"},{"rel":"action","href":"/gardens/[0-9a-v]{20}/action"}\]}`,
`{"notification_client_id":"c5cvhpcbcv45e8bp16dg"}`,
`{"name":"test-garden","topic_prefix":"test-garden","id":"[0-9a-v]{20}","max_zones":2,"created_at":"\d{4}-\d{2}-\d\dT\d\d:\d\d:\d\d\.\d+(-07:00|Z)","light_schedule":{"duration":"15h0m0s","start_time":"22:00:01-07:00"},"notification_client_id":"c5cvhpcbcv45e8bp16dg","next_light_action":{"time":"0000-12-31T17:00:00-07:00","state":"OFF"},"health":{"status":"UP","details":"last contact from Garden was \d+(s|ms) ago","last_contact":"\d{4}-\d{2}-\d\dT\d\d:\d\d:\d\d\.\d+(-07:00|Z)"},"num_zones":1,"links":\[{"rel":"self","href":"/gardens/[0-9a-v]{20}"},{"rel":"zones","href":"/gardens/c5cvhpcbcv45e8bp16dg/zones"},{"rel":"action","href":"/gardens/[0-9a-v]{20}/action"}\]}`,
http.StatusOK,
},
{
Expand Down
91 changes: 0 additions & 91 deletions garden-app/server/mqtt_handler_test.go

This file was deleted.

2 changes: 1 addition & 1 deletion garden-app/server/notification_clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func NewNotificationClientsAPI() *NotificationClientsAPI {

api.API = babyapi.NewAPI[*notifications.Client]("NotificationClients", notificationClientsBasePath, func() *notifications.Client { return &notifications.Client{} })

api.SetOnCreateOrUpdate(func(_ *http.Request, nc *notifications.Client) *babyapi.ErrResponse {
api.SetOnCreateOrUpdate(func(_ http.ResponseWriter, _ *http.Request, nc *notifications.Client) *babyapi.ErrResponse {
// make sure a valid NotificationClient can still be created
err := nc.TestCreate()
if err != nil {
Expand Down
7 changes: 5 additions & 2 deletions garden-app/server/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,14 @@ func templateFuncs(r *http.Request) map[string]any {
return item.String() == target.String()
})
},
"CompareNotificationClientID": func(ncID babyapi.ID, parent interface {
"CompareNotificationClientID": func(ncID string, parent interface {
GetNotificationClientID() string
},
) bool {
return ncID.String() == parent.GetNotificationClientID()
if parent == nil {
return false
}
return ncID == parent.GetNotificationClientID()
},
"ZoneQuickWater": func(z *ZoneResponse) []string {
var waterDurations []string
Expand Down
Loading

0 comments on commit cce653c

Please sign in to comment.