package meteo import ( "context" "encoding/json" "errors" "fmt" "io" "log/slog" "net/http" "time" "github.com/cenkalti/backoff/v5" "github.com/dgraph-io/ristretto/v2" ) type Service struct { cache *ristretto.Cache[string, string] } func NewService() *Service { cache, err := ristretto.NewCache(&ristretto.Config[string, string]{ NumCounters: 1024, MaxCost: 1 << 30, BufferItems: 64, }) if err != nil { slog.Error("cannot init cache", "err", err) return nil } return &Service{ cache: cache, } } func (s *Service) GetWeatherByCity(ctx context.Context, params GetMeteoData) (MeteoData, error) { fromDate, err := time.Parse("2006-01-02", params.Date) if err != nil { return MeteoData{}, err } toDate := fromDate.AddDate(0, 0, params.Days-1) operation := func() (*http.Response, error) { url := fmt.Sprintf("http://localhost:8080/data?city=%s&from=%s&to=%s", params.Location, params.Date, toDate.Format("2006-01-02")) slog.Info("url", "url", url) resp, err := http.Get(url) if err != nil { return nil, err } if resp.StatusCode == http.StatusBadRequest { resp.Body.Close() return nil, backoff.Permanent(errors.New("bad request")) } return resp, nil } result, err := backoff.Retry(ctx, operation, backoff.WithBackOff(backoff.NewExponentialBackOff())) if err != nil { slog.Error("somethin happened") return MeteoData{}, err } defer result.Body.Close() body, err := io.ReadAll(result.Body) if err != nil { slog.Error("error reading response body", "err", err) return MeteoData{}, err } var serviceAResponse struct { MeteoData []MeteoDataFromServiceA `json:"meteo_data"` } if err := json.Unmarshal(body, &serviceAResponse); err != nil { slog.Error("error unmarshaling response", "err", err) return MeteoData{}, err } if len(serviceAResponse.MeteoData) == 0 { return MeteoData{}, nil } if params.Agg == AggDaily { return s.processDailyData(serviceAResponse.MeteoData, params) } return s.processRolling7Data(serviceAResponse.MeteoData, params) } func (s *Service) processDailyData(data []MeteoDataFromServiceA, params GetMeteoData) (MeteoData, error) { days := make([]MeteoDataPerDay, 0, len(data)) for _, d := range data { avgTemp := (d.MaxTemp + d.MinTemp) / 2 day := MeteoDataPerDay{ MaxTemp: d.MaxTemp, MinTemp: d.MinTemp, AvgTemp: avgTemp, Rainfall: d.Rainfall, Cloudiness: d.Cloudiness, } if params.Unit == UnitF { day.ConvertValue() } days = append(days, day) } return MeteoData{ Location: params.Location, Unit: params.Unit, From: params.Date, Days: &days, }, nil } func (s *Service) processRolling7Data(data []MeteoDataFromServiceA, params GetMeteoData) (MeteoData, error) { if len(data) < 7 { return MeteoData{}, errors.New("insufficient data for rolling 7-day calculation") } var sumTemp, sumRainfall float32 var sumCloudiness int for i := len(data) - 7; i < len(data); i++ { avgTemp := (data[i].MaxTemp + data[i].MinTemp) / 2 sumTemp += avgTemp sumRainfall += data[i].Rainfall sumCloudiness += data[i].Cloudiness } rolling7 := &Rolling7Data{ AvgTemp: sumTemp / 7, AvgCloudiness: sumCloudiness / 7, SumRainfall: sumRainfall, } if params.Unit == UnitF { rolling7.AvgTemp = rolling7.AvgTemp*9/5 + 32 } return MeteoData{ Location: params.Location, Unit: params.Unit, From: params.Date, Rolling7: rolling7, }, nil }