All checks were successful
ci/woodpecker/push/build-image Pipeline was successful
119 lines
3.0 KiB
Go
119 lines
3.0 KiB
Go
package nws
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
|
|
"gitea.maximumdirect.net/ejr/feedkit/config"
|
|
"gitea.maximumdirect.net/ejr/feedkit/event"
|
|
fksources "gitea.maximumdirect.net/ejr/feedkit/sources"
|
|
nwscommon "gitea.maximumdirect.net/ejr/weatherfeeder/internal/providers/nws"
|
|
"gitea.maximumdirect.net/ejr/weatherfeeder/standards"
|
|
)
|
|
|
|
// WeatherStoriesSource polls an NWS weatherstories endpoint and emits a RAW weather story Event.
|
|
//
|
|
// Output schema:
|
|
// - standards.SchemaRawNWSWeatherStoriesV1
|
|
type WeatherStoriesSource struct {
|
|
http *fksources.HTTPSource
|
|
}
|
|
|
|
func NewWeatherStoriesSource(cfg config.SourceConfig) (*WeatherStoriesSource, error) {
|
|
const driver = "nws_weatherstories"
|
|
|
|
hs, err := fksources.NewHTTPSource(driver, cfg, "application/geo+json, application/json")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &WeatherStoriesSource{http: hs}, nil
|
|
}
|
|
|
|
func (s *WeatherStoriesSource) Name() string { return s.http.Name }
|
|
|
|
func (s *WeatherStoriesSource) Kinds() []event.Kind {
|
|
return []event.Kind{event.Kind("weather_story")}
|
|
}
|
|
|
|
func (s *WeatherStoriesSource) Poll(ctx context.Context) ([]event.Event, error) {
|
|
raw, meta, changed, err := s.fetchRaw(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !changed {
|
|
return nil, nil
|
|
}
|
|
|
|
var effectiveAt *time.Time
|
|
switch {
|
|
case !meta.ParsedLatestUpdateTime.IsZero():
|
|
t := meta.ParsedLatestUpdateTime.UTC()
|
|
effectiveAt = &t
|
|
case !meta.ParsedLatestStartTime.IsZero():
|
|
t := meta.ParsedLatestStartTime.UTC()
|
|
effectiveAt = &t
|
|
}
|
|
|
|
emittedAt := time.Now().UTC()
|
|
eventID := fksources.DefaultEventID("", s.http.Name, effectiveAt, emittedAt)
|
|
|
|
return fksources.SingleEvent(
|
|
event.Kind("weather_story"),
|
|
s.http.Name,
|
|
standards.SchemaRawNWSWeatherStoriesV1,
|
|
eventID,
|
|
emittedAt,
|
|
effectiveAt,
|
|
raw,
|
|
)
|
|
}
|
|
|
|
type weatherStoriesMeta struct {
|
|
Stories []struct {
|
|
StartTime string `json:"startTime"`
|
|
UpdateTime string `json:"updateTime"`
|
|
} `json:"stories"`
|
|
|
|
ParsedLatestUpdateTime time.Time `json:"-"`
|
|
ParsedLatestStartTime time.Time `json:"-"`
|
|
}
|
|
|
|
func (s *WeatherStoriesSource) fetchRaw(ctx context.Context) (json.RawMessage, weatherStoriesMeta, bool, error) {
|
|
raw, changed, err := s.http.FetchJSONIfChanged(ctx)
|
|
if err != nil {
|
|
return nil, weatherStoriesMeta{}, false, err
|
|
}
|
|
if !changed {
|
|
return nil, weatherStoriesMeta{}, false, nil
|
|
}
|
|
|
|
var meta weatherStoriesMeta
|
|
if err := json.Unmarshal(raw, &meta); err != nil {
|
|
return raw, weatherStoriesMeta{}, true, nil
|
|
}
|
|
|
|
for _, story := range meta.Stories {
|
|
if ts := strings.TrimSpace(story.UpdateTime); ts != "" {
|
|
if t, err := nwscommon.ParseTime(ts); err == nil {
|
|
t = t.UTC()
|
|
if meta.ParsedLatestUpdateTime.IsZero() || t.After(meta.ParsedLatestUpdateTime) {
|
|
meta.ParsedLatestUpdateTime = t
|
|
}
|
|
}
|
|
}
|
|
if ts := strings.TrimSpace(story.StartTime); ts != "" {
|
|
if t, err := nwscommon.ParseTime(ts); err == nil {
|
|
t = t.UTC()
|
|
if meta.ParsedLatestStartTime.IsZero() || t.After(meta.ParsedLatestStartTime) {
|
|
meta.ParsedLatestStartTime = t
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return raw, meta, true, nil
|
|
}
|