diff --git a/service_b/go.mod b/service_b/go.mod index 34d6ee3..5fe644d 100644 --- a/service_b/go.mod +++ b/service_b/go.mod @@ -1,3 +1,7 @@ module serviceb go 1.25.2 + +replace pkg => ../pkg + +require pkg v0.0.0-00010101000000-000000000000 diff --git a/service_b/internal/app/app.go b/service_b/internal/app/app.go new file mode 100644 index 0000000..93a6d9e --- /dev/null +++ b/service_b/internal/app/app.go @@ -0,0 +1,3 @@ +package app + +type H map[string]any diff --git a/service_b/internal/domains/meteo/domain.go b/service_b/internal/domains/meteo/domain.go new file mode 100644 index 0000000..417e26d --- /dev/null +++ b/service_b/internal/domains/meteo/domain.go @@ -0,0 +1,90 @@ +package meteo + +import ( + "errors" + "time" +) + +type MeteoDataPerDay struct { + MaxTemp float32 `json:"max_temp"` + MinTemp float32 `json:"min_temp"` + AvgTemp float32 `json:"avg_temp"` + Rainfall float32 `json:"rainfall"` + Cloudiness int `json:"cloudiness"` +} + +func (mtpd *MeteoDataPerDay) ConvertValue() { + mtpd.MaxTemp = mtpd.MaxTemp*9/5 + 32 + mtpd.MinTemp = mtpd.MinTemp*9/5 + 32 + mtpd.AvgTemp = mtpd.AvgTemp*9/5 + 32 +} + +type Rolling7Data struct { + AvgTemp float32 `json:"avg_temp"` + AvgCloudiness int `json:"avg_cloudiness"` + SumRainfall float32 `json:"sum_rainfall"` +} + +type MeteoData struct { + Location string `json:"location"` + Unit Unit `json:"unit"` + From string `json:"from"` + Days *[]MeteoDataPerDay `json:"days,omitempty"` + Rolling7 *Rolling7Data `json:"rolling7,omitempty"` +} + +type Unit string + +const ( + UnitC Unit = "C" + UnitF Unit = "F" +) + +type Agg string + +const ( + AggDaily Agg = "daily" + AggRolling7 Agg = "rolling7" +) + +type GetMeteoData struct { + Location string + Date string + Days int + Unit Unit + Agg Agg +} + +func (mt *GetMeteoData) Validate() error { + if mt.Date == "" { + mt.Date = time.Now().Format("2006-01-02") + } + + if _, err := time.Parse("2006-01-02", mt.Date); err != nil { + return ErrMissingOrInvalidDate + } + + if mt.Days == 0 { + mt.Days = 5 + } + if mt.Days < 1 || mt.Days > 10 { + return ErrInvalidDays + } + + if mt.Unit == "" { + mt.Unit = UnitC + } + + if mt.Agg == "" { + mt.Agg = AggDaily + } + + return nil +} + +var ( + ErrCityNotFound = errors.New("city not found") + ErrReadingData = errors.New("error reading data") + ErrMissingOrInvalidDate = errors.New("missing or invalid date") + ErrInvalidDays = errors.New("days must be between 1 and 10") +) diff --git a/service_b/internal/domains/meteo/handlers.go b/service_b/internal/domains/meteo/handlers.go new file mode 100644 index 0000000..a383faa --- /dev/null +++ b/service_b/internal/domains/meteo/handlers.go @@ -0,0 +1,40 @@ +package meteo + +import ( + "log/slog" + "net/http" + "pkg" + "serviceb/internal/app" +) + +type Handler struct { + pkg.BaseHandler + s *Service +} + +func NewHandler(service *Service) *Handler { + return &Handler{ + s: service, + } +} + +func (h *Handler) GetMeteoData(w http.ResponseWriter, r *http.Request) { + locationValue := r.PathValue("city") + queryParams := r.URL.Query() + + params := GetMeteoData{ + Location: locationValue, + Date: queryParams.Get("date"), + Days: h.ParamToInt(queryParams.Get("days"), 5), + Unit: Unit(queryParams.Get("unit")), + Agg: Agg(queryParams.Get("agg")), + } + + if err := params.Validate(); err != nil { + slog.Error("error validating struct", "error", err) + h.ToJSON(w, http.StatusBadRequest, app.H{"error": err.Error()}) + } + + slog.Info("loc value", "value", params) + return +} diff --git a/service_b/internal/domains/meteo/router.go b/service_b/internal/domains/meteo/router.go new file mode 100644 index 0000000..85d4c1f --- /dev/null +++ b/service_b/internal/domains/meteo/router.go @@ -0,0 +1,7 @@ +package meteo + +import "net/http" + +func RegisterRoutes(mux *http.ServeMux, handler *Handler) { + mux.HandleFunc("GET /weather/{city}", handler.GetMeteoData) +} diff --git a/service_b/internal/domains/meteo/service.go b/service_b/internal/domains/meteo/service.go new file mode 100644 index 0000000..37c3250 --- /dev/null +++ b/service_b/internal/domains/meteo/service.go @@ -0,0 +1,22 @@ +package meteo + +import ( + "context" + "sync" +) + +type inMemory struct { + data map[string]MeteoData + mu *sync.RWMutex +} +type Service struct { + inMemory +} + +func NewService() *Service { + return &Service{} +} + +func (s *Service) GetWeatherByCity(ctx context.Context, params GetMeteoData) ([]MeteoData, error) { + return []MeteoData{}, nil +} diff --git a/service_b/internal/router/router.go b/service_b/internal/router/router.go new file mode 100644 index 0000000..a2001c8 --- /dev/null +++ b/service_b/internal/router/router.go @@ -0,0 +1,16 @@ +package router + +import ( + "fmt" + "net/http" +) + +func SetupRoutes() *http.ServeMux { + mux := http.NewServeMux() + + mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "hello world") + }) + + return mux +} diff --git a/service_b/server/main.go b/service_b/server/main.go index 64c6a35..89ac09c 100644 --- a/service_b/server/main.go +++ b/service_b/server/main.go @@ -2,17 +2,31 @@ package main import ( "fmt" - "log" + "log/slog" "net/http" + "serviceb/internal/domains/meteo" + "serviceb/internal/router" + "time" ) func main() { - mux := http.NewServeMux() + mux := router.SetupRoutes() - mux.HandleFunc("GET /hello", func(w http.ResponseWriter, r *http.Request) { - log.Println("Received request on /hello endpoint") - fmt.Fprintf(w, "Hello world from service B") - }) + meteoService := meteo.NewService() + meteoHandler := meteo.NewHandler(meteoService) + meteo.RegisterRoutes(mux, meteoHandler) - http.ListenAndServe(":8090", mux) + server := http.Server{ + Addr: ":8090", + Handler: mux, + ReadTimeout: 15 * time.Second, + WriteTimeout: 15 * time.Second, + IdleTimeout: 60 * time.Second, + ReadHeaderTimeout: 5 * time.Second, + } + + slog.Info("server starting on :8090") + if err := server.ListenAndServe(); err != nil { + panic(fmt.Sprintf("server failed, error %s", err)) + } }