diff --git a/compose.yml b/compose.yml index 97ffedd..2415f06 100644 --- a/compose.yml +++ b/compose.yml @@ -36,6 +36,7 @@ services: - LOG_LEVEL=debug - TITLE=DEV - APP_VERSION=v0.0.1-dev + - WEATHER_KEY=${WEATHER_KEY} volumes: - .:/app/ ports: diff --git a/handlers/app.handlers.go b/handlers/app.handlers.go index bd89ec4..e723274 100644 --- a/handlers/app.handlers.go +++ b/handlers/app.handlers.go @@ -16,11 +16,16 @@ type SystemService interface { GetStaticInformation() *services.StaticInformation } -func NewAppHandler(env *env.Config, s SystemService, b BookmarkService) *AppHandler { +type WeatherService interface { + GetCurrentWeather() *services.OpenWeather +} + +func NewAppHandler(env *env.Config, s SystemService, w WeatherService, b BookmarkService) *AppHandler { return &AppHandler{ env: env, SystemService: s, + WeatherService: w, BookmarkService: b, } } @@ -28,6 +33,7 @@ func NewAppHandler(env *env.Config, s SystemService, b BookmarkService) *AppHand type AppHandler struct { env *env.Config SystemService SystemService + WeatherService WeatherService BookmarkService BookmarkService } @@ -35,8 +41,9 @@ func (bh *AppHandler) appHandler(c echo.Context) error { bookmarks := bh.BookmarkService.GetAllBookmarks() staticSystem := bh.SystemService.GetStaticInformation() liveSystem := bh.SystemService.GetLiveInformation() + weather := bh.WeatherService.GetCurrentWeather() titlePage := bh.env.Title - return renderView(c, home.HomeIndex(titlePage, bh.env.Version, bookmarks, staticSystem, liveSystem, home.Home(titlePage, bookmarks, staticSystem, liveSystem))) + return renderView(c, home.HomeIndex(titlePage, bh.env.Version, bookmarks, staticSystem, liveSystem, weather, home.Home(titlePage, bookmarks, staticSystem, liveSystem, weather))) } diff --git a/internal/env/env.go b/internal/env/env.go index 425a265..9e34732 100644 --- a/internal/env/env.go +++ b/internal/env/env.go @@ -8,10 +8,16 @@ import ( ) type Config struct { - TimeZone string `env:"TZ" envDefault:"Etc/UTC" validate:"timezone"` - Title string `env:"TITLE" envDefault:"goDash"` - Port int `env:"PORT" envDefault:"4000" validate:"min=1024,max=49151"` - Version string `env:"APP_VERSION"` + TimeZone string `env:"TZ" envDefault:"Etc/UTC" validate:"timezone"` + Title string `env:"TITLE" envDefault:"goDash"` + Port int `env:"PORT" envDefault:"4000" validate:"min=1024,max=49151"` + Version string `env:"APP_VERSION"` + LocationLatitude float32 `env:"LOCATION_LATITUDE" envDefault:"48.780331609463815"` + LocationLongitude float32 `env:"LOCATION_LONGITUDE" envDefault:"9.177968320179422"` + WeatherKey string `env:"WEATHER_KEY"` + WeatherUnits string `env:"WEATHER_UNITS" envDefault:"metric"` + WeatherLanguage string `env:"WEATHER_LANG" envDefault:"en"` + WeatherDigits bool `env:"WEATHER_DIGITS" envDefault:"true"` } var errParse = errors.New("error parsing environment variables") diff --git a/main.go b/main.go index d0f9057..1a42913 100644 --- a/main.go +++ b/main.go @@ -42,9 +42,10 @@ func main() { sse.AutoReplay = false s := services.NewSystemService(sse) + w := services.NewWeatherService(sse, env) b := services.NewBookmarkService() - ah := handlers.NewAppHandler(env, s, b) + ah := handlers.NewAppHandler(env, s, w, b) handlers.SetupRoutes(e, sse, ah) slog.Info("starting server", "url", fmt.Sprintf("http://localhost:%d", env.Port)) diff --git a/services/weather.services.go b/services/weather.services.go new file mode 100644 index 0000000..69d6de3 --- /dev/null +++ b/services/weather.services.go @@ -0,0 +1,77 @@ +package services + +import ( + "encoding/json" + "fmt" + "io" + "log/slog" + "math" + "net/http" + "time" + + "github.com/r3labs/sse/v2" + "gitlab.unjx.de/flohoss/godash/internal/env" +) + +func NewWeatherService(sse *sse.Server, env *env.Config) *WeatherService { + var w = WeatherService{sse: sse, env: env} + w.setWeatherUnits() + sse.CreateStream("weather") + go w.updateWeather(time.Second * 90) + return &w +} + +func (w *WeatherService) GetCurrentWeather() *OpenWeather { + return &w.CurrentWeather +} + +func (w *WeatherService) setWeatherUnits() { + if w.env.WeatherUnits == "imperial" { + w.CurrentWeather.Units = "°F" + } else { + w.CurrentWeather.Units = "°C" + } +} + +func (w *WeatherService) copyWeatherValues(weatherResp *OpenWeatherApiResponse) { + myTime := time.Unix(weatherResp.Sys.Sunrise, 0) + w.CurrentWeather.Sunrise = myTime.Format("15:04") + myTime = time.Unix(weatherResp.Sys.Sunset, 0) + w.CurrentWeather.Sunset = myTime.Format("15:04") + w.CurrentWeather.Icon = weatherResp.Weather[0].Icon + if w.env.WeatherDigits { + w.CurrentWeather.Temp = weatherResp.Main.Temp + } else { + w.CurrentWeather.Temp = math.Round(weatherResp.Main.Temp) + } + w.CurrentWeather.Description = weatherResp.Weather[0].Description + w.CurrentWeather.Humidity = weatherResp.Main.Humidity +} + +func (w *WeatherService) updateWeather(interval time.Duration) { + var weatherResponse OpenWeatherApiResponse + for { + resp, err := http.Get(fmt.Sprintf("https://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&appid=%s&units=%s&lang=%s", + w.env.LocationLatitude, + w.env.LocationLongitude, + w.env.WeatherKey, + w.env.WeatherUnits, + w.env.WeatherLanguage)) + if err != nil || resp.StatusCode != 200 { + slog.Error("weather cannot be updated, please check WEATHER_KEY") + } else { + body, _ := io.ReadAll(resp.Body) + err = json.Unmarshal(body, &weatherResponse) + if err != nil { + slog.Error("weather cannot be processed") + } else { + w.copyWeatherValues(&weatherResponse) + slog.Info("weather updated", "temp", w.CurrentWeather.Temp) + } + resp.Body.Close() + json, _ := json.Marshal(w.CurrentWeather) + w.sse.Publish("weather", &sse.Event{Data: json}) + } + time.Sleep(interval) + } +} diff --git a/services/weather.types.go b/services/weather.types.go new file mode 100644 index 0000000..f26817b --- /dev/null +++ b/services/weather.types.go @@ -0,0 +1,43 @@ +package services + +import ( + "github.com/r3labs/sse/v2" + "gitlab.unjx.de/flohoss/godash/internal/env" +) + +type WeatherService struct { + CurrentWeather OpenWeather + sse *sse.Server + env *env.Config +} + +type OpenWeather struct { + Icon string `json:"icon"` + Temp float64 `json:"temp"` + Description string `json:"description"` + Humidity uint8 `json:"humidity"` + Sunrise string `json:"sunrise"` + Sunset string `json:"sunset"` + Units string `json:"units"` +} + +type OpenWeatherApiResponse struct { + Weather []OpenWeatherApiWeather `json:"Weather"` + Main OpenWeatherApiMain `json:"main"` + Sys OpenWeatherApiSys `json:"sys"` +} + +type OpenWeatherApiWeather struct { + Description string `json:"description"` + Icon string `json:"icon"` +} + +type OpenWeatherApiMain struct { + Temp float64 `json:"temp"` + Humidity uint8 `json:"humidity"` +} + +type OpenWeatherApiSys struct { + Sunrise int64 `json:"sunrise"` + Sunset int64 `json:"sunset"` +} diff --git a/views/home/home.templ b/views/home/home.templ index 084d550..7d25734 100644 --- a/views/home/home.templ +++ b/views/home/home.templ @@ -8,8 +8,61 @@ import "gitlab.unjx.de/flohoss/godash/components/application" import "gitlab.unjx.de/flohoss/godash/components/link" import "fmt" -templ Home(title string, bookmarks *services.Bookmarks, static *services.StaticInformation, live *services.LiveInformation) { -
+func getIcon(icon string) string { + switch icon { + case "01d": + return "icon-[bi--sun-fill]" + case "01n": + return "icon-[bi--moon-fill]" + case "02d": + return "icon-[bi--cloud-sun-fill]" + case "02n": + return "icon-[bi--cloud-moon-fill]" + case "03d", "03n": + return "icon-[bi--cloud-fill]" + case "04d", "04n": + return "icon-[bi--clouds-fill]" + case "09d", "09n": + return "icon-[bi--cloud-rain-heavy-fill]" + case "10d", "10n": + return "icon-[bi--cloud-drizzle-fill]" + case "11d", "11n": + return "icon-[bi--cloud-lightning-rain-fill]" + case "13d", "13n": + return "icon-[bi--cloud-snow-fill]" + case "50d", "50n": + return "icon-[bi--cloud-fog2-fill]" + default: + return "" + } +} + +templ Home(title string, bookmarks *services.Bookmarks, static *services.StaticInformation, live *services.LiveInformation, weather *services.OpenWeather) { +
+
+ +
+
{ fmt.Sprintf("%0.2f",weather.Temp) } { weather.Units }
+
+
+ +
{ weather.Description }
+
+
+ +
{ fmt.Sprintf("%d %%",weather.Humidity) }
+
+ + +
+
+
@system.System("icon-[bi--cpu]",static.CPU.Name,"",static.CPU.Threads,"systemCpuPercentage","",live.CPU) @system.System("icon-[bi--nvme]",live.Disk.Value,fmt.Sprintf(" | %s", static.Disk.Total),static.Disk.Partitions,"systemDiskPercentage","systemDiskValue",live.Disk.Percentage) @@ -45,14 +98,30 @@ templ Home(title string, bookmarks *services.Bookmarks, static *services.StaticI