Compare commits
8 Commits
b3254e0d62
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c100d6e1b | |||
|
|
4e1f2a46fa | ||
| 9589ba8031 | |||
| 947e383e55 | |||
| bac2ec2de0 | |||
|
|
df3c280f13 | ||
| 5ba93c4377 | |||
| 066631b89e |
@@ -2,4 +2,7 @@ OPENWEATHER_API_KEY=
|
|||||||
OPENWEATHER_LAT=37
|
OPENWEATHER_LAT=37
|
||||||
OPENWEATHER_LON=126
|
OPENWEATHER_LON=126
|
||||||
NATS_URL=
|
NATS_URL=
|
||||||
DATABASE_DSN=
|
DATABASE_DSN=
|
||||||
|
KMA_API_KEY=
|
||||||
|
KMA_NX=
|
||||||
|
KMA_NY=
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@
|
|||||||
/.vscode/launch.json
|
/.vscode/launch.json
|
||||||
/.env
|
/.env
|
||||||
/openweather
|
/openweather
|
||||||
|
/kma
|
||||||
|
|||||||
251
cmd/kma/handler.go
Normal file
251
cmd/kma/handler.go
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/broker"
|
||||||
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/domain"
|
||||||
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/kma"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
broker broker.Broker
|
||||||
|
key string
|
||||||
|
nx int
|
||||||
|
ny int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(b broker.Broker, key string, nx int, ny int) *Handler {
|
||||||
|
return &Handler{
|
||||||
|
broker: b,
|
||||||
|
key: key,
|
||||||
|
nx: nx,
|
||||||
|
ny: ny,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TemporalData struct {
|
||||||
|
Temperature string
|
||||||
|
Sky string
|
||||||
|
Precipitation string
|
||||||
|
ForecastDate string
|
||||||
|
TargetDate string
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildTemporalData(resp *kma.ForecastResponse) []TemporalData {
|
||||||
|
dates := map[string]*TemporalData{}
|
||||||
|
for _, item := range resp.Response.Body.Items.Item {
|
||||||
|
data, ok := dates[item.FcstDate+item.FcstTime]
|
||||||
|
if !ok {
|
||||||
|
data = &TemporalData{
|
||||||
|
ForecastDate: item.BaseDate + item.BaseTime,
|
||||||
|
TargetDate: item.FcstDate + item.FcstTime,
|
||||||
|
Temperature: "",
|
||||||
|
Sky: "",
|
||||||
|
Precipitation: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch item.Category {
|
||||||
|
case "T1H", "TMP": // 온도
|
||||||
|
data.Temperature = item.FcstValue
|
||||||
|
case "SKY": // 하늘상태
|
||||||
|
data.Sky = item.FcstValue
|
||||||
|
case "PTY": // 강수형태
|
||||||
|
data.Precipitation = item.FcstValue
|
||||||
|
}
|
||||||
|
|
||||||
|
dates[item.FcstDate+item.FcstTime] = data
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret []TemporalData
|
||||||
|
for _, data := range dates {
|
||||||
|
ret = append(ret, *data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAndValidateTemporalData(data TemporalData) (*domain.WeatherEvent, bool) {
|
||||||
|
targetTime, err := parseKMAForecastDate(data.TargetDate)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error in parse forecast datetime", err)
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
forecastTime, err := parseKMAForecastDate(data.ForecastDate)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error in parse forecast datetime", err)
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
temperature, err := strconv.ParseInt(data.Temperature, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error in parse temperature", err)
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
condition := decideCondition(&data)
|
||||||
|
|
||||||
|
// 값 검증: 비어있는 값이 있으면 건너뛰기
|
||||||
|
if forecastTime.IsZero() {
|
||||||
|
log.Printf("skip weather event: forecastTime is empty (targetDate: %v)", targetTime)
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if targetTime.IsZero() {
|
||||||
|
log.Printf("skip weather event: targetTime is empty (forecastDate: %v)", forecastTime)
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if condition == "" {
|
||||||
|
log.Printf("skip weather event: condition is empty (forecastDate: %v, targetDate: %v)", forecastTime, targetTime)
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if temperature == 0 {
|
||||||
|
log.Printf("skip weather event: temperature is empty (forecastDate: %v, targetDate: %v)", forecastTime, targetTime)
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return &domain.WeatherEvent{
|
||||||
|
Source: domain.WeatherSourceKMA,
|
||||||
|
ForecastDate: forecastTime,
|
||||||
|
TargetDate: targetTime,
|
||||||
|
Condition: condition,
|
||||||
|
Temperature: domain.Temperature{
|
||||||
|
Value: domain.Celsius(temperature),
|
||||||
|
},
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertTemporalDataToWeatherEvents(datas []TemporalData) []*domain.WeatherEvent {
|
||||||
|
var weatherEvents []*domain.WeatherEvent
|
||||||
|
|
||||||
|
for _, data := range datas {
|
||||||
|
weatherEvent, ok := parseAndValidateTemporalData(data)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
weatherEvents = append(weatherEvents, weatherEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return weatherEvents
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleUltraShort는 초단기 예보를 조회해 이벤트로 발행한다.
|
||||||
|
func (h *Handler) HandleUltraShort(ctx context.Context) error {
|
||||||
|
log.Println("get ultra short forecast")
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
resp, err := kma.GetUltraShortForecast(ctx, h.key, now, h.nx, h.ny)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error in get ultra short forecast: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
datas := buildTemporalData(resp)
|
||||||
|
weatherEvents := convertTemporalDataToWeatherEvents(datas)
|
||||||
|
|
||||||
|
for _, weatherEvent := range weatherEvents {
|
||||||
|
message, err := json.Marshal(weatherEvent)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error in marshal weather event", err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.broker.Publish(ctx, "SEVEN_SKIES_SUBJECT.KMA", message)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error in publish weather event", err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decideCondition(data *TemporalData) domain.WeatherCondition {
|
||||||
|
if data.Precipitation == "0" && data.Sky == "1" {
|
||||||
|
return domain.WeatherConditionClear
|
||||||
|
}
|
||||||
|
|
||||||
|
switch data.Precipitation {
|
||||||
|
case "1", "5":
|
||||||
|
return domain.WeatherConditionRain
|
||||||
|
case "2", "6":
|
||||||
|
return domain.WeatherConditionRainSnow
|
||||||
|
case "3", "7":
|
||||||
|
return domain.WeatherConditionSnow
|
||||||
|
}
|
||||||
|
|
||||||
|
switch data.Sky {
|
||||||
|
case "3", "4":
|
||||||
|
return domain.WeatherConditionCloudy
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.WeatherConditionUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleShortTerm는 단기 예보를 조회해 이벤트로 발행한다.
|
||||||
|
func (h *Handler) HandleShortTerm(ctx context.Context) error {
|
||||||
|
log.Println("get short term forecast")
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
resp, err := kma.GetShortTermForecast(ctx, h.key, now, h.nx, h.ny)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error in get short term forecast: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
datas := buildTemporalData(resp)
|
||||||
|
weatherEvents := convertTemporalDataToWeatherEvents(datas)
|
||||||
|
|
||||||
|
for _, weatherEvent := range weatherEvents {
|
||||||
|
message, err := json.Marshal(weatherEvent)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error in marshal weather event", err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.broker.Publish(ctx, "SEVEN_SKIES_SUBJECT.KMA", message)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error in publish weather event", err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseKMAForecastDate(dateStr string) (time.Time, error) {
|
||||||
|
layout := "200601021504"
|
||||||
|
|
||||||
|
loc, err := time.LoadLocation("Asia/Seoul")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := time.ParseInLocation(layout, dateStr, loc)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, fmt.Errorf("error in parse datetime: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
191
cmd/kma/main.go
Normal file
191
cmd/kma/main.go
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/broker/nats"
|
||||||
|
"github.com/go-co-op/gocron/v2"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func version() string {
|
||||||
|
info, ok := debug.ReadBuildInfo()
|
||||||
|
if !ok {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
return info.Main.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Key string
|
||||||
|
NX int
|
||||||
|
NY int
|
||||||
|
NATSURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfig() *Config {
|
||||||
|
key := os.Getenv("KMA_API_KEY")
|
||||||
|
if key == "" {
|
||||||
|
log.Panic("KMA_API_KEY is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
nx := os.Getenv("KMA_NX")
|
||||||
|
if nx == "" {
|
||||||
|
log.Panic("KMA_NX is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
ny := os.Getenv("KMA_NY")
|
||||||
|
if ny == "" {
|
||||||
|
log.Panic("KMA_NY is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
NATS_URL := os.Getenv("NATS_URL")
|
||||||
|
if NATS_URL == "" {
|
||||||
|
log.Panic("NATS_URL is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
nxInt, err := strconv.Atoi(nx)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic("error in parse nx", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nyInt, err := strconv.Atoi(ny)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic("error in parse ny", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Config{
|
||||||
|
Key: key,
|
||||||
|
NX: nxInt,
|
||||||
|
NY: nyInt,
|
||||||
|
NATSURL: NATS_URL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupBroker(ctx context.Context, natsURL string) (*nats.Broker, error) {
|
||||||
|
broker, err := nats.NewBroker(ctx, natsURL, "SEVEN_SKIES_STREAM", []string{"SEVEN_SKIES_SUBJECT.>"})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error in create broker: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return broker, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:ireturn // gocron.Scheduler is an interface by design
|
||||||
|
func setupScheduler() (gocron.Scheduler, error) {
|
||||||
|
scheduler, err := gocron.NewScheduler()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error in create scheduler: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return scheduler, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runInitialTasks(ctx context.Context, handler *Handler) error {
|
||||||
|
err := handler.HandleUltraShort(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = handler.HandleShortTerm(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:ireturn // gocron.Job is an interface by design
|
||||||
|
func setupJobs(scheduler gocron.Scheduler, handler *Handler) (gocron.Job, gocron.Job, error) {
|
||||||
|
ultraJob, err := scheduler.NewJob(
|
||||||
|
gocron.CronJob("50 * * * *", false),
|
||||||
|
gocron.NewTask(handler.HandleUltraShort),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("error in create ultra short job: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
shortJob, err := scheduler.NewJob(
|
||||||
|
gocron.CronJob("15 2,5,8,11,14,17,20,23 * * *", false),
|
||||||
|
gocron.NewTask(handler.HandleShortTerm),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("error in create short term job: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ultraJob, shortJob, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func logNextRuns(ultraJob, shortJob gocron.Job) error {
|
||||||
|
ultraNext, err := ultraJob.NextRun()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error in get ultra short next run: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
shortNext, err := shortJob.NextRun()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error in get short term next run: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("ultra short next run", ultraNext)
|
||||||
|
log.Println("short term next run", shortNext)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.Println("version", version())
|
||||||
|
|
||||||
|
err := godotenv.Load()
|
||||||
|
if err == nil {
|
||||||
|
log.Println("env loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := NewConfig()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
broker, err := setupBroker(ctx, config.NATSURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic("error in create broker", err)
|
||||||
|
}
|
||||||
|
defer broker.Close()
|
||||||
|
|
||||||
|
handler := NewHandler(broker, config.Key, config.NX, config.NY)
|
||||||
|
|
||||||
|
scheduler, err := setupScheduler()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = runInitialTasks(ctx, handler)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic("error in initial tasks", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ultraJob, shortJob, err := setupJobs(scheduler, handler)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic("error in create jobs", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduler.Start()
|
||||||
|
|
||||||
|
err = logNextRuns(ultraJob, shortJob)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic("error in get next runs", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {}
|
||||||
|
}
|
||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/broker"
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/broker"
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/domain"
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/domain"
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/openweather"
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/openweather"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/broker/nats"
|
||||||
"github.com/go-co-op/gocron/v2"
|
"github.com/go-co-op/gocron/v2"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/broker/nats"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func version() string {
|
func version() string {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/neatflowcv/seven-skies/api"
|
"gitea.neatflow.kr/biosvos/seven-skies/api"
|
||||||
"github.com/neatflowcv/seven-skies/internal/app/flow"
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/app/flow"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ api.StrictServerInterface = (*Handler)(nil)
|
var _ api.StrictServerInterface = (*Handler)(nil)
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ import (
|
|||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gitea.neatflow.kr/biosvos/seven-skies/api"
|
||||||
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/app/flow"
|
||||||
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/broker/nats"
|
||||||
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/domain"
|
||||||
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/repository/gorm"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/neatflowcv/seven-skies/api"
|
|
||||||
"github.com/neatflowcv/seven-skies/internal/app/flow"
|
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/broker/nats"
|
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/domain"
|
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/repository/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func version() string {
|
func version() string {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
image: docker.io/caronc/apprise
|
image: docker.io/caronc/apprise
|
||||||
ports:
|
ports:
|
||||||
- 8080
|
- 8000
|
||||||
|
|||||||
37
deploy/cronjob.yaml
Normal file
37
deploy/cronjob.yaml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: weather-notification
|
||||||
|
spec:
|
||||||
|
schedule: "30 8 * * *"
|
||||||
|
timeZone: "Asia/Seoul"
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: weather-notifier
|
||||||
|
image: alpine:latest
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
apk add --no-cache curl jq
|
||||||
|
|
||||||
|
# 날씨 데이터 가져오기
|
||||||
|
WEATHER_DATA=$(curl -s seven-skies-app:8080/forecasts/daily | jq .items[0])
|
||||||
|
|
||||||
|
# JSON 형식 변환 (date: condition high/low 형식)
|
||||||
|
BODY=$(echo "$WEATHER_DATA" | jq -r '
|
||||||
|
"\(.date): \(.condition) \(.high)/\(.low)"
|
||||||
|
')
|
||||||
|
|
||||||
|
# Apprise API로 전송
|
||||||
|
curl -X POST "http://apprise:8000/notify" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"urls\": \"pover://ukqirq63ptby2z9ctgh67ae1636m2o@abiiap2zy7hzr9rt4vqybvqj8b24yq?priority=high\",
|
||||||
|
\"body\": $(echo "$BODY" | jq -Rs .),
|
||||||
|
\"title\": \"오늘의 날씨\"
|
||||||
|
}"
|
||||||
|
restartPolicy: OnFailure
|
||||||
618
docs/kma/GetUltraShortForecast.json
Normal file
618
docs/kma/GetUltraShortForecast.json
Normal file
@@ -0,0 +1,618 @@
|
|||||||
|
{
|
||||||
|
"response": {
|
||||||
|
"header": {
|
||||||
|
"resultCode": "00",
|
||||||
|
"resultMsg": "NORMAL_SERVICE"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"dataType": "JSON",
|
||||||
|
"items": {
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "LGT",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0100",
|
||||||
|
"fcstValue": "0",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "LGT",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0200",
|
||||||
|
"fcstValue": "0",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "LGT",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0300",
|
||||||
|
"fcstValue": "0",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "LGT",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0400",
|
||||||
|
"fcstValue": "0",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "LGT",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0500",
|
||||||
|
"fcstValue": "0",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "LGT",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0600",
|
||||||
|
"fcstValue": "0",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "PTY",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0100",
|
||||||
|
"fcstValue": "0",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "PTY",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0200",
|
||||||
|
"fcstValue": "0",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "PTY",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0300",
|
||||||
|
"fcstValue": "0",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "PTY",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0400",
|
||||||
|
"fcstValue": "0",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "PTY",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0500",
|
||||||
|
"fcstValue": "0",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "PTY",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0600",
|
||||||
|
"fcstValue": "0",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "RN1",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0100",
|
||||||
|
"fcstValue": "강수없음",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "RN1",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0200",
|
||||||
|
"fcstValue": "강수없음",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "RN1",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0300",
|
||||||
|
"fcstValue": "강수없음",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "RN1",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0400",
|
||||||
|
"fcstValue": "강수없음",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "RN1",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0500",
|
||||||
|
"fcstValue": "강수없음",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "RN1",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0600",
|
||||||
|
"fcstValue": "강수없음",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "SKY",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0100",
|
||||||
|
"fcstValue": "1",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "SKY",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0200",
|
||||||
|
"fcstValue": "3",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "SKY",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0300",
|
||||||
|
"fcstValue": "1",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "SKY",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0400",
|
||||||
|
"fcstValue": "3",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "SKY",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0500",
|
||||||
|
"fcstValue": "3",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "SKY",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0600",
|
||||||
|
"fcstValue": "1",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "T1H",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0100",
|
||||||
|
"fcstValue": "-1",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "T1H",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0200",
|
||||||
|
"fcstValue": "-2",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "T1H",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0300",
|
||||||
|
"fcstValue": "-2",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "T1H",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0400",
|
||||||
|
"fcstValue": "-2",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "T1H",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0500",
|
||||||
|
"fcstValue": "-2",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "T1H",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0600",
|
||||||
|
"fcstValue": "-2",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "REH",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0100",
|
||||||
|
"fcstValue": "75",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "REH",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0200",
|
||||||
|
"fcstValue": "75",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "REH",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0300",
|
||||||
|
"fcstValue": "75",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "REH",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0400",
|
||||||
|
"fcstValue": "75",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "REH",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0500",
|
||||||
|
"fcstValue": "75",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "REH",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0600",
|
||||||
|
"fcstValue": "75",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "UUU",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0100",
|
||||||
|
"fcstValue": "0.5",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "UUU",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0200",
|
||||||
|
"fcstValue": "0.3",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "UUU",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0300",
|
||||||
|
"fcstValue": "0.1",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "UUU",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0400",
|
||||||
|
"fcstValue": "0.1",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "UUU",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0500",
|
||||||
|
"fcstValue": "-0.1",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "UUU",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0600",
|
||||||
|
"fcstValue": "-0.4",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "VVV",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0100",
|
||||||
|
"fcstValue": "0.6",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "VVV",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0200",
|
||||||
|
"fcstValue": "0.7",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "VVV",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0300",
|
||||||
|
"fcstValue": "0.7",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "VVV",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0400",
|
||||||
|
"fcstValue": "0.3",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "VVV",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0500",
|
||||||
|
"fcstValue": "0.1",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "VVV",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0600",
|
||||||
|
"fcstValue": "0.2",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "VEC",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0100",
|
||||||
|
"fcstValue": "220",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "VEC",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0200",
|
||||||
|
"fcstValue": "202",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "VEC",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0300",
|
||||||
|
"fcstValue": "187",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "VEC",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0400",
|
||||||
|
"fcstValue": "197",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "VEC",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0500",
|
||||||
|
"fcstValue": "109",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "VEC",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0600",
|
||||||
|
"fcstValue": "112",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "WSD",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0100",
|
||||||
|
"fcstValue": "1",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "WSD",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0200",
|
||||||
|
"fcstValue": "1",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "WSD",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0300",
|
||||||
|
"fcstValue": "1",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "WSD",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0400",
|
||||||
|
"fcstValue": "0",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "WSD",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0500",
|
||||||
|
"fcstValue": "0",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseDate": "20251215",
|
||||||
|
"baseTime": "0030",
|
||||||
|
"category": "WSD",
|
||||||
|
"fcstDate": "20251215",
|
||||||
|
"fcstTime": "0600",
|
||||||
|
"fcstValue": "1",
|
||||||
|
"nx": 61,
|
||||||
|
"ny": 125
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pageNo": 1,
|
||||||
|
"numOfRows": 1000,
|
||||||
|
"totalCount": 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9818
docs/kma/ShortTermForecast.json
Normal file
9818
docs/kma/ShortTermForecast.json
Normal file
File diff suppressed because it is too large
Load Diff
8
go.mod
8
go.mod
@@ -1,19 +1,19 @@
|
|||||||
module github.com/neatflowcv/seven-skies
|
module gitea.neatflow.kr/biosvos/seven-skies
|
||||||
|
|
||||||
go 1.25.5
|
go 1.25.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-chi/chi/v5 v5.2.3
|
github.com/go-chi/chi/v5 v5.2.3
|
||||||
github.com/go-co-op/gocron/v2 v2.18.2
|
github.com/go-co-op/gocron/v2 v2.19.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/nats-io/nats.go v1.47.0
|
github.com/nats-io/nats.go v1.48.0
|
||||||
github.com/oapi-codegen/runtime v1.1.2
|
github.com/oapi-codegen/runtime v1.1.2
|
||||||
github.com/oklog/ulid/v2 v2.1.1
|
github.com/oklog/ulid/v2 v2.1.1
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
go.uber.org/mock v0.6.0
|
go.uber.org/mock v0.6.0
|
||||||
gorm.io/driver/postgres v1.6.0
|
gorm.io/driver/postgres v1.6.0
|
||||||
gorm.io/gorm v1.31.1
|
gorm.io/gorm v1.31.1
|
||||||
resty.dev/v3 v3.0.0-beta.4
|
resty.dev/v3 v3.0.0-beta.5
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
12
go.sum
12
go.sum
@@ -8,8 +8,8 @@ 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
github.com/go-co-op/gocron/v2 v2.18.2 h1:+5VU41FUXPWSPKLXZQ/77SGzUiPCcakU0v7ENc2H20Q=
|
github.com/go-co-op/gocron/v2 v2.19.0 h1:OKf2y6LXPs/BgBI2fl8PxUpNAI1DA9Mg+hSeGOS38OU=
|
||||||
github.com/go-co-op/gocron/v2 v2.18.2/go.mod h1:Zii6he+Zfgy5W9B+JKk/KwejFOW0kZTFvHtwIpR4aBI=
|
github.com/go-co-op/gocron/v2 v2.19.0/go.mod h1:5lEiCKk1oVJV39Zg7/YG10OnaVrDAV5GGR6O0663k6U=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
@@ -37,8 +37,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
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.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
|
||||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
github.com/nats-io/nats.go v1.48.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 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
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 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||||
@@ -89,5 +89,5 @@ gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
|||||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||||
resty.dev/v3 v3.0.0-beta.4 h1:2O77oFymtA4NT8AY87wAaSgSGUBk2yvvM1qno9VRXZU=
|
resty.dev/v3 v3.0.0-beta.5 h1:NV1xbqOLzSq7XMTs1t/HLPvu7xrxoXzF90SR4OO6faQ=
|
||||||
resty.dev/v3 v3.0.0-beta.4/go.mod h1:NTOerrC/4T7/FE6tXIZGIysXXBdgNqwMZuKtxpea9NM=
|
resty.dev/v3 v3.0.0-beta.5/go.mod h1:NTOerrC/4T7/FE6tXIZGIysXXBdgNqwMZuKtxpea9NM=
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/domain"
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/domain"
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/repository"
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/repository"
|
||||||
"github.com/oklog/ulid/v2"
|
"github.com/oklog/ulid/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/neatflowcv/seven-skies/internal/app/flow"
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/app/flow"
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/domain"
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/domain"
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/repository/mocks"
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/repository/mocks"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/mock/gomock"
|
"go.uber.org/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/broker"
|
||||||
"github.com/nats-io/nats.go"
|
"github.com/nats-io/nats.go"
|
||||||
"github.com/nats-io/nats.go/jetstream"
|
"github.com/nats-io/nats.go/jetstream"
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/broker"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ broker.Broker = (*Broker)(nil)
|
var _ broker.Broker = (*Broker)(nil)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/broker/nats"
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/broker/nats"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
7
internal/pkg/kma/errors.go
Normal file
7
internal/pkg/kma/errors.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package kma
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrForecast = errors.New("error in get forecast")
|
||||||
|
)
|
||||||
179
internal/pkg/kma/forecaster.go
Normal file
179
internal/pkg/kma/forecaster.go
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
package kma
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"resty.dev/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UltraShortForecastInterval = time.Hour
|
||||||
|
UltraShortForecastAvailableAfter = 15 * time.Minute
|
||||||
|
|
||||||
|
ShortTermForecastInterval = 3 * time.Hour
|
||||||
|
ShortTermForecastAvailableAfter = 10 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetUltraShortForecast(ctx context.Context, key string, base time.Time, nx int, ny int) (*ForecastResponse, error) {
|
||||||
|
// 초단기 예보는 0:30, 1:30, ..., 11:30 단위로 제공된다.
|
||||||
|
// 요청 시각(`base`)을 기준으로, 그보다 크지 않으면서 가장 가까운 기준 시각을 선택한다.
|
||||||
|
selectedBase := selectUltraShortBase(base)
|
||||||
|
|
||||||
|
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("pageNo", "1").
|
||||||
|
SetQueryParam("numOfRows", "1000").
|
||||||
|
SetQueryParam("dataType", "JSON").
|
||||||
|
SetQueryParam("base_date", selectedBase.Format("20060102")).
|
||||||
|
SetQueryParam("base_time", selectedBase.Format("1504")).
|
||||||
|
SetQueryParam("nx", strconv.Itoa(nx)).
|
||||||
|
SetQueryParam("ny", strconv.Itoa(ny)).
|
||||||
|
SetQueryParam("authKey", key).
|
||||||
|
SetResult(&ForecastResponse{}). //nolint:exhaustruct
|
||||||
|
Get("https://apihub.kma.go.kr/api/typ02/openApi/VilageFcstInfoService_2.0/getUltraSrtFcst")
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetShortTermForecast(ctx context.Context, key string, base time.Time, nx int, ny int) (*ForecastResponse, error) {
|
||||||
|
selectedBase := selectShortTermBase(base)
|
||||||
|
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := client.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error in close client", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
resp, err := client.R().SetDebug(true).
|
||||||
|
SetContext(ctx).
|
||||||
|
SetQueryParam("pageNo", "1").
|
||||||
|
SetQueryParam("numOfRows", "1000").
|
||||||
|
SetQueryParam("dataType", "JSON").
|
||||||
|
SetQueryParam("base_date", selectedBase.Format("20060102")).
|
||||||
|
SetQueryParam("base_time", selectedBase.Format("1504")).
|
||||||
|
SetQueryParam("nx", strconv.Itoa(nx)).
|
||||||
|
SetQueryParam("ny", strconv.Itoa(ny)).
|
||||||
|
SetQueryParam("authKey", key).
|
||||||
|
SetResult(&ForecastResponse{}). //nolint:exhaustruct
|
||||||
|
Get("https://apihub.kma.go.kr/api/typ02/openApi/VilageFcstInfoService_2.0/getVilageFcst")
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 초단기 예보 기준 시각(0:30, 1:30, ..., 11:30) 중에서,
|
||||||
|
// 주어진 시각 t 기준으로 t 보다 크지 않으면서 가장 가까운 시각을 반환한다.
|
||||||
|
func selectUltraShortBase(base time.Time) time.Time {
|
||||||
|
// 초단기 예보:
|
||||||
|
// - 기준 시각 간격: 1시간
|
||||||
|
// - 조회 가능 시각: 기준 시각 + 15분
|
||||||
|
// - 후보 시작 시각: 이전날 23:30
|
||||||
|
const (
|
||||||
|
startHour = 23
|
||||||
|
startMinute = 30
|
||||||
|
)
|
||||||
|
|
||||||
|
return selectBase(
|
||||||
|
base,
|
||||||
|
UltraShortForecastInterval,
|
||||||
|
UltraShortForecastAvailableAfter,
|
||||||
|
startHour,
|
||||||
|
startMinute,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 단기 예보 기준 시각(02, 05, 08, 11, 14, 17, 20, 23) 중에서,
|
||||||
|
// 주어진 시각 t 기준으로, t 시각에서 조회 가능한 가장 최근 기준 시각을 반환한다.
|
||||||
|
// 각 기준 시각은 기준 시각 + 10분 이후부터 조회 가능하다.
|
||||||
|
func selectShortTermBase(base time.Time) time.Time {
|
||||||
|
// 단기 예보:
|
||||||
|
// - 기준 시각 간격: 3시간
|
||||||
|
// - 조회 가능 시각: 기준 시각 + 10분
|
||||||
|
// - 후보 시작 시각: 이전날 23:00
|
||||||
|
const (
|
||||||
|
startHour = 23
|
||||||
|
startMinute = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
return selectBase(
|
||||||
|
base,
|
||||||
|
ShortTermForecastInterval,
|
||||||
|
ShortTermForecastAvailableAfter,
|
||||||
|
startHour,
|
||||||
|
startMinute,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectBase(base time.Time, interval, availableAfter time.Duration, startHour, startMinute int) time.Time {
|
||||||
|
// KMA API 는 한국 표준시(Asia/Seoul)를 기준으로 동작하므로,
|
||||||
|
// base 가 다른 타임존(예: UTC)인 경우 한국 시간으로 변환해서 계산한다.
|
||||||
|
loc, err := time.LoadLocation("Asia/Seoul")
|
||||||
|
if err != nil {
|
||||||
|
// 로케일 정보를 불러오지 못하면, 기존 base 의 로케이션을 그대로 사용한다.
|
||||||
|
loc = base.Location()
|
||||||
|
}
|
||||||
|
|
||||||
|
baseInKST := base.In(loc)
|
||||||
|
|
||||||
|
prevDay := baseInKST.AddDate(0, 0, -1)
|
||||||
|
|
||||||
|
start := time.Date(prevDay.Year(), prevDay.Month(), prevDay.Day(), startHour, startMinute, 0, 0, loc)
|
||||||
|
end := baseInKST
|
||||||
|
|
||||||
|
var candidates []time.Time
|
||||||
|
|
||||||
|
for t := start; !t.After(end); t = t.Add(interval) {
|
||||||
|
candidates = append(candidates, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectBaseFromCandidates(baseInKST, candidates, availableAfter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 공통 기준 시각 선택 로직:
|
||||||
|
// - candidates: 오름차순(과거 -> 미래) 기준 시각 목록
|
||||||
|
// - 각 기준 시각은 availableAfter 이후부터 조회 가능
|
||||||
|
// - base 시각 기준으로, 조회 가능한 가장 최근 기준 시각을 반환한다.
|
||||||
|
func selectBaseFromCandidates(base time.Time, candidates []time.Time, availableAfter time.Duration) time.Time {
|
||||||
|
// 가장 최근 기준 시각을 찾기 위해 역순으로 순회한다.
|
||||||
|
for i := len(candidates) - 1; i >= 0; i-- {
|
||||||
|
candidate := candidates[i]
|
||||||
|
availableAt := candidate.Add(availableAfter)
|
||||||
|
|
||||||
|
if base.Before(availableAt) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("no available base time found")
|
||||||
|
}
|
||||||
38
internal/pkg/kma/model.go
Normal file
38
internal/pkg/kma/model.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package kma
|
||||||
|
|
||||||
|
type ForecastResponse struct {
|
||||||
|
Response Response `json:"response,omitzero"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Header struct {
|
||||||
|
ResultCode string `json:"resultCode,omitempty"`
|
||||||
|
ResultMsg string `json:"resultMsg,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Item struct {
|
||||||
|
BaseDate string `json:"baseDate,omitempty"`
|
||||||
|
BaseTime string `json:"baseTime,omitempty"`
|
||||||
|
Category string `json:"category,omitempty"`
|
||||||
|
FcstDate string `json:"fcstDate,omitempty"`
|
||||||
|
FcstTime string `json:"fcstTime,omitempty"`
|
||||||
|
FcstValue string `json:"fcstValue,omitempty"`
|
||||||
|
Nx int `json:"nx,omitempty"`
|
||||||
|
Ny int `json:"ny,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Items struct {
|
||||||
|
Item []Item `json:"item,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Body struct {
|
||||||
|
DataType string `json:"dataType,omitempty"`
|
||||||
|
Items Items `json:"items,omitzero"`
|
||||||
|
PageNo int `json:"pageNo,omitempty"`
|
||||||
|
NumOfRows int `json:"numOfRows,omitempty"`
|
||||||
|
TotalCount int `json:"totalCount,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Header Header `json:"header,omitzero"`
|
||||||
|
Body Body `json:"body,omitzero"`
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/openweather"
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/openweather"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/domain"
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/domain"
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/repository"
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/repository"
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package gorm
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/domain"
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Weather struct {
|
type Weather struct {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/neatflowcv/seven-skies/internal/pkg/repository (interfaces: Repository)
|
// Source: gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/repository (interfaces: Repository)
|
||||||
//
|
//
|
||||||
// Generated by this command:
|
// Generated by this command:
|
||||||
//
|
//
|
||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
time "time"
|
time "time"
|
||||||
|
|
||||||
domain "github.com/neatflowcv/seven-skies/internal/pkg/domain"
|
domain "gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/domain"
|
||||||
gomock "go.uber.org/mock/gomock"
|
gomock "go.uber.org/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/domain"
|
"gitea.neatflow.kr/biosvos/seven-skies/internal/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate mockgen -typed -package=mocks -destination=mocks/repository.go . Repository
|
//go:generate mockgen -typed -package=mocks -destination=mocks/repository.go . Repository
|
||||||
|
|||||||
Reference in New Issue
Block a user