diff --git a/controller/controller.go b/controller/controller.go index 5f95131..4310c48 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -49,7 +49,7 @@ func GetWeather(res http.ResponseWriter, req *http.Request, cache *types.Cache[t weather, found := cache.GetEntry(cityName, vars.TimeToLive) if found { - // Format weather values and then return it + // Format weather object and then return it weather.Temperature = fmtTemperature(weather.Temperature, isImperial) weather.FeelsLike = fmtTemperature(weather.FeelsLike, isImperial) @@ -72,10 +72,60 @@ func GetWeather(res http.ResponseWriter, req *http.Request, cache *types.Cache[t // Add result to cache cache.AddEntry(weather, cityName) - // Format weather values and then return it + // Format weather object and then return it weather.Temperature = fmtTemperature(weather.Temperature, isImperial) weather.FeelsLike = fmtTemperature(weather.FeelsLike, isImperial) jsonValue(res, weather) } } + +func GetMetrics(res http.ResponseWriter, req *http.Request, cache *types.Cache[types.Metrics], vars *types.Variables) { + if req.Method != http.MethodGet { + jsonError(res, "error", "method not allowed", http.StatusMethodNotAllowed) + return + } + + // Extract city name from '/metrics/:city' + path := strings.TrimPrefix(req.URL.Path, "/metrics/") + cityName := strings.Trim(path, "/") // Remove trailing slash if present + + // Check whether the 'i' parameter(imperial mode) is specified + isImperial := req.URL.Query().Has("i") + + metrics, found := cache.GetEntry(cityName, vars.TimeToLive) + if found { + // Format metrics object and then return it + metrics.Humidity = fmt.Sprintf("%s%%", metrics.Humidity) + metrics.Pressure = fmt.Sprintf("%s hPa", metrics.Pressure) + metrics.DewPoint = fmtTemperature(metrics.DewPoint, isImperial) + metrics.Visibility = fmt.Sprintf("%skm", metrics.Visibility) + + jsonValue(res, metrics) + } else { + // Get city coordinates + city, err := model.GetCoordinates(cityName, vars.Token) + if err != nil { + jsonError(res, "error", err.Error(), http.StatusBadRequest) + return + } + + // Get city weather + metrics, err := model.GetMetrics(&city, vars.Token) + if err != nil { + jsonError(res, "error", err.Error(), http.StatusBadRequest) + return + } + + // Add result to cache + cache.AddEntry(metrics, cityName) + + // Format metrics object and then return it + metrics.Humidity = fmt.Sprintf("%s%%", metrics.Humidity) + metrics.Pressure = fmt.Sprintf("%s hPa", metrics.Pressure) + metrics.DewPoint = fmtTemperature(metrics.DewPoint, isImperial) + metrics.Visibility = fmt.Sprintf("%skm", metrics.Visibility) + + jsonValue(res, metrics) + } +} diff --git a/main.go b/main.go index 05ded3e..77ca8b7 100644 --- a/main.go +++ b/main.go @@ -35,6 +35,10 @@ func main() { controller.GetWeather(res, req, &cache.WeatherCache, &vars) }) + http.HandleFunc("/metrics/", func(res http.ResponseWriter, req *http.Request) { + controller.GetMetrics(res, req, &cache.MetricsCache, &vars) + }) + listenAddr := fmt.Sprintf(":%s", port) log.Printf("Server listening on %s", listenAddr) http.ListenAndServe(listenAddr, nil) diff --git a/model/geoModel.go b/model/geoModel.go new file mode 100644 index 0000000..145a1a4 --- /dev/null +++ b/model/geoModel.go @@ -0,0 +1,45 @@ +package model + +import ( + "encoding/json" + "errors" + "net/http" + "net/url" + + "github.com/ceticamarco/zephyr/types" +) + +func GetCoordinates(cityName string, apiKey string) (types.City, error) { + url, err := url.Parse(GEO_URL) + if err != nil { + return types.City{}, err + } + + params := url.Query() + params.Set("q", cityName) + params.Set("limit", "1") + params.Set("appid", apiKey) + + url.RawQuery = params.Encode() + + res, err := http.Get(url.String()) + if err != nil { + return types.City{}, err + } + defer res.Body.Close() + + var geoArr []types.City + if err := json.NewDecoder(res.Body).Decode(&geoArr); err != nil { + return types.City{}, err + } + + if len(geoArr) == 0 { + return types.City{}, errors.New("Cannot find this city") + } + + return types.City{ + Name: geoArr[0].Name, + Lat: geoArr[0].Lat, + Lon: geoArr[0].Lon, + }, nil +} diff --git a/model/metricsModel.go b/model/metricsModel.go new file mode 100644 index 0000000..ce2afe2 --- /dev/null +++ b/model/metricsModel.go @@ -0,0 +1,59 @@ +package model + +import ( + "encoding/json" + "math" + "net/http" + "net/url" + "strconv" + + "github.com/ceticamarco/zephyr/types" +) + +func GetMetrics(city *types.City, apiKey string) (types.Metrics, error) { + url, err := url.Parse(WTR_URL) + if err != nil { + return types.Metrics{}, err + } + + params := url.Query() + params.Set("lat", strconv.FormatFloat(city.Lat, 'f', -1, 64)) + params.Set("lon", strconv.FormatFloat(city.Lon, 'f', -1, 64)) + params.Set("appid", apiKey) + params.Set("units", "metric") + params.Set("exclude", "minutely,hourly,daily,alerts") + + url.RawQuery = params.Encode() + + res, err := http.Get(url.String()) + if err != nil { + return types.Metrics{}, err + } + defer res.Body.Close() + + // Structures representing the JSON response + type CurrentRes struct { + Humidity int `json:"humidity"` + Pressure int `json:"pressure"` + DewPoint float64 `json:"dew_point"` + UvIndex float64 `json:"uvi"` + Visibility float64 `json:"visibility"` + } + + type MetricsRes struct { + Current CurrentRes `json:"current"` + } + + var metricRes MetricsRes + if err := json.NewDecoder(res.Body).Decode(&metricRes); err != nil { + return types.Metrics{}, err + } + + return types.Metrics{ + Humidity: strconv.Itoa(metricRes.Current.Humidity), + Pressure: strconv.Itoa(metricRes.Current.Pressure), + DewPoint: strconv.FormatFloat(metricRes.Current.DewPoint, 'f', -1, 64), + UvIndex: strconv.FormatFloat(math.Round(metricRes.Current.UvIndex), 'f', -1, 64), + Visibility: strconv.FormatFloat((metricRes.Current.Visibility / 1000), 'f', -1, 64), + }, nil +} diff --git a/model/urls.go b/model/urls.go new file mode 100644 index 0000000..b9a8a35 --- /dev/null +++ b/model/urls.go @@ -0,0 +1,6 @@ +package model + +const ( + GEO_URL = "https://api.openweathermap.org/geo/1.0/direct" + WTR_URL = "https://api.openweathermap.org/data/3.0/onecall" +) diff --git a/model/weatherModel.go b/model/weatherModel.go index 4cb80ce..efc7bdb 100644 --- a/model/weatherModel.go +++ b/model/weatherModel.go @@ -2,7 +2,6 @@ package model import ( "encoding/json" - "errors" "net/http" "net/url" "strconv" @@ -12,11 +11,6 @@ import ( "github.com/ceticamarco/zephyr/types" ) -const ( - GEO_URL = "https://api.openweathermap.org/geo/1.0/direct" - WTR_URL = "https://api.openweathermap.org/data/3.0/onecall" -) - func getEmoji(condition string, isNight bool) string { switch condition { case "Thunderstorm": @@ -27,8 +21,8 @@ func getEmoji(condition string, isNight bool) string { return "🌧️" case "Snow": return "☃️" - case "Mist", "Smoke", "Haze", "Dust", "Fog", "Sand", "Ash", "Squall": - return "🌫️" + case "Mist", "Smoke", "Haze", "Dust", "Fog", "Sand", "Ash", "Squall", "Clouds": + return "☁️" case "Tornado": return "🌪️" case "Clear": @@ -39,8 +33,6 @@ func getEmoji(condition string, isNight bool) string { return "☀️" } } - case "Clouds": - return "☁️" case "SunWithCloud": return "🌤️" case "CloudWithSun": @@ -50,41 +42,6 @@ func getEmoji(condition string, isNight bool) string { return "❓" } -func GetCoordinates(cityName string, apiKey string) (types.City, error) { - url, err := url.Parse(GEO_URL) - if err != nil { - return types.City{}, err - } - - params := url.Query() - params.Set("q", cityName) - params.Set("limit", "1") - params.Set("appid", apiKey) - - url.RawQuery = params.Encode() - - res, err := http.Get(url.String()) - if err != nil { - return types.City{}, err - } - defer res.Body.Close() - - var geoArr []types.City - if err := json.NewDecoder(res.Body).Decode(&geoArr); err != nil { - return types.City{}, err - } - - if len(geoArr) == 0 { - return types.City{}, errors.New("Cannot find this city") - } - - return types.City{ - Name: geoArr[0].Name, - Lat: geoArr[0].Lat, - Lon: geoArr[0].Lon, - }, nil -} - func GetWeather(city *types.City, apiKey string) (types.Weather, error) { url, err := url.Parse(WTR_URL) if err != nil { diff --git a/types/cache.go b/types/cache.go index 34bfb34..87ba732 100644 --- a/types/cache.go +++ b/types/cache.go @@ -54,13 +54,9 @@ func (cache *Cache[T]) GetEntry(key string, ttl int8) (T, bool) { func (cache *Cache[T]) AddEntry(entry T, cityName string) { currentTime := time.Now() - switch any(entry).(type) { - case Weather: - { - cache.Data[cityName] = CacheEntity[T]{ - Element: entry, - Timestamp: currentTime, - } - } + cache.Data[cityName] = CacheEntity[T]{ + Element: entry, + Timestamp: currentTime, } + } diff --git a/types/metrics.go b/types/metrics.go index a142f5b..f16406b 100644 --- a/types/metrics.go +++ b/types/metrics.go @@ -6,6 +6,6 @@ type Metrics struct { Humidity string `json:"humidity"` Pressure string `json:"pressure"` DewPoint string `json:"dewPoint"` - UvIndex int8 `json:"uvIndex"` + UvIndex string `json:"uvIndex"` Visibility string `json:"visibility"` }