package weather import ( "encoding/json" "fmt" "io" "net/http" "time" ) // WeatherData contains current weather conditions type WeatherData struct { Temperature float64 `json:"temp"` FeelsLike float64 `json:"feels_like"` Humidity int `json:"humidity"` Pressure int `json:"pressure"` WindSpeed float64 `json:"wind_speed"` WindGust float64 `json:"wind_gust"` WindDeg int `json:"wind_deg"` Clouds int `json:"clouds"` Description string `json:"description"` Icon string `json:"icon"` UpdatedAt string `json:"updated"` } // OpenWeatherMapResponse matches the API response type OpenWeatherMapResponse struct { Weather []struct { Description string `json:"description"` Icon string `json:"icon"` } `json:"weather"` Main struct { Temp float64 `json:"temp"` FeelsLike float64 `json:"feels_like"` Humidity int `json:"humidity"` Pressure int `json:"pressure"` } `json:"main"` Wind struct { Speed float64 `json:"speed"` Deg int `json:"deg"` Gust float64 `json:"gust"` } `json:"wind"` Clouds struct { All int `json:"all"` } `json:"clouds"` Dt int64 `json:"dt"` } type Client struct { apiKey string latitude float64 longitude float64 httpClient *http.Client lastUpdate time.Time cachedData *WeatherData } func New(apiKey string, latitude, longitude float64) *Client { return &Client{ apiKey: apiKey, latitude: latitude, longitude: longitude, httpClient: &http.Client{ Timeout: 10 * time.Second, }, } } // GetWeatherData fetches current weather from OpenWeatherMap // Data is cached for 10 minutes func (c *Client) GetWeatherData() (*WeatherData, error) { // Return cached data if less than 10 minutes old if c.cachedData != nil && time.Since(c.lastUpdate) < 10*time.Minute { return c.cachedData, nil } url := fmt.Sprintf( "https://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&appid=%s&units=metric", c.latitude, c.longitude, c.apiKey, ) resp, err := c.httpClient.Get(url) if err != nil { return nil, fmt.Errorf("failed to fetch weather data: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, string(body)) } body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } var owmData OpenWeatherMapResponse if err := json.Unmarshal(body, &owmData); err != nil { return nil, fmt.Errorf("failed to parse JSON: %w", err) } // Convert to our structure weatherData := &WeatherData{ Temperature: owmData.Main.Temp, FeelsLike: owmData.Main.FeelsLike, Humidity: owmData.Main.Humidity, Pressure: owmData.Main.Pressure, WindSpeed: owmData.Wind.Speed, WindGust: owmData.Wind.Gust, WindDeg: owmData.Wind.Deg, Clouds: owmData.Clouds.All, UpdatedAt: time.Unix(owmData.Dt, 0).Format(time.RFC3339), } if len(owmData.Weather) > 0 { weatherData.Description = owmData.Weather[0].Description weatherData.Icon = owmData.Weather[0].Icon } // Cache the data c.cachedData = weatherData c.lastUpdate = time.Now() return weatherData, nil }