@ -0,0 +1 @@ | |||||
weather-api |
@ -0,0 +1,56 @@ | |||||
package main | |||||
import ( | |||||
"fmt" | |||||
"net/http" | |||||
"github.com/google/go-querystring/query" | |||||
) | |||||
// DarkSky API endpoint | |||||
var ( | |||||
BaseURL = "https://api.darksky.net/forecast" | |||||
) | |||||
// DarkSky Api client | |||||
type DarkSky interface { | |||||
Forecast(request ForecastRequest) (ForecastResponse, error) | |||||
} | |||||
type darkSky struct { | |||||
APIKey string | |||||
Client *http.Client | |||||
} | |||||
// NewDarkSkyAPI creates a new DarkSky client | |||||
func NewDarkSkyAPI(apiKey string) DarkSky { | |||||
return &darkSky{apiKey, &http.Client{}} | |||||
} | |||||
// Forecast request a forecast | |||||
func (d *darkSky) Forecast(request ForecastRequest) (ForecastResponse, error) { | |||||
response := ForecastResponse{} | |||||
url := d.buildRequestURL(request) | |||||
err := get(d.Client, url, &response) | |||||
return response, err | |||||
} | |||||
func (d *darkSky) buildRequestURL(request ForecastRequest) string { | |||||
url := fmt.Sprintf("%s/%s/%f,%f", BaseURL, d.APIKey, request.Latitude, request.Longitude) | |||||
if request.Time > 0 { | |||||
url = url + fmt.Sprintf(",%d", request.Time) | |||||
} | |||||
values, _ := query.Values(request.Options) | |||||
queryString := values.Encode() | |||||
if len(queryString) > 0 { | |||||
url = url + "?" + queryString | |||||
} | |||||
return url | |||||
} |
@ -0,0 +1,60 @@ | |||||
package main | |||||
import ( | |||||
"log" | |||||
"net/http" | |||||
"os" | |||||
"strconv" | |||||
"github.com/fvbock/endless" | |||||
"github.com/gin-gonic/gin" | |||||
) | |||||
func main() { | |||||
apikey := os.Getenv("DARK_SKY_API_KEY") | |||||
if len(apikey) == 0 { | |||||
log.Fatalln("DARK_SKY_API_KEY environment variable must be set.") | |||||
} | |||||
router := gin.Default() | |||||
router.GET("/ping", func(c *gin.Context) { | |||||
c.String(http.StatusOK, "pong") | |||||
}) | |||||
router.GET("/current_weather/:lat/:long", func(c *gin.Context) { | |||||
lat, err := strconv.ParseFloat(c.Params.ByName("lat"), 64) | |||||
long, err := strconv.ParseFloat(c.Params.ByName("long"), 64) | |||||
if err != nil { | |||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Latitude and Longitude are required"}) | |||||
return | |||||
} | |||||
response, err := currentWeather(lat, long, apikey) | |||||
if err != nil { | |||||
c.JSON(http.StatusExpectationFailed, gin.H{"error": err.Error()}) | |||||
return | |||||
} | |||||
c.Header("Access-Control-Allow-Origin", "*") | |||||
c.JSON(http.StatusOK, gin.H{"weather": response}) | |||||
}) | |||||
err := endless.ListenAndServe("localhost:8080", router) | |||||
if err != nil { | |||||
log.Fatalf("Error: %s\n", err) | |||||
} | |||||
} | |||||
func currentWeather(lat, long float64, apikey string) (ForecastResponse, error) { | |||||
client := NewDarkSkyAPI(apikey) | |||||
request := ForecastRequest{} | |||||
request.Latitude = lat | |||||
request.Longitude = long | |||||
request.Options = ForecastRequestOptions{ | |||||
Exclude: "minutely", | |||||
Lang: "en", | |||||
Units: "us", | |||||
} | |||||
return client.Forecast(request) | |||||
} |
@ -0,0 +1,74 @@ | |||||
package main | |||||
import ( | |||||
"compress/gzip" | |||||
"encoding/json" | |||||
"errors" | |||||
"io" | |||||
"io/ioutil" | |||||
"net/http" | |||||
) | |||||
func get(client *http.Client, url string, output interface{}) error { | |||||
req, err := http.NewRequest("GET", url, nil) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
req.Header.Add("Content-Type", "application/json; charset=utf-8") | |||||
req.Header.Add("Accept-Encoding", "gzip") | |||||
response, err := client.Do(req) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
defer response.Body.Close() | |||||
err = checkErrors(response) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
body, err := decompress(response) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
return decodeJson(body, &output) | |||||
} | |||||
func checkErrors(response *http.Response) error { | |||||
if response.StatusCode != 200 { | |||||
body, _ := ioutil.ReadAll(response.Body) | |||||
return errors.New("Bad response: " + string(body)) | |||||
} | |||||
return nil | |||||
} | |||||
func decompress(response *http.Response) (io.Reader, error) { | |||||
header := response.Header.Get("Content-Encoding") | |||||
if len(header) < 1 { | |||||
return response.Body, nil | |||||
} | |||||
reader, err := gzip.NewReader(response.Body) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
return reader, nil | |||||
} | |||||
func decodeJson(body io.Reader, into interface{}) error { | |||||
jsonDecoder := json.NewDecoder(body) | |||||
return jsonDecoder.Decode(&into) | |||||
} |
@ -0,0 +1,81 @@ | |||||
package main | |||||
// Timestamp is an int64 timestamp | |||||
type Timestamp int64 | |||||
// ForecastRequest contains all available options for requesting a forecast | |||||
type ForecastRequest struct { | |||||
Latitude float64 | |||||
Longitude float64 | |||||
Time Timestamp | |||||
Options ForecastRequestOptions | |||||
} | |||||
// ForecastRequestOptions are optional and passed as query parameters | |||||
type ForecastRequestOptions struct { | |||||
Exclude string `url:"exclude,omitempty"` | |||||
Extend string `url:"extend,omitempty"` | |||||
Lang string `url:"lang,omitempty"` | |||||
Units string `url:"units,omitempty"` | |||||
} | |||||
// ForecastResponse is the response containing all requested properties | |||||
type ForecastResponse struct { | |||||
Latitude float64 `json:"latitude,omitempty"` | |||||
Longitude float64 `json:"longitude,omitempty"` | |||||
Timezone string `json:"timezone,omitempty"` | |||||
Currently *DataPoint `json:"currently,omitempty"` | |||||
Minutely *DataBlock `json:"minutely,omitempty"` | |||||
Hourly *DataBlock `json:"hourly,omitempty"` | |||||
Daily *DataBlock `json:"daily,omitempty"` | |||||
Alerts []*Alert `json:"alerts,omitempty"` | |||||
Flags *Flags `json:"flags,omitempty"` | |||||
} | |||||
// DataPoint contains various properties, each representing the average (unless otherwise specified) of a particular weather phenomenon occurring during a period of time. | |||||
type DataPoint struct { | |||||
ApparentTemperature float64 `json:"apparentTemperature,omitempty"` | |||||
ApparentTemperatureHigh float64 `json:"apparentTemperatureHigh,omitempty"` | |||||
ApparentTemperatureLow float64 `json:"apparentTemperatureLow,omitempty"` | |||||
Humidity float64 `json:"humidity,omitempty"` | |||||
Icon string `json:"icon"` | |||||
MoonPhase float64 `json:"moonPhase,omitempty"` | |||||
Summary string `json:"summary,omitempty"` | |||||
SunriseTime Timestamp `json:"sunriseTime,omitempty"` | |||||
SunsetTime Timestamp `json:"sunsetTime,omitempty"` | |||||
Temperature float64 `json:"temperature,omitempty"` | |||||
TemperatureHigh float64 `json:"temperatureHigh"` | |||||
TemperatureLow float64 `json:"temperatureLow"` | |||||
TemperatureMax float64 `json:"temperatureMax"` | |||||
TemperatureMin float64 `json:"temperatureMin"` | |||||
Time Timestamp `json:"time,omitempty"` | |||||
Visibility float64 `json:"visibility,omitempty"` | |||||
WindBearing float64 `json:"windBearing"` | |||||
WindGust float64 `json:"windGust"` | |||||
WindSpeed float64 `json:"windSpeed"` | |||||
} | |||||
// DataBlock represents the various weather phenomena occurring over a period of time | |||||
type DataBlock struct { | |||||
Summary string `json:"summary,omitempty"` | |||||
Icon string `json:"icon,omitempty"` | |||||
Data []DataPoint `json:"data,omitempty"` | |||||
} | |||||
// Alert contains objects representing the severe weather warnings issued for the requested location by a governmental authority | |||||
type Alert struct { | |||||
Title string `json:"title,omitempty"` | |||||
Severity string `json:"severity,omitempty"` | |||||
Description string `json:"description,omitempty"` | |||||
Expires Timestamp `json:"expires,omitempty"` | |||||
Regions []string `json:"regions,omitempty"` | |||||
Time Timestamp `json:"time,omitempty"` | |||||
URI string `json:"uri,omitempty"` | |||||
} | |||||
// Flags contains various metadata information related to the request | |||||
type Flags struct { | |||||
DarkSkyUnavailable string `json:"darksky-unavailable,omitempty"` | |||||
Sources []string `json:"sources,omitempty"` | |||||
Units string `json:"units,omitempty"` | |||||
} |