Added moon route and changed Date fields from pointers to values
This commit is contained in:
@@ -259,3 +259,33 @@ func GetForecast(res http.ResponseWriter, req *http.Request, cache *types.Cache[
|
|||||||
jsonValue(res, forecast)
|
jsonValue(res, forecast)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetMoon(res http.ResponseWriter, req *http.Request, cache *types.CacheEntity[types.Moon], vars *types.Variables) {
|
||||||
|
if req.Method != http.MethodGet {
|
||||||
|
jsonError(res, "error", "method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedValue, found := cache.GetEntry(vars.TimeToLive)
|
||||||
|
if found {
|
||||||
|
// Format moon object and then return it
|
||||||
|
cachedValue.Percentage = fmt.Sprintf("%s%%", cachedValue.Percentage)
|
||||||
|
|
||||||
|
jsonValue(res, cachedValue)
|
||||||
|
} else {
|
||||||
|
// Get moon data
|
||||||
|
moon, err := model.GetMoon(vars.Token)
|
||||||
|
if err != nil {
|
||||||
|
jsonError(res, "error", err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add result to cache
|
||||||
|
cache.AddEntry(moon)
|
||||||
|
|
||||||
|
// Format moon object and then return it
|
||||||
|
moon.Percentage = fmt.Sprintf("%s%%", moon.Percentage)
|
||||||
|
|
||||||
|
jsonValue(res, moon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
4
main.go
4
main.go
@@ -47,6 +47,10 @@ func main() {
|
|||||||
controller.GetForecast(res, req, &cache.ForecastCache, &vars)
|
controller.GetForecast(res, req, &cache.ForecastCache, &vars)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
http.HandleFunc("/moon", func(res http.ResponseWriter, req *http.Request) {
|
||||||
|
controller.GetMoon(res, req, &cache.MoonCache, &vars)
|
||||||
|
})
|
||||||
|
|
||||||
listenAddr := fmt.Sprintf(":%s", port)
|
listenAddr := fmt.Sprintf(":%s", port)
|
||||||
log.Printf("Server listening on %s", listenAddr)
|
log.Printf("Server listening on %s", listenAddr)
|
||||||
http.ListenAndServe(listenAddr, nil)
|
http.ListenAndServe(listenAddr, nil)
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ type forecastRes struct {
|
|||||||
func getForecastEntity(dailyForecast dailyRes) types.ForecastEntity {
|
func getForecastEntity(dailyForecast dailyRes) types.ForecastEntity {
|
||||||
// Format UNIX timestamp as 'YYYY-MM-DD'
|
// Format UNIX timestamp as 'YYYY-MM-DD'
|
||||||
utcTime := time.Unix(int64(dailyForecast.Timestamp), 0)
|
utcTime := time.Unix(int64(dailyForecast.Timestamp), 0)
|
||||||
weatherDate := &types.ZephyrDate{Date: utcTime.UTC()}
|
weatherDate := types.ZephyrDate{Date: utcTime.UTC()}
|
||||||
|
|
||||||
// Set condition accordingly to weather description
|
// Set condition accordingly to weather description
|
||||||
var condition string
|
var condition string
|
||||||
|
|||||||
90
model/moonModel.go
Normal file
90
model/moonModel.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/ceticamarco/zephyr/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getMoonPhase(moonValue float64) (string, string) {
|
||||||
|
// 0 and 1 are 'new moon',
|
||||||
|
// 0.25 is 'first quarter moon',
|
||||||
|
// 0.5 is 'full moon' and 0.75 is 'last quarter moon'.
|
||||||
|
// The periods in between are called 'waxing crescent',
|
||||||
|
// 'waxing gibbous', 'waning gibbous' and 'waning crescent', respectively.
|
||||||
|
switch {
|
||||||
|
case moonValue == 0, moonValue == 1:
|
||||||
|
return "🌑", "New Moon"
|
||||||
|
case moonValue > 0 && moonValue < 0.25:
|
||||||
|
return "🌒", "Waxing Crescent"
|
||||||
|
case moonValue == 0.25:
|
||||||
|
return "🌓", "First Quarter"
|
||||||
|
case moonValue > 0.25 && moonValue < 0.5:
|
||||||
|
return "🌔", "Waxing Gibbous"
|
||||||
|
case moonValue == 0.5:
|
||||||
|
return "🌕", "Full Moon"
|
||||||
|
case moonValue > 0.5 && moonValue < 0.75:
|
||||||
|
return "🌖", "Waning Gibbous"
|
||||||
|
case moonValue == 0.75:
|
||||||
|
return "🌗", "Last Quarter"
|
||||||
|
case moonValue > 0.75 && moonValue < 1:
|
||||||
|
return "🌘", "Waning Crescent"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "❓", "Unknown moon phase"
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMoon(apiKey string) (types.Moon, error) {
|
||||||
|
url, err := url.Parse(WTR_URL)
|
||||||
|
if err != nil {
|
||||||
|
return types.Moon{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
params := url.Query()
|
||||||
|
params.Set("lat", "41.8933203") // Rome latitude
|
||||||
|
params.Set("lon", "12.4829321") // Rome longitude
|
||||||
|
params.Set("appid", apiKey)
|
||||||
|
params.Set("units", "metric")
|
||||||
|
params.Set("exclude", "current,hourly,alerts")
|
||||||
|
|
||||||
|
url.RawQuery = params.Encode()
|
||||||
|
|
||||||
|
res, err := http.Get(url.String())
|
||||||
|
if err != nil {
|
||||||
|
return types.Moon{}, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
// Structure representing the JSON response
|
||||||
|
type MoonRes struct {
|
||||||
|
Daily []struct {
|
||||||
|
Value float64 `json:"moon_phase"`
|
||||||
|
} `json:"daily"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var moonRes MoonRes
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(&moonRes); err != nil {
|
||||||
|
return types.Moon{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve moon icon and moon phase(description) from moon phase(value)
|
||||||
|
icon, phase := getMoonPhase(moonRes.Daily[0].Value)
|
||||||
|
|
||||||
|
getMoonPercentage := func(moonVal float64) int {
|
||||||
|
// Approximate moon illumination percentage using moon phase
|
||||||
|
// by computing \sin(\pi * moonValue)^2
|
||||||
|
res := math.Pow(math.Sin(math.Pi*moonVal), 2)
|
||||||
|
|
||||||
|
return int(math.Round(res * 100))
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.Moon{
|
||||||
|
Icon: icon,
|
||||||
|
Phase: phase,
|
||||||
|
Percentage: strconv.Itoa(getMoonPercentage(moonRes.Daily[0].Value)),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -86,7 +86,7 @@ func GetWeather(city *types.City, apiKey string) (types.Weather, error) {
|
|||||||
|
|
||||||
// Format UNIX timestamp as 'YYYY-MM-DD'
|
// Format UNIX timestamp as 'YYYY-MM-DD'
|
||||||
utcTime := time.Unix(int64(weather.Current.Timestamp), 0)
|
utcTime := time.Unix(int64(weather.Current.Timestamp), 0)
|
||||||
weatherDate := &types.ZephyrDate{Date: utcTime.UTC()}
|
weatherDate := types.ZephyrDate{Date: utcTime.UTC()}
|
||||||
|
|
||||||
// Set condition accordingly to weather description
|
// Set condition accordingly to weather description
|
||||||
var condition string
|
var condition string
|
||||||
|
|||||||
@@ -4,19 +4,19 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CacheType, representing the abstract value of a CacheEntity
|
// cacheType, representing the abstract value of a CacheEntity
|
||||||
type CacheType interface {
|
type cacheType interface {
|
||||||
Weather | Metrics | Wind | Forecast
|
Weather | Metrics | Wind | Forecast | Moon
|
||||||
}
|
}
|
||||||
|
|
||||||
// CacheEntity, representing the value of the cache
|
// CacheEntity, representing the value of the cache
|
||||||
type CacheEntity[T CacheType] struct {
|
type CacheEntity[T cacheType] struct {
|
||||||
Element T
|
element T
|
||||||
Timestamp time.Time
|
timestamp time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache, representing a mapping between a key(str) and a CacheEntity
|
// Cache, representing a mapping between a key(str) and a CacheEntity
|
||||||
type Cache[T CacheType] struct {
|
type Cache[T cacheType] struct {
|
||||||
Data map[string]CacheEntity[T]
|
Data map[string]CacheEntity[T]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +26,7 @@ type Caches struct {
|
|||||||
MetricsCache Cache[Metrics]
|
MetricsCache Cache[Metrics]
|
||||||
WindCache Cache[Wind]
|
WindCache Cache[Wind]
|
||||||
ForecastCache Cache[Forecast]
|
ForecastCache Cache[Forecast]
|
||||||
|
MoonCache CacheEntity[Moon]
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitCache() *Caches {
|
func InitCache() *Caches {
|
||||||
@@ -34,6 +35,7 @@ func InitCache() *Caches {
|
|||||||
MetricsCache: Cache[Metrics]{Data: make(map[string]CacheEntity[Metrics])},
|
MetricsCache: Cache[Metrics]{Data: make(map[string]CacheEntity[Metrics])},
|
||||||
WindCache: Cache[Wind]{Data: make(map[string]CacheEntity[Wind])},
|
WindCache: Cache[Wind]{Data: make(map[string]CacheEntity[Wind])},
|
||||||
ForecastCache: Cache[Forecast]{Data: make(map[string]CacheEntity[Forecast])},
|
ForecastCache: Cache[Forecast]{Data: make(map[string]CacheEntity[Forecast])},
|
||||||
|
MoonCache: CacheEntity[Moon]{element: Moon{}, timestamp: time.Time{}},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,25 +44,50 @@ func (cache *Cache[T]) GetEntry(key string, ttl int8) (T, bool) {
|
|||||||
|
|
||||||
// If key is not present, return a zero value
|
// If key is not present, return a zero value
|
||||||
if !isPresent {
|
if !isPresent {
|
||||||
return val.Element, false
|
return val.element, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise check whether cache element is expired
|
// Otherwise check whether cache element is expired
|
||||||
currentTime := time.Now()
|
currentTime := time.Now()
|
||||||
expired := currentTime.Sub(val.Timestamp) > (time.Duration(ttl) * time.Hour)
|
expired := currentTime.Sub(val.timestamp) > (time.Duration(ttl) * time.Hour)
|
||||||
if expired {
|
if expired {
|
||||||
return val.Element, false
|
return val.element, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return val.Element, true
|
return val.element, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cache *Cache[T]) AddEntry(entry T, cityName string) {
|
func (cache *Cache[T]) AddEntry(entry T, cityName string) {
|
||||||
currentTime := time.Now()
|
currentTime := time.Now()
|
||||||
|
|
||||||
cache.Data[cityName] = CacheEntity[T]{
|
cache.Data[cityName] = CacheEntity[T]{
|
||||||
Element: entry,
|
element: entry,
|
||||||
Timestamp: currentTime,
|
timestamp: currentTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (moon *CacheEntity[Moon]) GetEntry(ttl int8) (Moon, bool) {
|
||||||
|
var zeroMoon Moon
|
||||||
|
|
||||||
|
// If moon data is not present, return a zero value
|
||||||
|
if moon == nil {
|
||||||
|
return zeroMoon, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise check whether the element is expired
|
||||||
|
currentTime := time.Now()
|
||||||
|
expired := currentTime.Sub(moon.timestamp) > (time.Duration(ttl) * time.Hour)
|
||||||
|
if expired {
|
||||||
|
return zeroMoon, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return moon.element, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cache *CacheEntity[Moon]) AddEntry(entry Moon) {
|
||||||
|
currentTime := time.Now()
|
||||||
|
|
||||||
|
cache.element = entry
|
||||||
|
cache.timestamp = currentTime
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func (date *ZephyrDate) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (date *ZephyrDate) MarshalJSON() ([]byte, error) {
|
func (date ZephyrDate) MarshalJSON() ([]byte, error) {
|
||||||
if date.Date.IsZero() {
|
if date.Date.IsZero() {
|
||||||
return []byte("\"\""), nil
|
return []byte("\"\""), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ package types
|
|||||||
// The ForecastEntity data type, representing the weather forecast
|
// The ForecastEntity data type, representing the weather forecast
|
||||||
// of a single day
|
// of a single day
|
||||||
type ForecastEntity struct {
|
type ForecastEntity struct {
|
||||||
Date *ZephyrDate `json:"date"`
|
Date ZephyrDate `json:"date"`
|
||||||
Min string `json:"min"`
|
Min string `json:"min"`
|
||||||
Max string `json:"max"`
|
Max string `json:"max"`
|
||||||
Condition string `json:"condition"`
|
Condition string `json:"condition"`
|
||||||
Emoji string `json:"emoji"`
|
Emoji string `json:"emoji"`
|
||||||
FeelsLike string `json:"feelsLike"`
|
FeelsLike string `json:"feelsLike"`
|
||||||
Wind Wind `json:"wind"`
|
Wind Wind `json:"wind"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Forecast data type, representing the an set
|
// The Forecast data type, representing the an set
|
||||||
|
|||||||
9
types/moon.go
Normal file
9
types/moon.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
// The Moon data type, representing the moon phase,
|
||||||
|
// the moon phase icon and the moon progress(%).
|
||||||
|
type Moon struct {
|
||||||
|
Icon string `json:"icon"`
|
||||||
|
Phase string `json:"phase"`
|
||||||
|
Percentage string `json:"percentage"`
|
||||||
|
}
|
||||||
@@ -2,9 +2,9 @@ package types
|
|||||||
|
|
||||||
// The Weather data type, representing the weather of a certain location
|
// The Weather data type, representing the weather of a certain location
|
||||||
type Weather struct {
|
type Weather struct {
|
||||||
Date *ZephyrDate `json:"date"`
|
Date ZephyrDate `json:"date"`
|
||||||
Temperature string `json:"temperature"`
|
Temperature string `json:"temperature"`
|
||||||
Condition string `json:"condition"`
|
Condition string `json:"condition"`
|
||||||
FeelsLike string `json:"feelsLike"`
|
FeelsLike string `json:"feelsLike"`
|
||||||
Emoji string `json:"emoji"`
|
Emoji string `json:"emoji"`
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user