// 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 }