Browse Source

Initial working API

master
Levi Olson 5 years ago
commit
840c649b3e
5 changed files with 272 additions and 0 deletions
  1. +1
    -0
      .gitignore
  2. +56
    -0
      darksky.go
  3. +60
    -0
      main.go
  4. +74
    -0
      rest.go
  5. +81
    -0
      structs.go

+ 1
- 0
.gitignore View File

@ -0,0 +1 @@
weather-api

+ 56
- 0
darksky.go View File

@ -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
}

+ 60
- 0
main.go View File

@ -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)
}

+ 74
- 0
rest.go View File

@ -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)
}

+ 81
- 0
structs.go View File

@ -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"`
}

Loading…
Cancel
Save