All checks were successful
ci/woodpecker/push/build-image Pipeline was successful
109 lines
2.8 KiB
Go
109 lines
2.8 KiB
Go
// forecast_endpoint.go defines forecast endpoint behavior.
|
|
// Layer: adapters/inbound/httpapi forecast route.
|
|
package httpapi
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"gitea.maximumdirect.net/ejr/feedapi/endpoint"
|
|
"gitea.maximumdirect.net/ejr/feedapi/render"
|
|
"gitea.maximumdirect.net/ejr/feedapi/response"
|
|
"gitea.maximumdirect.net/ejr/weatherapi/internal/adapters/inbound/httpapi/presenter"
|
|
"gitea.maximumdirect.net/ejr/weatherfeeder/model"
|
|
)
|
|
|
|
type forecastDaySlice int
|
|
|
|
const (
|
|
forecastDaySliceAll forecastDaySlice = iota
|
|
forecastDaySliceToday
|
|
forecastDaySliceTomorrow
|
|
)
|
|
|
|
var forecastNow = time.Now
|
|
|
|
func forecastDefinitions(svc Service) []endpoint.Definition {
|
|
out := make([]endpoint.Definition, 0, 6)
|
|
out = append(out, forecastDefinitionSet(
|
|
"/forecast/hourly",
|
|
"forecast_hourly.txt.tmpl",
|
|
svc.LatestHourlyForecast,
|
|
)...)
|
|
out = append(out, forecastDefinitionSet(
|
|
"/forecast/narrative",
|
|
"forecast_narrative.txt.tmpl",
|
|
svc.LatestNarrativeForecast,
|
|
)...)
|
|
return out
|
|
}
|
|
|
|
func forecastDefinitionSet(
|
|
basePath string,
|
|
templateName string,
|
|
fetch func(context.Context) (*model.WeatherForecastRun, error),
|
|
) []endpoint.Definition {
|
|
return []endpoint.Definition{
|
|
forecastDefinition(basePath, forecastDaySliceAll, templateName, fetch),
|
|
forecastDefinition(basePath+"/today", forecastDaySliceToday, templateName, fetch),
|
|
forecastDefinition(basePath+"/tomorrow", forecastDaySliceTomorrow, templateName, fetch),
|
|
}
|
|
}
|
|
|
|
func forecastDefinition(
|
|
path string,
|
|
daySlice forecastDaySlice,
|
|
templateName string,
|
|
fetch func(context.Context) (*model.WeatherForecastRun, error),
|
|
) endpoint.Definition {
|
|
return endpoint.GET(
|
|
path,
|
|
bindForecastPrecisionQuery,
|
|
func(ctx context.Context, req precisionQueryRequest) (any, error) {
|
|
run, err := fetch(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if daySlice != forecastDaySliceAll {
|
|
run = filterForecastRunByDaySlice(run, req.Timezone, daySlice)
|
|
}
|
|
|
|
return response.Envelope{Data: presenter.ForecastPayload(run, req.Units, req.Precision, req.Timezone)}, nil
|
|
},
|
|
endpoint.WithProduces(render.FormatJSON, render.FormatXML, render.FormatText),
|
|
endpoint.WithTemplate(templateName),
|
|
)
|
|
}
|
|
|
|
func filterForecastRunByDaySlice(run *model.WeatherForecastRun, tz *time.Location, daySlice forecastDaySlice) *model.WeatherForecastRun {
|
|
if run == nil {
|
|
return nil
|
|
}
|
|
|
|
loc := tz
|
|
if loc == nil {
|
|
loc = time.UTC
|
|
}
|
|
|
|
now := forecastNow().In(loc)
|
|
target := now
|
|
if daySlice == forecastDaySliceTomorrow {
|
|
target = target.AddDate(0, 0, 1)
|
|
}
|
|
year, month, day := target.Date()
|
|
|
|
periods := make([]model.WeatherForecastPeriod, 0, len(run.Periods))
|
|
for _, period := range run.Periods {
|
|
start := period.StartTime.In(loc)
|
|
y, m, d := start.Date()
|
|
if y == year && m == month && d == day {
|
|
periods = append(periods, period)
|
|
}
|
|
}
|
|
|
|
cloned := *run
|
|
cloned.Periods = periods
|
|
return &cloned
|
|
}
|