diff --git a/README.md b/README.md index 471f9f4..41a98f7 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,33 @@ which yield the following: ```json { - "date": "Thursday, 2025/07/31", - "temperature": "29°C", - "min": "19°C", - "max": "29°C", - "condition": "Clear", - "feelsLike": "29°C", - "emoji": "☀️" + "date": "Friday, 2025/08/29", + "temperature": "18°C", + "min": "18°C", + "max": "24°C", + "condition": "Clouds", + "feelsLike": "18°C", + "emoji": "☁️", + "alerts": [ + { + "event": "Yellow Thunderstorm Warning", + "startDate": "Friday, 2025/08/29 2:00 AM", + "endDate": "Friday, 2025/08/29 11:59 PM", + "description": "Moderate intensity weather phenomena expected EASTERN ALPINE AND PRE-ALPINE SECTOR" + }, + { + "event": "Yellow Thunderstorm Warning", + "startDate": "Saturday, 2025/08/30 12:00 AM", + "endDate": "Saturday, 2025/08/30 5:59 AM", + "description": "Moderate intensity weather phenomena expected" + }, + { + "event": "Orange Thunderstorm Warning", + "startDate": "Friday, 2025/08/29 9:00 AM", + "endDate": "Friday, 2025/08/29 11:59 PM", + "description": "Severe weather expected" + } + ] } ``` @@ -57,13 +77,33 @@ which yields: ```json { - "date": "Thursday, 2025/07/31", - "temperature": "61°F", - "min": "51°F", - "max": "61°F", - "condition": "Clear", - "feelsLike": "61°F", - "emoji": "☀️" + "date": "Friday, 2025/08/29", + "temperature": "50°F", + "min": "50°F", + "max": "56°F", + "condition": "Clouds", + "feelsLike": "50°F", + "emoji": "☁️", + "alerts": [ + { + "event": "Yellow Thunderstorm Warning", + "startDate": "Friday, 2025/08/29 2:00 AM", + "endDate": "Friday, 2025/08/29 11:59 PM", + "description": "Moderate intensity weather phenomena expected EASTERN ALPINE AND PRE-ALPINE SECTOR" + }, + { + "event": "Yellow Thunderstorm Warning", + "startDate": "Saturday, 2025/08/30 12:00 AM", + "endDate": "Saturday, 2025/08/30 5:59 AM", + "description": "Moderate intensity weather phenomena expected" + }, + { + "event": "Orange Thunderstorm Warning", + "startDate": "Friday, 2025/08/29 9:00 AM", + "endDate": "Friday, 2025/08/29 11:59 PM", + "description": "Severe weather expected" + } + ] } ``` diff --git a/controller/controller.go b/controller/controller.go index fa754d8..70feeb2 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -112,6 +112,11 @@ func GetWeather(res http.ResponseWriter, req *http.Request, cache *types.Cache[t path := strings.TrimPrefix(req.URL.Path, "/weather/") cityName := strings.Trim(path, "/") // Remove trailing slash if present + if cityName == "" { + jsonError(res, "error", "specify city name", http.StatusMethodNotAllowed) + return + } + // Check whether the 'i' parameter(imperial mode) is specified isImperial := req.URL.Query().Has("i") @@ -165,6 +170,11 @@ func GetMetrics(res http.ResponseWriter, req *http.Request, cache *types.Cache[t path := strings.TrimPrefix(req.URL.Path, "/metrics/") cityName := strings.Trim(path, "/") // Remove trailing slash if present + if cityName == "" { + jsonError(res, "error", "specify city name", http.StatusMethodNotAllowed) + return + } + // Check whether the 'i' parameter(imperial mode) is specified isImperial := req.URL.Query().Has("i") @@ -215,6 +225,11 @@ func GetWind(res http.ResponseWriter, req *http.Request, cache *types.Cache[type path := strings.TrimPrefix(req.URL.Path, "/wind/") cityName := strings.Trim(path, "/") // Remove trailing slash if present + if cityName == "" { + jsonError(res, "error", "specify city name", http.StatusMethodNotAllowed) + return + } + // Check whether the 'i' parameter(imperial mode) is specified isImperial := req.URL.Query().Has("i") @@ -265,6 +280,11 @@ func GetForecast( path := strings.TrimPrefix(req.URL.Path, "/forecast/") cityName := strings.Trim(path, "/") // Remove trailing slash if present + if cityName == "" { + jsonError(res, "error", "specify city name", http.StatusMethodNotAllowed) + return + } + // Check whether the 'i' parameter(imperial mode) is specified isImperial := req.URL.Query().Has("i") @@ -360,6 +380,11 @@ func GetStatistics(res http.ResponseWriter, req *http.Request, statDB *types.Sta path := strings.TrimPrefix(req.URL.Path, "/stats/") cityName := strings.Trim(path, "/") // Remove trailing slash if present + if cityName == "" { + jsonError(res, "error", "specify city name", http.StatusMethodNotAllowed) + return + } + // Check whether the 'i' parameter(imperial mode) is specified isImperial := req.URL.Query().Has("i") diff --git a/model/weatherModel.go b/model/weatherModel.go index 101ba0f..a282132 100644 --- a/model/weatherModel.go +++ b/model/weatherModel.go @@ -53,7 +53,7 @@ func GetWeather(city *types.City, apiKey string) (types.Weather, error) { params.Set("lon", strconv.FormatFloat(city.Lon, 'f', -1, 64)) params.Set("appid", apiKey) params.Set("units", "metric") - params.Set("exclude", "minutely,hourly,alerts") + params.Set("exclude", "minutely,hourly") url.RawQuery = params.Encode() @@ -81,6 +81,12 @@ func GetWeather(city *types.City, apiKey string) (types.Weather, error) { Max float64 `json:"max"` } `json:"temp"` } `json:"daily"` + Alerts []struct { + Event string `json:"event"` + Start int64 `json:"start"` + End int64 `json:"end"` + Description string `json:"description"` + } `json:"alerts"` } var weather WeatherRes @@ -107,6 +113,27 @@ func GetWeather(city *types.City, apiKey string) (types.Weather, error) { isNight := strings.HasSuffix(weather.Current.Weather[0].Icon, "n") emoji := GetEmoji(condition, isNight) + // Format weather alerts + var alerts []types.WeatherAlert + for _, alert := range weather.Alerts { + // Format both start and end timestamp as 'YYYY-MM-DD' + utcStartDate := time.Unix(int64(alert.Start), 0) + startDate := types.ZephyrAlertDate{Date: utcStartDate} + + utcEndDate := time.Unix(int64(alert.End), 0) + endDate := types.ZephyrAlertDate{Date: utcEndDate} + + // Extract the first line of alert description + eventDescription := strings.Split(alert.Description, "\n")[0] + + alerts = append(alerts, types.WeatherAlert{ + Event: alert.Event, + Start: startDate, + End: endDate, + Description: eventDescription, + }) + } + return types.Weather{ Date: weatherDate, Temperature: strconv.FormatFloat(weather.Current.Temperature, 'f', -1, 64), @@ -115,5 +142,6 @@ func GetWeather(city *types.City, apiKey string) (types.Weather, error) { FeelsLike: strconv.FormatFloat(weather.Current.FeelsLike, 'f', -1, 64), Condition: weather.Current.Weather[0].Title, Emoji: emoji, + Alerts: alerts, }, nil } diff --git a/types/date.go b/types/date.go index 3965c22..63bd26e 100644 --- a/types/date.go +++ b/types/date.go @@ -62,3 +62,32 @@ func (t ZephyrTime) MarshalJSON() ([]byte, error) { return []byte("\"" + fmtTime + "\""), nil } + +type ZephyrAlertDate struct { + Date time.Time +} + +func (t *ZephyrAlertDate) UnmarshalJSON(b []byte) error { + s := strings.Trim(string(b), "\"") + if s == "" { + return nil + } + + var err error + t.Date, err = time.Parse("Monday, 2006/01/02 15:04", s) + if err != nil { + return err + } + + return nil +} + +func (t ZephyrAlertDate) MarshalJSON() ([]byte, error) { + if t.Date.IsZero() { + return []byte("\"\""), nil + } + + fmtTime := t.Date.Format("Monday, 2006/01/02 3:04 PM") + + return []byte("\"" + fmtTime + "\""), nil +} diff --git a/types/weather.go b/types/weather.go index 8be6f4e..8f59035 100644 --- a/types/weather.go +++ b/types/weather.go @@ -1,12 +1,22 @@ package types +// The WeatherAlert data type, representing a +// weather alert +type WeatherAlert struct { + Event string `json:"event"` + Start ZephyrAlertDate `json:"startDate"` + End ZephyrAlertDate `json:"endDate"` + Description string `json:"description"` +} + // The Weather data type, representing the weather of a certain location type Weather struct { - Date ZephyrDate `json:"date"` - Temperature string `json:"temperature"` - Min string `json:"min"` - Max string `json:"max"` - Condition string `json:"condition"` - FeelsLike string `json:"feelsLike"` - Emoji string `json:"emoji"` + Date ZephyrDate `json:"date"` + Temperature string `json:"temperature"` + Min string `json:"min"` + Max string `json:"max"` + Condition string `json:"condition"` + FeelsLike string `json:"feelsLike"` + Emoji string `json:"emoji"` + Alerts []WeatherAlert `json:"alerts"` }