implement openweather main
This commit is contained in:
1
.env.template
Normal file
1
.env.template
Normal file
@@ -0,0 +1 @@
|
||||
OPENWEATHER_API_KEY=
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/vendor/
|
||||
/seven-skies
|
||||
/.vscode/launch.json
|
||||
/.env
|
||||
107
cmd/openweather/main.go
Normal file
107
cmd/openweather/main.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-co-op/gocron/v2"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/neatflowcv/seven-skies/internal/pkg/openweather"
|
||||
)
|
||||
|
||||
func version() string {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
return info.Main.Version
|
||||
}
|
||||
|
||||
func main() {
|
||||
godotenv.Load()
|
||||
|
||||
key := os.Getenv("OPENWEATHER_API_KEY")
|
||||
if key == "" {
|
||||
log.Panic("OPENWEATHER_API_KEY is not set")
|
||||
}
|
||||
|
||||
lat := os.Getenv("OPENWEATHER_LAT")
|
||||
if lat == "" {
|
||||
log.Panic("OPENWEATHER_LAT is not set")
|
||||
}
|
||||
|
||||
lon := os.Getenv("OPENWEATHER_LON")
|
||||
if lon == "" {
|
||||
log.Panic("OPENWEATHER_LON is not set")
|
||||
}
|
||||
|
||||
log.Println("version", version())
|
||||
|
||||
scheduler, err := gocron.NewScheduler()
|
||||
if err != nil {
|
||||
log.Panic("error in create scheduler", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := scheduler.Shutdown()
|
||||
if err != nil {
|
||||
log.Println("error in shutdown scheduler", err)
|
||||
}
|
||||
}()
|
||||
|
||||
latFloat, err := strconv.ParseFloat(lat, 64)
|
||||
if err != nil {
|
||||
log.Panic("error in parse lat", err)
|
||||
}
|
||||
|
||||
lonFloat, err := strconv.ParseFloat(lon, 64)
|
||||
if err != nil {
|
||||
log.Panic("error in parse lon", err)
|
||||
}
|
||||
|
||||
err = Task(key, latFloat, lonFloat)
|
||||
if err != nil {
|
||||
log.Panic("error in task", err)
|
||||
}
|
||||
|
||||
job, err := scheduler.NewJob(
|
||||
gocron.CronJob("5 */2 * * *", false), // openweather에서 2시간마다 데이터가 업데이트 된다.
|
||||
// 5분을 준 이유는 업데이트가 바로 된다는 보장이 없어서
|
||||
gocron.NewTask(
|
||||
Task,
|
||||
key,
|
||||
latFloat,
|
||||
lonFloat,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
log.Panic("error in create job", err)
|
||||
}
|
||||
|
||||
scheduler.Start()
|
||||
|
||||
nextRun, err := job.NextRun()
|
||||
if err != nil {
|
||||
log.Panic("error in get next run", err)
|
||||
}
|
||||
log.Println("next run", nextRun)
|
||||
|
||||
select {}
|
||||
}
|
||||
|
||||
func Task(key string, lat float64, lon float64) error {
|
||||
log.Println("get forecast")
|
||||
forecast, err := openweather.Forecast(context.Background(), key, lat, lon)
|
||||
if err != nil {
|
||||
log.Println("error in get forecast", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println("get forecast successfully", forecast)
|
||||
|
||||
return nil
|
||||
}
|
||||
8
go.mod
8
go.mod
@@ -3,17 +3,25 @@ module github.com/neatflowcv/seven-skies
|
||||
go 1.25.5
|
||||
|
||||
require (
|
||||
github.com/go-co-op/gocron/v2 v2.18.2
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/nats-io/nats.go v1.47.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
resty.dev/v3 v3.0.0-beta.4
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jonboulle/clockwork v0.5.0 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
27
go.sum
27
go.sum
@@ -1,22 +1,47 @@
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-co-op/gocron/v2 v2.18.2 h1:+5VU41FUXPWSPKLXZQ/77SGzUiPCcakU0v7ENc2H20Q=
|
||||
github.com/go-co-op/gocron/v2 v2.18.2/go.mod h1:Zii6he+Zfgy5W9B+JKk/KwejFOW0kZTFvHtwIpR4aBI=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
resty.dev/v3 v3.0.0-beta.4 h1:2O77oFymtA4NT8AY87wAaSgSGUBk2yvvM1qno9VRXZU=
|
||||
resty.dev/v3 v3.0.0-beta.4/go.mod h1:NTOerrC/4T7/FE6tXIZGIysXXBdgNqwMZuKtxpea9NM=
|
||||
|
||||
43
internal/pkg/openweather/forecaster.go
Normal file
43
internal/pkg/openweather/forecaster.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package openweather
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrForecast = errors.New("error in get forecast")
|
||||
)
|
||||
|
||||
func Forecast(ctx context.Context, key string, lat float64, lon float64) (*ForecastResponse, error) {
|
||||
client := resty.New()
|
||||
|
||||
defer func() {
|
||||
err := client.Close()
|
||||
if err != nil {
|
||||
log.Println("error in close client", err)
|
||||
}
|
||||
}()
|
||||
|
||||
resp, err := client.R().
|
||||
SetContext(ctx).
|
||||
SetQueryParam("lat", fmt.Sprintf("%f", lat)).
|
||||
SetQueryParam("lon", fmt.Sprintf("%f", lon)).
|
||||
SetQueryParam("appid", key).
|
||||
SetQueryParam("units", "metric").
|
||||
SetResult(&ForecastResponse{}). //nolint:exhaustruct
|
||||
Get("https://api.openweathermap.org/data/2.5/forecast")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in get forecast: %w", err)
|
||||
}
|
||||
|
||||
if resp.IsError() {
|
||||
return nil, fmt.Errorf("%w: %s, %s", ErrForecast, resp.Status(), resp.String())
|
||||
}
|
||||
|
||||
return resp.Result().(*ForecastResponse), nil //nolint:forcetypeassert
|
||||
}
|
||||
24
internal/pkg/openweather/forecaster_test.go
Normal file
24
internal/pkg/openweather/forecaster_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package openweather_test
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/neatflowcv/seven-skies/internal/pkg/openweather"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
//go:embed testdata/forecast.json
|
||||
var forecast []byte
|
||||
|
||||
func TestForecast(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var resp openweather.ForecastResponse
|
||||
|
||||
err := json.Unmarshal(forecast, &resp)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, forecast)
|
||||
}
|
||||
70
internal/pkg/openweather/model.go
Normal file
70
internal/pkg/openweather/model.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package openweather
|
||||
|
||||
type ForecastResponse struct {
|
||||
Cod string `json:"cod,omitempty"`
|
||||
Message int `json:"message,omitempty"`
|
||||
Cnt int `json:"cnt,omitempty"`
|
||||
List []List `json:"list,omitempty"`
|
||||
City City `json:"city,omitzero"`
|
||||
}
|
||||
|
||||
type Main struct {
|
||||
Temp float64 `json:"temp,omitempty"`
|
||||
FeelsLike float64 `json:"feels_like,omitempty"`
|
||||
TempMin float64 `json:"temp_min,omitempty"`
|
||||
TempMax float64 `json:"temp_max,omitempty"`
|
||||
Pressure int `json:"pressure,omitempty"`
|
||||
SeaLevel int `json:"sea_level,omitempty"`
|
||||
GrndLevel int `json:"grnd_level,omitempty"`
|
||||
Humidity int `json:"humidity,omitempty"`
|
||||
TempKf float64 `json:"temp_kf,omitempty"`
|
||||
}
|
||||
|
||||
type Weather struct {
|
||||
ID int `json:"id,omitempty"`
|
||||
Main string `json:"main,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
}
|
||||
|
||||
type Clouds struct {
|
||||
All int `json:"all,omitempty"`
|
||||
}
|
||||
|
||||
type Wind struct {
|
||||
Speed float64 `json:"speed,omitempty"`
|
||||
Deg int `json:"deg,omitempty"`
|
||||
Gust float64 `json:"gust,omitempty"`
|
||||
}
|
||||
|
||||
type Sys struct {
|
||||
Pod string `json:"pod,omitempty"`
|
||||
}
|
||||
|
||||
type List struct {
|
||||
Dt int `json:"dt,omitempty"`
|
||||
Main Main `json:"main,omitzero"`
|
||||
Weather []Weather `json:"weather,omitempty"`
|
||||
Clouds Clouds `json:"clouds,omitzero"`
|
||||
Wind Wind `json:"wind,omitzero"`
|
||||
Visibility int `json:"visibility,omitempty"`
|
||||
Pop float64 `json:"pop,omitempty"`
|
||||
Sys Sys `json:"sys,omitzero"`
|
||||
DtTxt string `json:"dt_txt,omitempty"`
|
||||
}
|
||||
|
||||
type Coord struct {
|
||||
Lat float64 `json:"lat,omitempty"`
|
||||
Lon float64 `json:"lon,omitempty"`
|
||||
}
|
||||
|
||||
type City struct {
|
||||
ID int `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Coord Coord `json:"coord,omitzero"`
|
||||
Country string `json:"country,omitempty"`
|
||||
Population int `json:"population,omitempty"`
|
||||
Timezone int `json:"timezone,omitempty"`
|
||||
Sunrise int `json:"sunrise,omitempty"`
|
||||
Sunset int `json:"sunset,omitempty"`
|
||||
}
|
||||
1472
internal/pkg/openweather/testdata/forecast.json
vendored
Normal file
1472
internal/pkg/openweather/testdata/forecast.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user