fully working version
This commit is contained in:
commit
005169f973
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.idea
|
||||||
|
*.iml
|
60
src/client/CurrentWeatherStruct.go
Normal file
60
src/client/CurrentWeatherStruct.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
type WeatherResponse struct {
|
||||||
|
Coordinates Coordinates `json:"coord,omitempty"`
|
||||||
|
Base string `json:"base,omitempty"`
|
||||||
|
Main Main `json:"main,omitempty"`
|
||||||
|
Visibility int `json:"visibility,omitempty"`
|
||||||
|
Wind Wind `json:"wind,omitempty"`
|
||||||
|
Clouds Clouds `json:"clouds,omitempty"`
|
||||||
|
Rain Rain `json:"rain,omitempty"`
|
||||||
|
Snow Snow `json:"snow,omitempty"`
|
||||||
|
Date int `json:"dt,omitempty"`
|
||||||
|
System System `json:"sys,omitempty"`
|
||||||
|
Timezone int `json:"timezone,omitempty"`
|
||||||
|
Id int `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Cod int `json:"cod,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Coordinates struct {
|
||||||
|
Longitude float32 `json:"lon,omitempty"`
|
||||||
|
Latitude float32 `json:"lat,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Main struct {
|
||||||
|
Temperature float32 `json:"temp,omitempty"`
|
||||||
|
FeelsLike float32 `json:"feels_like,omitempty"`
|
||||||
|
TemperatureMin float32 `json:"temp_min,omitempty"`
|
||||||
|
TemperatureMax float32 `json:"temp_max,omitempty"`
|
||||||
|
Pressure int `json:"pressure,omitempty"`
|
||||||
|
Humidity int `json:"humidity,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Wind struct {
|
||||||
|
Speed float32 `json:"speed,omitempty"`
|
||||||
|
Degree int `json:"deg,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Clouds struct {
|
||||||
|
All int `json:"all,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rain struct {
|
||||||
|
OneHour int `json:",omitempty"`
|
||||||
|
ThreeHours int `json:"3h,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Snow struct {
|
||||||
|
OneHour int `json:"1h,omitempty"`
|
||||||
|
ThreeHours int `json:"3h,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type System struct {
|
||||||
|
Type int `json:",omitempty"`
|
||||||
|
Id int `json:"id,omitempty"`
|
||||||
|
Message float32 `json:"message,omitempty"`
|
||||||
|
Country string `json:"country,omitempty"`
|
||||||
|
Sunrise int `json:"sunrise,omitempty"`
|
||||||
|
Sunset int `json:"sunset,omitempty"`
|
||||||
|
}
|
44
src/client/WeatherClient.go
Normal file
44
src/client/WeatherClient.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"../config"
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetByCityName(city string) WeatherResponse {
|
||||||
|
return get("https://api.openweathermap.org/data/2.5/weather?q=" + city)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetByCoordinates(longitude, latitude float64) WeatherResponse {
|
||||||
|
return get("https://api.openweathermap.org/data/2.5/weather?lat=" + strconv.FormatFloat(latitude, 'f', 6, 32) + "&lon=" + strconv.FormatFloat(longitude, 'f', 6, 32))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetByZipCode(zip int, country string) WeatherResponse {
|
||||||
|
return get("https://api.openweathermap.org/data/2.5/weather?zip=" + string(zip) + "," + country)
|
||||||
|
}
|
||||||
|
|
||||||
|
func get(url string) WeatherResponse {
|
||||||
|
var configuration = config.LoadConfiguration()
|
||||||
|
|
||||||
|
response, err := http.Get(url + "&appid=" + configuration.ApiKey + "&lang=" + configuration.Language + "&units=" + configuration.Units)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData, err := ioutil.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var responseObject WeatherResponse
|
||||||
|
err = json.Unmarshal(responseData, &responseObject)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseObject
|
||||||
|
}
|
50
src/config/Configuration.go
Normal file
50
src/config/Configuration.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Configuration struct {
|
||||||
|
ApiKey string
|
||||||
|
Language string
|
||||||
|
Units string
|
||||||
|
|
||||||
|
Port int
|
||||||
|
JwtSecret string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config Configuration) String() string {
|
||||||
|
return fmt.Sprintf("api_key=%s language=%s, units=%s, port=%d, jwt_secret=%s", config.ApiKey, config.Language, config.Units, config.Port, config.JwtSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
//returns a new Config struct
|
||||||
|
func LoadConfiguration() *Configuration {
|
||||||
|
return &Configuration{
|
||||||
|
ApiKey: getEnv("API_KEY", ""),
|
||||||
|
Language: getEnv("LANGUAGE", "en"),
|
||||||
|
Units: getEnv("UNITS", "metric"),
|
||||||
|
Port: getEnvAsInt("PORT", 8080),
|
||||||
|
JwtSecret: getEnv("JWT_SECRET", "super secret value"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple helper function to read an environment or return a default value
|
||||||
|
func getEnv(key string, defaultVal string) string {
|
||||||
|
if value, exists := os.LookupEnv(key); exists {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple helper function to read an environment variable into integer or return a default value
|
||||||
|
func getEnvAsInt(name string, defaultVal int) int {
|
||||||
|
valueStr := getEnv(name, "")
|
||||||
|
if value, err := strconv.Atoi(valueStr); err == nil {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultVal
|
||||||
|
}
|
9
src/main.go
Normal file
9
src/main.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"./server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
server.StartServer()
|
||||||
|
}
|
133
src/server/WeatherServer.go
Normal file
133
src/server/WeatherServer.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"../client"
|
||||||
|
"../config"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/auth0/go-jwt-middleware"
|
||||||
|
"github.com/dgrijalva/jwt-go"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/justinas/alice"
|
||||||
|
"github.com/justinas/nosurf"
|
||||||
|
"github.com/throttled/throttled"
|
||||||
|
"github.com/throttled/throttled/store/memstore"
|
||||||
|
cache "github.com/victorspringer/http-cache"
|
||||||
|
"github.com/victorspringer/http-cache/adapter/memory"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StartServer() {
|
||||||
|
conf := config.LoadConfiguration()
|
||||||
|
port := strconv.Itoa(conf.Port)
|
||||||
|
log.Println("starting server on port " + port)
|
||||||
|
|
||||||
|
store, err := memstore.New(65536)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
quota := throttled.RateQuota{
|
||||||
|
MaxRate: throttled.PerMin(20), MaxBurst: 30,
|
||||||
|
}
|
||||||
|
rateLimiter, err := throttled.NewGCRARateLimiter(store, quota)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpRateLimiter := throttled.HTTPRateLimiter{
|
||||||
|
RateLimiter: rateLimiter,
|
||||||
|
VaryBy: &throttled.VaryBy{Path: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
memcached, err := memory.NewAdapter(
|
||||||
|
memory.AdapterWithAlgorithm(memory.LRU),
|
||||||
|
memory.AdapterWithCapacity(10000000),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheClient, err := cache.NewClient(
|
||||||
|
cache.ClientWithAdapter(memcached),
|
||||||
|
cache.ClientWithTTL(10*time.Minute),
|
||||||
|
cache.ClientWithRefreshKey("opn"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
jwtAuth := jwtmiddleware.New(jwtmiddleware.Options{
|
||||||
|
ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
|
||||||
|
return []byte(conf.JwtSecret), nil
|
||||||
|
},
|
||||||
|
SigningMethod: jwt.SigningMethodHS512,
|
||||||
|
})
|
||||||
|
|
||||||
|
chain := alice.New(jwtAuth.Handler, timeoutHandler, httpRateLimiter.RateLimit, nosurf.NewPure, cacheClient.Middleware)
|
||||||
|
|
||||||
|
router := mux.NewRouter()
|
||||||
|
router.HandleFunc("/city/{city}", chain.ThenFunc(cityHandler).ServeHTTP)
|
||||||
|
router.HandleFunc("/coordinates/{latitude},{longitude}", chain.ThenFunc(coordinatesHandler).ServeHTTP)
|
||||||
|
router.HandleFunc("/zip/{zip},{country}", chain.ThenFunc(zipCodeHandler).ServeHTTP)
|
||||||
|
_ = http.ListenAndServe(":"+port, router)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cityHandler(writer http.ResponseWriter, req *http.Request) {
|
||||||
|
writer.Header().Set("Content-Type", "application/json")
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
data := client.GetByCityName(vars["city"])
|
||||||
|
writer.WriteHeader(http.StatusOK)
|
||||||
|
_ = json.NewEncoder(writer).Encode(convert(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func coordinatesHandler(writer http.ResponseWriter, req *http.Request) {
|
||||||
|
writer.Header().Set("Content-Type", "application/json")
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
|
||||||
|
longitude, err := strconv.ParseFloat(vars["longitude"], 32)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
latitude, err := strconv.ParseFloat(vars["latitude"], 32)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
data := client.GetByCoordinates(longitude, latitude)
|
||||||
|
writer.WriteHeader(http.StatusOK)
|
||||||
|
_ = json.NewEncoder(writer).Encode(convert(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func zipCodeHandler(writer http.ResponseWriter, req *http.Request) {
|
||||||
|
writer.Header().Set("Content-Type", "application/json")
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
zip, err := strconv.Atoi(vars["zip"])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
data := client.GetByZipCode(zip, vars["country"])
|
||||||
|
writer.WriteHeader(http.StatusOK)
|
||||||
|
_ = json.NewEncoder(writer).Encode(convert(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeoutHandler(h http.Handler) http.Handler {
|
||||||
|
return http.TimeoutHandler(h, 1*time.Second, "timed out")
|
||||||
|
}
|
||||||
|
|
||||||
|
func convert(weatherData client.WeatherResponse) Weather {
|
||||||
|
return Weather{
|
||||||
|
TemperatureCurrent: weatherData.Main.Temperature,
|
||||||
|
TemperatureMin: weatherData.Main.TemperatureMin,
|
||||||
|
TemperatureMax: weatherData.Main.TemperatureMax,
|
||||||
|
FeelsLike: weatherData.Main.FeelsLike,
|
||||||
|
Pressure: weatherData.Main.Pressure,
|
||||||
|
Humidity: weatherData.Main.Humidity,
|
||||||
|
WindSpeeed: weatherData.Wind.Speed,
|
||||||
|
WindDegree: weatherData.Wind.Degree,
|
||||||
|
Clouds: weatherData.Clouds.All,
|
||||||
|
Rain: weatherData.Rain.OneHour,
|
||||||
|
Snow: weatherData.Snow.OneHour,
|
||||||
|
}
|
||||||
|
}
|
18
src/server/WeatherStruct.go
Normal file
18
src/server/WeatherStruct.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
type Weather struct {
|
||||||
|
TemperatureCurrent float32 `json:"temperature_current"`
|
||||||
|
FeelsLike float32 `json:"feels_like"`
|
||||||
|
TemperatureMin float32 `json:"temperature_min"`
|
||||||
|
TemperatureMax float32 `json:"temperature_max"`
|
||||||
|
Pressure int `json:"pressure"`
|
||||||
|
Humidity int `json:"humidity"`
|
||||||
|
|
||||||
|
WindSpeeed float32 `json:"wind_speed"`
|
||||||
|
WindDegree int `json:"wind_degree"`
|
||||||
|
|
||||||
|
Clouds int `json:"clouds"`
|
||||||
|
|
||||||
|
Rain int `json:"rain"`
|
||||||
|
Snow int `json:"snow"`
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user