diff --git a/garden-app/server/templates.go b/garden-app/server/templates.go
index b286b85c..df2e2471 100644
--- a/garden-app/server/templates.go
+++ b/garden-app/server/templates.go
@@ -31,6 +31,9 @@ const (
waterScheduleDetailModalTemplate html.Template = "WaterScheduleDetailModal"
zoneModalTemplate html.Template = "ZoneModal"
zoneActionModalTemplate html.Template = "ZoneActionModal"
+ weatherClientsPageTemplate html.Template = "WeatherClientsPage"
+ weatherClientsTemplate html.Template = "WeatherClients"
+ weatherClientModalTemplate html.Template = "WeatherClientModal"
)
func templateFuncs(r *http.Request) map[string]any {
diff --git a/garden-app/server/templates/weather_client_modal.html b/garden-app/server/templates/weather_client_modal.html
new file mode 100644
index 00000000..abbaf588
--- /dev/null
+++ b/garden-app/server/templates/weather_client_modal.html
@@ -0,0 +1,23 @@
+{{ define "WeatherClientModal" }}
+
+
+
{{ if .Type }}{{ .Type }}{{ else }}Create Weather Client{{ end }}
+
+
+
+
+{{ end }}
\ No newline at end of file
diff --git a/garden-app/server/templates/weather_clients.html b/garden-app/server/templates/weather_clients.html
new file mode 100644
index 00000000..849f9246
--- /dev/null
+++ b/garden-app/server/templates/weather_clients.html
@@ -0,0 +1,30 @@
+{{ define "WeatherClientsPage" }}
+{{ template "start" }}
+{{ template "WeatherClients" . }}
+{{ template "end" }}
+{{ end }}
+
+{{ define "WeatherClients" }}
+
+ {{ range .Items }}
+ {{ template "weatherClientCard" . }}
+ {{ end }}
+
+{{ end }}
+
+{{ define "weatherClientCard" }}
+
+{{ end }}
\ No newline at end of file
diff --git a/garden-app/server/weather_client_responses.go b/garden-app/server/weather_client_responses.go
new file mode 100644
index 00000000..286b139f
--- /dev/null
+++ b/garden-app/server/weather_client_responses.go
@@ -0,0 +1,64 @@
+package server
+
+import (
+ "fmt"
+ "net/http"
+ "slices"
+ "strings"
+
+ "github.com/calvinmclean/automated-garden/garden-app/pkg/weather"
+ "github.com/calvinmclean/babyapi"
+ "github.com/go-chi/render"
+)
+
+type WeatherClientTestResponse struct {
+ WeatherData
+}
+
+func (resp *WeatherClientTestResponse) Render(_ http.ResponseWriter, _ *http.Request) error {
+ return nil
+}
+
+type WeatherClientResponse struct {
+ *weather.Config
+
+ Links []Link `json:"links,omitempty"`
+}
+
+// Render ...
+func (resp *WeatherClientResponse) Render(w http.ResponseWriter, r *http.Request) error {
+ if resp != nil {
+ resp.Links = append(resp.Links,
+ Link{
+ "self",
+ fmt.Sprintf("%s/%s", weatherClientsBasePath, resp.ID),
+ },
+ )
+ }
+
+ if render.GetAcceptedContentType(r) == render.ContentTypeHTML && r.Method == http.MethodPut {
+ w.Header().Add("HX-Trigger", "newWeatherClient")
+ }
+
+ return nil
+}
+
+type AllWeatherClientsResponse struct {
+ babyapi.ResourceList[*weather.Config]
+}
+
+func (aws AllWeatherClientsResponse) Render(w http.ResponseWriter, r *http.Request) error {
+ return aws.ResourceList.Render(w, r)
+}
+
+func (aws AllWeatherClientsResponse) HTML(r *http.Request) string {
+ slices.SortFunc(aws.Items, func(w *weather.Config, x *weather.Config) int {
+ return strings.Compare(w.Type, x.Type)
+ })
+
+ if r.URL.Query().Get("refresh") == "true" {
+ return weatherClientsTemplate.Render(r, aws)
+ }
+
+ return weatherClientsPageTemplate.Render(r, aws)
+}
diff --git a/garden-app/server/weather_clients.go b/garden-app/server/weather_clients.go
index a4bc84b9..03eb4cdd 100644
--- a/garden-app/server/weather_clients.go
+++ b/garden-app/server/weather_clients.go
@@ -1,6 +1,7 @@
package server
import (
+ "context"
"fmt"
"net/http"
"time"
@@ -46,8 +47,31 @@ func NewWeatherClientsAPI(storageClient *storage.Client) (*WeatherClientsAPI, er
api.SetResponseWrapper(func(wc *weather.Config) render.Renderer {
return &WeatherClientResponse{Config: wc}
})
+ api.SetGetAllResponseWrapper(func(wcs []*weather.Config) render.Renderer {
+ return AllWeatherClientsResponse{ResourceList: babyapi.ResourceList[*weather.Config]{wcs}}
+ })
+
+ api.AddCustomIDRoute(http.MethodGet, "/test", babyapi.Handler(api.testWeatherClient))
- api.AddCustomIDRoute(http.MethodGet, "/test", http.HandlerFunc(api.testWeatherClient))
+ api.AddCustomRoute(http.MethodGet, "/components", babyapi.Handler(func(_ http.ResponseWriter, r *http.Request) render.Renderer {
+ switch r.URL.Query().Get("type") {
+ case "create_modal":
+ return weatherClientModalTemplate.Renderer(&weather.Config{
+ ID: babyapi.NewID(),
+ })
+ default:
+ return babyapi.ErrInvalidRequest(fmt.Errorf("invalid component: %s", r.URL.Query().Get("type")))
+ }
+ }))
+
+ api.AddCustomIDRoute(http.MethodGet, "/components", api.GetRequestedResourceAndDo(func(r *http.Request, wc *weather.Config) (render.Renderer, *babyapi.ErrResponse) {
+ switch r.URL.Query().Get("type") {
+ case "edit_modal":
+ return weatherClientModalTemplate.Renderer(wc), nil
+ default:
+ return nil, babyapi.ErrInvalidRequest(fmt.Errorf("invalid component: %s", r.URL.Query().Get("type")))
+ }
+ }))
api.SetBeforeDelete(func(r *http.Request) *babyapi.ErrResponse {
id := api.GetIDParam(r)
@@ -67,81 +91,50 @@ func NewWeatherClientsAPI(storageClient *storage.Client) (*WeatherClientsAPI, er
return api, nil
}
-func (api *WeatherClientsAPI) testWeatherClient(w http.ResponseWriter, r *http.Request) {
+func (api *WeatherClientsAPI) testWeatherClient(w http.ResponseWriter, r *http.Request) render.Renderer {
logger := babyapi.GetLoggerFromContext(r.Context())
logger.Info("received request to test WeatherClient")
weatherClient, httpErr := api.GetRequestedResource(r)
if httpErr != nil {
logger.Error("error getting requested resource", "error", httpErr.Error())
- render.Render(w, r, httpErr)
- return
+ return httpErr
}
+ weatherData, err := api.getWeatherData(r.Context(), weatherClient)
+ if err != nil {
+ logger.Error("unable to get weather data", "error", err)
+ return InternalServerError(err)
+ }
+
+ return &WeatherClientTestResponse{WeatherData: weatherData}
+}
+
+func (api *WeatherClientsAPI) getWeatherData(ctx context.Context, weatherClient *weather.Config) (WeatherData, error) {
wc, err := weather.NewClient(weatherClient, func(weatherClientOptions map[string]interface{}) error {
weatherClient.Options = weatherClientOptions
- return api.storageClient.WeatherClientConfigs.Set(r.Context(), weatherClient)
+ return api.storageClient.WeatherClientConfigs.Set(ctx, weatherClient)
})
if err != nil {
- logger.Error("unable to get WeatherClient", "error", err)
- render.Render(w, r, InternalServerError(err))
- return
+ return WeatherData{}, fmt.Errorf("error getting weather client: %w", err)
}
rd, err := wc.GetTotalRain(72 * time.Hour)
if err != nil {
- logger.Error("unable to get total rain in the last 72 hours", "error", err)
- render.Render(w, r, InternalServerError(err))
- return
+ return WeatherData{}, fmt.Errorf("unable to get total rain in the last 72 hours: %w", err)
}
td, err := wc.GetAverageHighTemperature(72 * time.Hour)
if err != nil {
- logger.Error("unable to get average high temperature in the last 72 hours", "error", err)
- render.Render(w, r, InternalServerError(err))
- return
+ return WeatherData{}, fmt.Errorf("unable to get average high temperature in the last 72 hours: %w", err)
}
- resp := &WeatherClientTestResponse{WeatherData: WeatherData{
+ return WeatherData{
Rain: &RainData{
MM: rd,
},
Temperature: &TemperatureData{
Celsius: td,
},
- }}
-
- if err := render.Render(w, r, resp); err != nil {
- logger.Error("unable to render WeatherClientResponse", "error", err)
- render.Render(w, r, ErrRender(err))
- }
-}
-
-// WeatherClientTestResponse is used to return WeatherData from testing that the client works
-type WeatherClientTestResponse struct {
- WeatherData
-}
-
-// Render ...
-func (resp *WeatherClientTestResponse) Render(_ http.ResponseWriter, _ *http.Request) error {
- return nil
-}
-
-type WeatherClientResponse struct {
- *weather.Config
-
- Links []Link `json:"links,omitempty"`
-}
-
-// Render ...
-func (resp *WeatherClientResponse) Render(_ http.ResponseWriter, _ *http.Request) error {
- if resp != nil {
- resp.Links = append(resp.Links,
- Link{
- "self",
- fmt.Sprintf("%s/%s", weatherClientsBasePath, resp.ID),
- },
- )
- }
- return nil
+ }, nil
}