diff --git a/.github/workflows/mertricstest.yml b/.github/workflows/mertricstest.yml index 5f71aca..7d1b03c 100644 --- a/.github/workflows/mertricstest.yml +++ b/.github/workflows/mertricstest.yml @@ -78,8 +78,7 @@ jobs: github.head_ref == 'iter11' || github.head_ref == 'iter12' || github.head_ref == 'iter13' || - github.head_ref == 'iter14' || - github.head_ref == 'iter15' + github.head_ref == 'iter14' run: | metricstest -test.v -test.run=^TestIteration1$ \ -binary-path=cmd/server/server @@ -104,8 +103,7 @@ jobs: github.head_ref == 'iter11' || github.head_ref == 'iter12' || github.head_ref == 'iter13' || - github.head_ref == 'iter14' || - github.head_ref == 'iter15' + github.head_ref == 'iter14' run: | metricstest -test.v -test.run=^TestIteration2[AB]*$ \ -source-path=. \ @@ -125,8 +123,7 @@ jobs: github.head_ref == 'iter11' || github.head_ref == 'iter12' || github.head_ref == 'iter13' || - github.head_ref == 'iter14' || - github.head_ref == 'iter15' + github.head_ref == 'iter14' run: | metricstest -test.v -test.run=^TestIteration3[AB]*$ \ -source-path=. \ @@ -146,8 +143,7 @@ jobs: github.head_ref == 'iter11' || github.head_ref == 'iter12' || github.head_ref == 'iter13' || - github.head_ref == 'iter14' || - github.head_ref == 'iter15' + github.head_ref == 'iter14' run: | SERVER_PORT=$(random unused-port) ADDRESS="localhost:${SERVER_PORT}" @@ -170,8 +166,7 @@ jobs: github.head_ref == 'iter11' || github.head_ref == 'iter12' || github.head_ref == 'iter13' || - github.head_ref == 'iter14' || - github.head_ref == 'iter15' + github.head_ref == 'iter14' run: | SERVER_PORT=$(random unused-port) ADDRESS="localhost:${SERVER_PORT}" @@ -193,8 +188,7 @@ jobs: github.head_ref == 'iter11' || github.head_ref == 'iter12' || github.head_ref == 'iter13' || - github.head_ref == 'iter14' || - github.head_ref == 'iter15' + github.head_ref == 'iter14' run: | SERVER_PORT=$(random unused-port) ADDRESS="localhost:${SERVER_PORT}" @@ -215,8 +209,7 @@ jobs: github.head_ref == 'iter11' || github.head_ref == 'iter12' || github.head_ref == 'iter13' || - github.head_ref == 'iter14' || - github.head_ref == 'iter15' + github.head_ref == 'iter14' run: | SERVER_PORT=$(random unused-port) ADDRESS="localhost:${SERVER_PORT}" @@ -236,8 +229,7 @@ jobs: github.head_ref == 'iter11' || github.head_ref == 'iter12' || github.head_ref == 'iter13' || - github.head_ref == 'iter14' || - github.head_ref == 'iter15' + github.head_ref == 'iter14' run: | SERVER_PORT=$(random unused-port) ADDRESS="localhost:${SERVER_PORT}" @@ -256,8 +248,7 @@ jobs: github.head_ref == 'iter11' || github.head_ref == 'iter12' || github.head_ref == 'iter13' || - github.head_ref == 'iter14' || - github.head_ref == 'iter15' + github.head_ref == 'iter14' run: | SERVER_PORT=$(random unused-port) ADDRESS="localhost:${SERVER_PORT}" @@ -276,8 +267,7 @@ jobs: github.head_ref == 'iter11' || github.head_ref == 'iter12' || github.head_ref == 'iter13' || - github.head_ref == 'iter14' || - github.head_ref == 'iter15' + github.head_ref == 'iter14' run: | SERVER_PORT=$(random unused-port) ADDRESS="localhost:${SERVER_PORT}" @@ -295,8 +285,7 @@ jobs: github.head_ref == 'iter11' || github.head_ref == 'iter12' || github.head_ref == 'iter13' || - github.head_ref == 'iter14' || - github.head_ref == 'iter15' + github.head_ref == 'iter14' run: | SERVER_PORT=$(random unused-port) ADDRESS="localhost:${SERVER_PORT}" @@ -313,8 +302,7 @@ jobs: github.ref == 'refs/heads/main' || github.head_ref == 'iter12' || github.head_ref == 'iter13' || - github.head_ref == 'iter14' || - github.head_ref == 'iter15' + github.head_ref == 'iter14' run: | SERVER_PORT=$(random unused-port) ADDRESS="localhost:${SERVER_PORT}" @@ -330,8 +318,7 @@ jobs: if: | github.ref == 'refs/heads/main' || github.head_ref == 'iter13' || - github.head_ref == 'iter14' || - github.head_ref == 'iter15' + github.head_ref == 'iter14' run: | SERVER_PORT=$(random unused-port) ADDRESS="localhost:${SERVER_PORT}" @@ -346,8 +333,7 @@ jobs: - name: "Code increment #14" if: | github.ref == 'refs/heads/main' || - github.head_ref == 'iter14' || - github.head_ref == 'iter15' + github.head_ref == 'iter14' run: | SERVER_PORT=$(random unused-port) ADDRESS="localhost:${SERVER_PORT}" @@ -363,7 +349,6 @@ jobs: - name: "Code increment #14 (race detection)" if: | github.ref == 'refs/heads/main' || - github.head_ref == 'iter14' || - github.head_ref == 'iter15' + github.head_ref == 'iter14' run: | go test -v -race ./... diff --git a/Makefile b/Makefile index 79c2a0c..2e0a348 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,12 @@ -postgres: - docker run --name metric_db -e POSTGRES_USER=metric_db -e POSTGRES_PASSWORD=metric_db -p 5434:5432 -d postgres:alpine -postgresrm: - docker stop metric_db - docker rm metric_db - -migrateup: - migrate -path internal/adapter/db/postgres/migration -database "postgres://metric_db:metric_db@localhost:5434/metric_db?sslmode=disable" -verbose up - -migratedown: - migrate -path internal/adapter/db/postgres/migration -database "postgres://metric_db:metric_db@localhost:5434/metric_db?sslmode=disable" -verbose down +postgres: + docker run --name metric_db -e POSTGRES_USER=metric_db -e POSTGRES_PASSWORD=metric_db -p 5434:5432 -d postgres:alpine +postgresrm: + docker stop metric_db + docker rm metric_db + +migrateup: + migrate -path internal/adapter/db/postgres/migration -database "postgres://metric_db:metric_db@localhost:5434/metric_db?sslmode=disable" -verbose up + +migratedown: + migrate -path internal/adapter/db/postgres/migration -database "postgres://metric_db:metric_db@localhost:5434/metric_db?sslmode=disable" -verbose down .PHONY: postgres postgresrm migrateup migratedown \ No newline at end of file diff --git a/cmd/server/main.go b/cmd/server/main.go index 280784c..ff604c4 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -9,25 +9,126 @@ import ( "os/signal" "sync" "syscall" - "time" + // "time" _ "net/http/pprof" - "github.com/The-Gleb/go_metrics_and_alerting/internal/app" - "github.com/The-Gleb/go_metrics_and_alerting/internal/filestorage" - "github.com/The-Gleb/go_metrics_and_alerting/internal/handlers" + // "github.com/The-Gleb/go_metrics_and_alerting/internal/app" + // "github.com/The-Gleb/go_metrics_and_alerting/internal/compressor" + v1 "github.com/The-Gleb/go_metrics_and_alerting/internal/controller/http/v1/handler" + "github.com/The-Gleb/go_metrics_and_alerting/internal/controller/http/v1/middleware" + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/service" + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/usecase" + // "github.com/The-Gleb/go_metrics_and_alerting/internal/filestorage" + // "github.com/The-Gleb/go_metrics_and_alerting/internal/handlers" "github.com/The-Gleb/go_metrics_and_alerting/internal/logger" - "github.com/The-Gleb/go_metrics_and_alerting/internal/repositories" - "github.com/The-Gleb/go_metrics_and_alerting/internal/repositories/database" - "github.com/The-Gleb/go_metrics_and_alerting/internal/repositories/memory" - "github.com/The-Gleb/go_metrics_and_alerting/internal/server" - "github.com/The-Gleb/go_metrics_and_alerting/pkg/utils/retry" + // "github.com/The-Gleb/go_metrics_and_alerting/internal/repositories" + "github.com/The-Gleb/go_metrics_and_alerting/internal/repository/database" + "github.com/The-Gleb/go_metrics_and_alerting/internal/repository/memory" + // "github.com/The-Gleb/go_metrics_and_alerting/internal/repository" + "github.com/The-Gleb/go_metrics_and_alerting/internal/repository/file_storage" + // "github.com/The-Gleb/go_metrics_and_alerting/internal/server" + // "github.com/The-Gleb/go_metrics_and_alerting/pkg/utils/retry" + "github.com/go-chi/chi/v5" ) // postgres://metric_db:metric_db@localhost:5434/metric_db?sslmode=disable // TODO: fix status in logger func main() { + + if err := Run(); err != nil { + log.Fatal(err) + } + + // go func() { + // log.Println(http.ListenAndServe("localhost:6060", nil)) + // }() + + // config := NewConfigFromFlags() + + // if err := logger.Initialize(config.LogLevel); err != nil { + // logger.Log.Fatal(err) + // return + // } + // logger.Log.Info(config) + + // var repository repositories.Repositiries + // var fileStorage app.FileStorage + + // if config.FileStoragePath != "" { + // repository = memory.New() + // fileStorage = filestorage.NewFileStorage(config.FileStoragePath, config.StoreInterval, config.Restore) + // } + + // if config.DatabaseDSN != "" { + // var db *database.DB + // var err error + // err = retry.DefaultRetry( + // context.Background(), + // func(ctx context.Context) error { + // db, err = database.ConnectDB(config.DatabaseDSN) + // return err + // }, + // ) + + // if err != nil { + // logger.Log.Fatal(err) + // return + // } + // repository = db + // } + + // app := app.NewApp(repository, fileStorage) + // handlers := handlers.New(app) + // s := server.NewWithProfiler(config.Addres, handlers, []byte(config.SignKey)) + + // if config.Restore { + // app.LoadDataFromFile(context.Background()) + // } + + // var wg sync.WaitGroup + // ctx, cancel := context.WithCancel(context.Background()) + + // if config.StoreInterval > 0 && config.DatabaseDSN == "" { + // saveTicker := time.NewTicker(time.Duration(config.StoreInterval) * time.Second) + // wg.Add(1) + // go func() { + // defer wg.Done() + // for { + // select { + // case <-saveTicker.C: + // app.StoreDataToFile(context.Background()) + // case <-ctx.Done(): + // logger.Log.Debug("stop saving to file") + // return + // } + // } + + // }() + // } + + // wg.Add(1) + // go func() { + // defer wg.Done() + // ServerShutdownSignal := make(chan os.Signal, 1) + // signal.Notify(ServerShutdownSignal, syscall.SIGINT) + // <-ServerShutdownSignal + // s.Shutdown(context.Background()) + // cancel() + // }() + + // err := server.Run(s) + // if err != nil && err != http.ErrServerClosed { + // panic(err) + // } + // os. + // wg.Wait() + // logger.Log.Info("server shutdown") +} + +func Run() error { + go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() @@ -36,64 +137,64 @@ func main() { if err := logger.Initialize(config.LogLevel); err != nil { logger.Log.Fatal(err) - return + return err } logger.Log.Info(config) - var repository repositories.Repositiries - var fileStorage app.FileStorage + var repository service.MetricStorage + var fileStorage service.FileStorage if config.FileStoragePath != "" { - repository = memory.New() - fileStorage = filestorage.NewFileStorage(config.FileStoragePath, config.StoreInterval, config.Restore) + fileStorage = file_storage.NewFileStorage(config.FileStoragePath) } if config.DatabaseDSN != "" { - var db *database.DB - var err error - err = retry.DefaultRetry( - context.Background(), - func(ctx context.Context) error { - db, err = database.ConnectDB(config.DatabaseDSN) - return err - }, - ) + db, err := database.ConnectDB(config.DatabaseDSN) if err != nil { logger.Log.Fatal(err) - return + return err } repository = db + } else { + repository = memory.New() } - app := app.NewApp(repository, fileStorage) - handlers := handlers.New(app) - s := server.NewWithProfiler(config.Addres, handlers, []byte(config.SignKey)) - - if config.Restore { - app.LoadDataFromFile(context.Background()) + metricServie := service.NewMetricService(repository) + backupService := service.NewBackupService(repository, fileStorage, config.StoreInterval, config.Restore) + + updateMetricUsecase := usecase.NewUpdateMetricUsecase(metricServie, backupService) + updateMetricSetUsecase := usecase.NewUpdateMetricSetUsecase(metricServie, backupService) + getMetricUsecase := usecase.NewGetMetricUsecase(metricServie) + getAllMetricsUsecase := usecase.NewGetAllMetricsUsecase(metricServie) + + updateMetricHandler := v1.NewUpdateMetricHandler(updateMetricUsecase) + updateMetricJSONHandler := v1.NewUpdateMetricJSONHandler(updateMetricUsecase) + getMetricHandler := v1.NewGetMetricHandler(getMetricUsecase) + getMetricJSONHandler := v1.NewGetMetricJSONHandler(getMetricUsecase) + updateMetricSetHandler := v1.NewUpdateMetricSetHandler(updateMetricSetUsecase) + getAllMetricsHandler := v1.NewGetAllMetricsHandler(getAllMetricsUsecase) + + gzipMiddleware := middleware.NewGzipMiddleware() + checkSignatureMiddleware := middleware.NewCheckSignatureMiddleware([]byte(config.SignKey)) + loggerMidleware := middleware.NewLoggerMiddleware(logger.Log) + + r := chi.NewMux() + r.Use(gzipMiddleware.Do, checkSignatureMiddleware.Do, loggerMidleware.Do) + + updateMetricHandler.AddToRouter(r) + updateMetricJSONHandler.AddToRouter(r) + getMetricHandler.AddToRouter(r) + getMetricJSONHandler.AddToRouter(r) + updateMetricSetHandler.AddToRouter(r) + getAllMetricsHandler.AddToRouter(r) + + s := http.Server{ + Addr: config.Addres, + Handler: r, } var wg sync.WaitGroup - ctx, cancel := context.WithCancel(context.Background()) - - if config.StoreInterval > 0 && config.DatabaseDSN == "" { - saveTicker := time.NewTicker(time.Duration(config.StoreInterval) * time.Second) - wg.Add(1) - go func() { - defer wg.Done() - for { - select { - case <-saveTicker.C: - app.StoreDataToFile(context.Background()) - case <-ctx.Done(): - logger.Log.Debug("stop saving to file") - return - } - } - - }() - } wg.Add(1) go func() { @@ -101,15 +202,17 @@ func main() { ServerShutdownSignal := make(chan os.Signal, 1) signal.Notify(ServerShutdownSignal, syscall.SIGINT) <-ServerShutdownSignal - s.Shutdown(context.Background()) - cancel() + err := s.Shutdown(context.Background()) + if err != nil { + panic(err) + } + logger.Log.Info("server shutdown") }() - err := server.Run(s) - if err != nil && err != http.ErrServerClosed { - panic(err) + logger.Log.Info("starting server") + if err := s.ListenAndServe(); err != nil { + logger.Log.Error("server error", "error", err) } - wg.Wait() - logger.Log.Info("server shutdown") + return nil } diff --git a/internal/app/app.go b/internal/app/app.go index a029203..74e9c79 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -11,7 +11,7 @@ import ( "github.com/The-Gleb/go_metrics_and_alerting/internal/logger" "github.com/The-Gleb/go_metrics_and_alerting/internal/models" - "github.com/The-Gleb/go_metrics_and_alerting/internal/repositories" + "github.com/The-Gleb/go_metrics_and_alerting/internal/repository" "github.com/The-Gleb/go_metrics_and_alerting/pkg/utils/retry" ) @@ -26,12 +26,12 @@ type FileStorage interface { } type app struct { - storage repositories.Repositiries + storage repository.Repositiries fileStorage FileStorage } // TODO: add FileWriter -func NewApp(s repositories.Repositiries, fs FileStorage) *app { +func NewApp(s repository.Repositiries, fs FileStorage) *app { return &app{ storage: s, fileStorage: fs, diff --git a/internal/controller/http/handler.go b/internal/controller/http/handler.go new file mode 100644 index 0000000..cd5f8e2 --- /dev/null +++ b/internal/controller/http/handler.go @@ -0,0 +1,12 @@ +package http + +import ( + "net/http" + + "github.com/go-chi/chi/v5" +) + +type Handler interface { + AddToRouter(*chi.Mux) + Middlewares(md ...func(http.Handler) http.Handler) *Handler +} diff --git a/internal/controller/http/middleware.go b/internal/controller/http/middleware.go new file mode 100644 index 0000000..bca8564 --- /dev/null +++ b/internal/controller/http/middleware.go @@ -0,0 +1,7 @@ +package http + +import "net/http" + +type Middleware interface { + Handler(http.Handler) http.Handler +} diff --git a/internal/controller/http/v1/handler/get_all_metrics.go b/internal/controller/http/v1/handler/get_all_metrics.go new file mode 100644 index 0000000..0ef67d8 --- /dev/null +++ b/internal/controller/http/v1/handler/get_all_metrics.go @@ -0,0 +1,56 @@ +package v1 + +import ( + "context" + "fmt" + "net/http" + + "github.com/go-chi/chi/v5" +) + +const ( + getAllMetricsURL = "/" +) + +type GetAllMetricsUsecase interface { + GetAllMetricsJSON(ctx context.Context) ([]byte, error) + GetAllMetricsHTML(ctx context.Context) ([]byte, error) +} + +type getAllMetricsHandler struct { + usecase GetAllMetricsUsecase + middlewares []func(http.Handler) http.Handler +} + +func NewGetAllMetricsHandler(usecase GetAllMetricsUsecase) *getAllMetricsHandler { + return &getAllMetricsHandler{ + usecase: usecase, + middlewares: make([]func(http.Handler) http.Handler, 0), + } +} + +func (h *getAllMetricsHandler) AddToRouter(r *chi.Mux) { + r.Route(getAllMetricsURL, func(r chi.Router) { + r.Use(h.middlewares...) + r.Get("/", h.ServeHTTP) + }) +} + +func (h *getAllMetricsHandler) Middlewares(md ...func(http.Handler) http.Handler) *getAllMetricsHandler { + h.middlewares = append(h.middlewares, md...) + return h +} + +func (h *getAllMetricsHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + + body, err := h.usecase.GetAllMetricsJSON(r.Context()) + if err != nil { + err = fmt.Errorf("getAllMetricsHandler: %w", err) // TODO: handler errors + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + rw.Header().Set("Content-Type", "application/json") + rw.Write(body) + +} diff --git a/internal/controller/http/v1/handler/get_all_metrics_test.go b/internal/controller/http/v1/handler/get_all_metrics_test.go new file mode 100644 index 0000000..88a7383 --- /dev/null +++ b/internal/controller/http/v1/handler/get_all_metrics_test.go @@ -0,0 +1,132 @@ +package v1 + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + + "os" + + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/entity" + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/service" + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/usecase" + "github.com/The-Gleb/go_metrics_and_alerting/internal/repository/memory" + "github.com/go-chi/chi/v5" + "github.com/stretchr/testify/require" +) + +func testRequest(t *testing.T, ts *httptest.Server, method, + path string, body []byte) (*http.Response, string) { + req, err := http.NewRequest(method, ts.URL+path, bytes.NewReader(body)) + require.NoError(t, err) + + resp, err := ts.Client().Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + return resp, string(respBody) +} + +func Test_getAllMetricHandler_ServeHTTP(t *testing.T) { + var val1 float64 = 3782369280 + var val2 int64 = 123 + metrics := []entity.Metric{ + { + MType: "gauge", + ID: "HeapAlloc", + Value: &val1, + }, + { + MType: "counter", + ID: "PollCount", + Delta: &val2, + }, + } + + metricMaps := entity.MetricsMaps{ + Gauge: metrics[:1], + Counter: metrics[1:], + } + + jsonMetrics, err := json.Marshal(metricMaps) + require.NoError(t, err) + + s := memory.New() + metricService := service.NewMetricService(s) + metricService.UpdateMetricSet(context.Background(), metrics) + getAllMetricsUsecase := usecase.NewGetAllMetricsUsecase(metricService) + getAllMetricsHandler := NewGetAllMetricsHandler(getAllMetricsUsecase) + + router := chi.NewRouter() + getAllMetricsHandler.AddToRouter(router) + ts := httptest.NewServer(router) + defer ts.Close() + + // validJsonBody := `[{"id": "HeapAlloc","type": "gauge","value": 3782369280},{"id": "PollCount","type": "counter","delta": 123}]` + + type want struct { + code int + } + tests := []struct { + name string + want want + }{ + { + name: "normal", + want: want{200}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + resp, b := testRequest(t, ts, "GET", "/", nil) + defer resp.Body.Close() + + t.Log(b) + + require.Equal(t, tt.want.code, resp.StatusCode) + if tt.want.code != 200 { + return + } + + require.Equal(t, string(jsonMetrics), b) + }) + } +} + +func Example_getAllMetricHandler_ServeHTTP() { + + s := memory.New() + s.UpdateMetric("gauge", "Alloc", "123.4") + s.UpdateMetric("counter", "PollCount", "12") + metricService := service.NewMetricService(s) + getAllMetricsUsecase := usecase.NewGetAllMetricsUsecase(metricService) + getAllMetricsHandler := NewGetAllMetricsHandler(getAllMetricsUsecase) + + router := chi.NewRouter() + getAllMetricsHandler.AddToRouter(router) + ts := httptest.NewServer(router) + defer ts.Close() + + req, _ := http.NewRequest("GET", ts.URL+"/", nil) + + resp, _ := ts.Client().Do(req) + + fmt.Println(resp.StatusCode) + b, _ := io.ReadAll(resp.Body) + fmt.Fprintln(os.Stdout, []any{string(b)}...) + + // Output: + // 200 + // {"Gauge":[{"id":"Alloc","type":"gauge","value":123.4}],"Counter":[{"id":"PollCount","type":"counter","delta":12}]} + +} diff --git a/internal/controller/http/v1/handler/get_metric.go b/internal/controller/http/v1/handler/get_metric.go new file mode 100644 index 0000000..735fae4 --- /dev/null +++ b/internal/controller/http/v1/handler/get_metric.go @@ -0,0 +1,86 @@ +package v1 + +import ( + "context" + "errors" + "net/http" + "strconv" + + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/entity" + "github.com/The-Gleb/go_metrics_and_alerting/internal/repository" + "github.com/go-chi/chi/v5" +) + +const ( + getMetricURL = "/value/{type}/{name}" +) + +type GetMetricUsecase interface { + GetMetric(ctx context.Context, metrics entity.Metric) (entity.Metric, error) +} + +type getMetricHandler struct { + usecase GetMetricUsecase + middlewares []func(http.Handler) http.Handler +} + +func NewGetMetricHandler(usecase GetMetricUsecase) *getMetricHandler { + return &getMetricHandler{ + usecase: usecase, + middlewares: make([]func(http.Handler) http.Handler, 0), + } +} + +func (h *getMetricHandler) AddToRouter(r *chi.Mux) { + r.Route(getMetricURL, func(r chi.Router) { + r.Use(h.middlewares...) + r.Get("/", h.ServeHTTP) + }) +} + +func (h *getMetricHandler) Middlewares(md ...func(http.Handler) http.Handler) *getMetricHandler { + h.middlewares = append(h.middlewares, md...) + return h +} + +func (h *getMetricHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + + mType := chi.URLParam(r, "type") + mName := chi.URLParam(r, "name") + metric := entity.Metric{ + MType: mType, + ID: mName, + } + + metric, err := h.usecase.GetMetric(r.Context(), metric) + if err != nil { + if errors.Is(err, repository.ErrNotFound) { + rw.WriteHeader(http.StatusNotFound) + http.Error(rw, err.Error(), http.StatusNotFound) + return + } else { + rw.WriteHeader(http.StatusBadRequest) + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + } + + switch mType { + case "gauge": + + strVal := strconv.FormatFloat(*metric.Value, 'g', -1, 64) + rw.Write([]byte(strVal)) + + case "counter": + + strVal := strconv.FormatInt(*metric.Delta, 10) + rw.Write([]byte(strVal)) + + default: + + http.Error(rw, "invalid metric type", http.StatusBadRequest) + return + + } + +} diff --git a/internal/controller/http/v1/handler/get_metric_json.go b/internal/controller/http/v1/handler/get_metric_json.go new file mode 100644 index 0000000..f3adc39 --- /dev/null +++ b/internal/controller/http/v1/handler/get_metric_json.go @@ -0,0 +1,76 @@ +package v1 + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/entity" + "github.com/The-Gleb/go_metrics_and_alerting/internal/logger" + "github.com/The-Gleb/go_metrics_and_alerting/internal/repository" + "github.com/go-chi/chi/v5" +) + +const ( + getMetricJSONURL = "/value" +) + +type getMetricJSONHandler struct { + usecase GetMetricUsecase + middlewares []func(http.Handler) http.Handler +} + +func NewGetMetricJSONHandler(usecase GetMetricUsecase) *getMetricJSONHandler { + return &getMetricJSONHandler{ + usecase: usecase, + middlewares: make([]func(http.Handler) http.Handler, 0), + } +} + +func (h *getMetricJSONHandler) AddToRouter(r *chi.Mux) { + r.Route(getMetricJSONURL, func(r chi.Router) { + r.Use(h.middlewares...) + r.Post("/", h.ServeHTTP) + }) +} + +func (h *getMetricJSONHandler) Middlewares(md ...func(http.Handler) http.Handler) *getMetricJSONHandler { + h.middlewares = append(h.middlewares, md...) + return h +} + +func (h *getMetricJSONHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + + var metric entity.Metric + err := json.NewDecoder(r.Body).Decode(&metric) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + metric, err = h.usecase.GetMetric(r.Context(), metric) + if err != nil { + err = fmt.Errorf("handlers.GetMetricJSON: %w", err) + logger.Log.Error(err) + + if errors.Is(err, repository.ErrNotFound) { + rw.WriteHeader(http.StatusNotFound) + http.Error(rw, err.Error(), http.StatusNotFound) + return + } else { + rw.WriteHeader(http.StatusBadRequest) + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + } + + b, err := json.Marshal(metric) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + rw.Write(b) + +} diff --git a/internal/controller/http/v1/handler/get_metric_json_test.go b/internal/controller/http/v1/handler/get_metric_json_test.go new file mode 100644 index 0000000..13c7044 --- /dev/null +++ b/internal/controller/http/v1/handler/get_metric_json_test.go @@ -0,0 +1,66 @@ +package v1 + +import ( + "bytes" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/service" + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/usecase" + "github.com/The-Gleb/go_metrics_and_alerting/internal/repository/memory" + "github.com/go-chi/chi/v5" +) + +func Test_getMetricJSONHandler_ServeHTTP(t *testing.T) { + type args struct { + rw http.ResponseWriter + r *http.Request + } + tests := []struct { + name string + h *getMetricJSONHandler + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.h.ServeHTTP(tt.args.rw, tt.args.r) + }) + } +} + +func Example_getMetricJSONHandler_ServeHTTP() { + + s := memory.New() + s.UpdateMetric("gauge", "Alloc", "3782369280") + metricService := service.NewMetricService(s) + getMetricUsecase := usecase.NewGetMetricUsecase(metricService) + getMetricJSONHandler := NewGetMetricJSONHandler(getMetricUsecase) + + router := chi.NewRouter() + getMetricJSONHandler.AddToRouter(router) + ts := httptest.NewServer(router) + defer ts.Close() + + validBody := `{ + "id": "Alloc", + "type": "gauge" + }` + + req, _ := http.NewRequest("POST", ts.URL+"/value", bytes.NewReader([]byte(validBody))) + + resp, _ := ts.Client().Do(req) + + fmt.Println(resp.StatusCode) + b, _ := io.ReadAll(resp.Body) + fmt.Println(string(b)) + + // Output: + // 200 + // {"id":"Alloc","type":"gauge","value":3782369280} + +} diff --git a/internal/controller/http/v1/handler/get_metric_test.go b/internal/controller/http/v1/handler/get_metric_test.go new file mode 100644 index 0000000..8404b41 --- /dev/null +++ b/internal/controller/http/v1/handler/get_metric_test.go @@ -0,0 +1,113 @@ +package v1 + +import ( + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/service" + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/usecase" + "github.com/The-Gleb/go_metrics_and_alerting/internal/repository/memory" + "github.com/go-chi/chi/v5" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_getMetricHandler_ServeHTTP(t *testing.T) { + s := memory.New() + s.UpdateMetric("gauge", "Alloc", "123.4") + s.UpdateMetric("counter", "Counter", "123") + metricService := service.NewMetricService(s) + getMetricUsecase := usecase.NewGetMetricUsecase(metricService) + getMetricHandler := NewGetMetricHandler(getMetricUsecase) + + router := chi.NewRouter() + getMetricHandler.AddToRouter(router) + ts := httptest.NewServer(router) + defer ts.Close() + + type want struct { + value string + code int + } + tests := []struct { + name string + address string + want want + }{ + { + name: "normal gauge test #1", + address: "/value/gauge/Alloc", + want: want{ + value: "123.4", + code: 200, + }, + }, + { + name: "normal counter test #2", + address: "/value/counter/Counter", + want: want{ + value: "123", + code: 200, + }, + }, + { + name: "neg counter test #3", + address: "/value/counter/erff", + want: want{ + value: "", + code: 404, + }, + }, + { + name: "wrong metric type test #4", + address: "/value/ssds/erff", + want: want{ + value: "", + code: 400, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp, val := testRequest(t, ts, "GET", tt.address, nil) + defer resp.Body.Close() + + require.Equal(t, tt.want.code, resp.StatusCode) + if tt.want.code != 200 { + return + } + assert.Equal(t, tt.want.value, string(val)) + + }) + } +} + +func Example_getMetricHandler_ServeHTTP() { + + s := memory.New() + s.UpdateMetric("gauge", "Alloc", "123.4") + metricService := service.NewMetricService(s) + getMetricUsecase := usecase.NewGetMetricUsecase(metricService) + getMetricHandler := NewGetMetricHandler(getMetricUsecase) + + router := chi.NewRouter() + getMetricHandler.AddToRouter(router) + ts := httptest.NewServer(router) + defer ts.Close() + + req, _ := http.NewRequest("GET", ts.URL+"/value/gauge/Alloc", nil) + + resp, _ := ts.Client().Do(req) + + fmt.Println(resp.StatusCode) + b, _ := io.ReadAll(resp.Body) + fmt.Println(string(b)) + + // Output: + // 200 + // 123.4 + +} diff --git a/internal/controller/http/v1/handler/update_metric.go b/internal/controller/http/v1/handler/update_metric.go new file mode 100644 index 0000000..c9e0ff9 --- /dev/null +++ b/internal/controller/http/v1/handler/update_metric.go @@ -0,0 +1,114 @@ +package v1 + +import ( + "context" + "errors" + "fmt" + "net/http" + "strconv" + + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/entity" + "github.com/The-Gleb/go_metrics_and_alerting/internal/repository" + "github.com/go-chi/chi/v5" +) + +const ( + updateMetricURL = "/update/{type}/{name}/{value}" +) + +type UpdateMetricUsecase interface { + UpdateMetric(ctx context.Context, metrics entity.Metric) (entity.Metric, error) +} + +type updateMetricHandler struct { + usecase UpdateMetricUsecase + middlewares []func(http.Handler) http.Handler +} + +func NewUpdateMetricHandler(usecase UpdateMetricUsecase) *updateMetricHandler { + return &updateMetricHandler{ + usecase: usecase, + middlewares: make([]func(http.Handler) http.Handler, 0), + } +} + +func (h *updateMetricHandler) AddToRouter(r *chi.Mux) { + r.Route(updateMetricURL, func(r chi.Router) { + r.Use(h.middlewares...) + r.Post("/", h.ServeHTTP) + }) +} + +func (h *updateMetricHandler) Middlewares(md ...func(http.Handler) http.Handler) *updateMetricHandler { + h.middlewares = append(h.middlewares, md...) + return h +} + +func (h *updateMetricHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + + mType := chi.URLParam(r, "type") + mName := chi.URLParam(r, "name") + metric := entity.Metric{ + MType: mType, + ID: mName, + } + + switch mType { + case "gauge": + val, err := strconv.ParseFloat(chi.URLParam(r, "value"), 64) + if err != nil { + err = fmt.Errorf("getMetricHandler: %w", err) + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + metric.Value = &val + + metric, err = h.usecase.UpdateMetric(r.Context(), metric) + if err != nil { + if errors.Is(err, repository.ErrNotFound) { + http.Error(rw, err.Error(), http.StatusNotFound) + return + } else { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + } + + // strVal := strconv.FormatFloat(*metric.Value, 'g', -1, 64) + // rw.Write([]byte(strVal)) + + rw.Write([]byte(chi.URLParam(r, "value"))) + + case "counter": + delta, err := strconv.ParseInt(chi.URLParam(r, "value"), 10, 64) + if err != nil { + err = fmt.Errorf("getMetricHandler: %w", err) + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + metric.Delta = &delta + + metric, err = h.usecase.UpdateMetric(r.Context(), metric) + if err != nil { + if errors.Is(err, repository.ErrNotFound) { + rw.WriteHeader(http.StatusNotFound) + http.Error(rw, err.Error(), http.StatusNotFound) + return + } else { + rw.WriteHeader(http.StatusBadRequest) + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + } + + strVal := strconv.FormatInt(*metric.Delta, 10) + rw.Write([]byte(strVal)) + + // rw.Write([]byte(chi.URLParam(r, "value"))) + + default: + http.Error(rw, "invalid metric type", http.StatusBadRequest) + return + } + +} diff --git a/internal/controller/http/v1/handler/update_metric_json.go b/internal/controller/http/v1/handler/update_metric_json.go new file mode 100644 index 0000000..78388b4 --- /dev/null +++ b/internal/controller/http/v1/handler/update_metric_json.go @@ -0,0 +1,64 @@ +package v1 + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/entity" + "github.com/go-chi/chi/v5" +) + +const ( + updateMetricJSONURL = "/update" +) + +type updateMetricJSONHandler struct { + usecase UpdateMetricUsecase + middlewares []func(http.Handler) http.Handler +} + +func NewUpdateMetricJSONHandler(usecase UpdateMetricUsecase) *updateMetricJSONHandler { + return &updateMetricJSONHandler{ + usecase: usecase, + middlewares: make([]func(http.Handler) http.Handler, 0), + } +} + +func (h *updateMetricJSONHandler) AddToRouter(r *chi.Mux) { + r.Route(updateMetricJSONURL, func(r chi.Router) { + r.Use(h.middlewares...) + r.Post("/", h.ServeHTTP) + }) +} + +func (h *updateMetricJSONHandler) Middlewares(md ...func(http.Handler) http.Handler) *updateMetricJSONHandler { + h.middlewares = append(h.middlewares, md...) + return h +} + +func (h *updateMetricJSONHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + + var metric entity.Metric + err := json.NewDecoder(r.Body).Decode(&metric) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + metric, err = h.usecase.UpdateMetric(r.Context(), metric) + if err != nil { + err = fmt.Errorf("updateMetricJSONHandler: %w", err) + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + b, err := json.Marshal(metric) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + rw.Write(b) + +} diff --git a/internal/controller/http/v1/handler/update_metric_json_test.go b/internal/controller/http/v1/handler/update_metric_json_test.go new file mode 100644 index 0000000..efa30ad --- /dev/null +++ b/internal/controller/http/v1/handler/update_metric_json_test.go @@ -0,0 +1,46 @@ +package v1 + +import ( + "bytes" + "fmt" + "io" + "net/http" + "net/http/httptest" + + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/service" + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/usecase" + "github.com/The-Gleb/go_metrics_and_alerting/internal/repository/memory" + "github.com/go-chi/chi/v5" +) + +func Example_updateMetricJSONHandler_ServeHTTP() { + + s := memory.New() + metricService := service.NewMetricService(s) + updateMetricUsecase := usecase.NewUpdateMetricUsecase(metricService, nil) + updateMetricJSONHandler := NewUpdateMetricJSONHandler(updateMetricUsecase) + + router := chi.NewRouter() + updateMetricJSONHandler.AddToRouter(router) + ts := httptest.NewServer(router) + defer ts.Close() + + validBody := `{ + "id": "Alloc", + "type": "gauge", + "value": 123.123 + }` + + req, _ := http.NewRequest("POST", ts.URL+"/update", bytes.NewReader([]byte(validBody))) + + resp, _ := ts.Client().Do(req) + + fmt.Println(resp.StatusCode) + b, _ := io.ReadAll(resp.Body) + fmt.Println(string(b)) + + // Output: + // 200 + // {"id":"Alloc","type":"gauge","value":123.123} + +} diff --git a/internal/controller/http/v1/handler/update_metric_set.go b/internal/controller/http/v1/handler/update_metric_set.go new file mode 100644 index 0000000..fc7c58d --- /dev/null +++ b/internal/controller/http/v1/handler/update_metric_set.go @@ -0,0 +1,65 @@ +package v1 + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strconv" + + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/entity" + "github.com/go-chi/chi/v5" +) + +const ( + updateMetricSetURL = "/updates/" +) + +type UpdateMetricSetUsecase interface { + UpdateMetricSet(ctx context.Context, metrics []entity.Metric) (int64, error) +} + +type updateMetricSetHandler struct { + usecase UpdateMetricSetUsecase + middlewares []func(http.Handler) http.Handler +} + +func NewUpdateMetricSetHandler(usecase UpdateMetricSetUsecase) *updateMetricSetHandler { + return &updateMetricSetHandler{ + usecase: usecase, + middlewares: make([]func(http.Handler) http.Handler, 0), + } +} + +func (h *updateMetricSetHandler) AddToRouter(r *chi.Mux) { + r.Route(updateMetricSetURL, func(r chi.Router) { + r.Use(h.middlewares...) + r.Post("/", h.ServeHTTP) + }) +} + +func (h *updateMetricSetHandler) Middlewares(md ...func(http.Handler) http.Handler) *updateMetricSetHandler { + h.middlewares = append(h.middlewares, md...) + return h +} + +func (h *updateMetricSetHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + + var metrics []entity.Metric + err := json.NewDecoder(r.Body).Decode(&metrics) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + n, err := h.usecase.UpdateMetricSet(r.Context(), metrics) + if err != nil { + err = fmt.Errorf("updateMetricSetHandler: %w", err) + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + updatedMetricsCount := strconv.FormatInt(int64(n), 10) + rw.Write([]byte(updatedMetricsCount + " metrics updated")) + +} diff --git a/internal/controller/http/v1/handler/update_metric_set_test.go b/internal/controller/http/v1/handler/update_metric_set_test.go new file mode 100644 index 0000000..dbc61c9 --- /dev/null +++ b/internal/controller/http/v1/handler/update_metric_set_test.go @@ -0,0 +1,72 @@ +package v1 + +import ( + "encoding/json" + "net/http/httptest" + "testing" + + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/service" + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/usecase" + "github.com/The-Gleb/go_metrics_and_alerting/internal/repository/memory" + "github.com/go-chi/chi/v5" + "github.com/stretchr/testify/require" +) + +func Test_updateMetricSetHandler_ServeHTTP(t *testing.T) { + s := memory.New() + metricService := service.NewMetricService(s) + updateMetricSetUsecase := usecase.NewUpdateMetricSetUsecase(metricService, nil) + updateMetricSetHandler := NewUpdateMetricSetHandler(updateMetricSetUsecase) + + router := chi.NewRouter() + updateMetricSetHandler.AddToRouter(router) + ts := httptest.NewServer(router) + defer ts.Close() + + validJsonBody := `[ + { + "id": "HeapAlloc", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "PollCount", + "type": "counter", + "delta": 123 + }]` + + type want struct { + code int + } + tests := []struct { + name string + body json.RawMessage + want want + }{ + { + name: "normal", + body: json.RawMessage(validJsonBody), + want: want{200}, + }, + { + name: "request with invalid body", + body: json.RawMessage([]byte("some invalid body")), + want: want{400}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + resp, b := testRequest(t, ts, "POST", "/updates/", tt.body) + defer resp.Body.Close() + + t.Log(b) + + require.Equal(t, tt.want.code, resp.StatusCode) + if tt.want.code != 200 { + return + } + }) + } +} diff --git a/internal/controller/http/v1/handler/update_metric_test.go b/internal/controller/http/v1/handler/update_metric_test.go new file mode 100644 index 0000000..6aa0784 --- /dev/null +++ b/internal/controller/http/v1/handler/update_metric_test.go @@ -0,0 +1,142 @@ +package v1 + +import ( + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/service" + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/usecase" + "github.com/The-Gleb/go_metrics_and_alerting/internal/repository/memory" + "github.com/go-chi/chi/v5" + "github.com/stretchr/testify/require" +) + +func Test_updateMetricHandler_ServeHTTP(t *testing.T) { + s := memory.New() + metricService := service.NewMetricService(s) + updateMetricUsecase := usecase.NewUpdateMetricUsecase(metricService, nil) + updateMetricHandler := NewUpdateMetricHandler(updateMetricUsecase) + + router := chi.NewRouter() + updateMetricHandler.AddToRouter(router) + ts := httptest.NewServer(router) + defer ts.Close() + + type want struct { + code int + } + tests := []struct { + name string + address string + mType string + mName string + mValue string + want want + }{ + { + name: "normal gauge test #1", + address: "/update/gauge/Alloc/23.23", + mType: "gauge", + mName: "Alloc", + mValue: "23.23", + want: want{ + code: 200, + }, + }, + { + name: "first add counter test #2", + address: "/update/counter/counter/23", + mType: "counter", + mName: "counter", + mValue: "23", + want: want{ + code: 200, + }, + }, + { + name: "second add counter test #3", + address: "/update/counter/counter/7", + mType: "counter", + mName: "counter", + mValue: "30", + want: want{ + code: 200, + }, + }, + { + name: "name and value not sent - test #4", + address: "/update/gauge", + want: want{ + code: 404, + }, + }, + { + name: "value not sent - test #5", + address: "/update/gauge/nbhj", + want: want{ + code: 404, + }, + }, + { + name: "wrong metric type- test #5", + address: "/update/gaunjh/efvefv/eefe", + want: want{ + code: 400, + }, + }, + { + name: "incorrect metric value type - test #6", + address: "/update/gauge/alloc/string", + want: want{ + code: 400, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + resp, body := testRequest(t, ts, "POST", tt.address, nil) + defer resp.Body.Close() + + require.Equal(t, tt.want.code, resp.StatusCode) + if tt.want.code != 200 { + return + } + + require.Equal(t, tt.mValue, body) + + // val, _ := h.app.GetMetricFromParams(context.Background(), tt.mType, tt.mName) + // assert.Equal(t, tt.mValue, string(val)) + + }) + } +} + +func Example_updateMetricHandler_ServeHTTP() { + + s := memory.New() + metricService := service.NewMetricService(s) + updateMetricUsecase := usecase.NewUpdateMetricUsecase(metricService, nil) + updateMetricHandler := NewUpdateMetricHandler(updateMetricUsecase) + + router := chi.NewRouter() + updateMetricHandler.AddToRouter(router) + ts := httptest.NewServer(router) + defer ts.Close() + + req, _ := http.NewRequest("POST", ts.URL+"/update/gauge/Alloc/12.12", nil) + + resp, _ := ts.Client().Do(req) + + fmt.Println(resp.StatusCode) + b, _ := io.ReadAll(resp.Body) + fmt.Println(string(b)) + + // Output: + // 200 + // 12.12 + +} diff --git a/internal/controller/http/v1/middleware/check_signature.go b/internal/controller/http/v1/middleware/check_signature.go new file mode 100644 index 0000000..096a2c9 --- /dev/null +++ b/internal/controller/http/v1/middleware/check_signature.go @@ -0,0 +1,93 @@ +package middleware + +import ( + "bytes" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "io" + "net/http" + + "github.com/The-Gleb/go_metrics_and_alerting/internal/logger" +) + +type checkSignatureMiddleware struct { + signKey []byte +} + +func NewCheckSignatureMiddleware(signKey []byte) *checkSignatureMiddleware { + return &checkSignatureMiddleware{signKey: signKey} +} + +type signingResponseWriter struct { + http.ResponseWriter + key []byte +} + +func (w *signingResponseWriter) Write(b []byte) (int, error) { + h := hmac.New(sha256.New, w.key) + _, err := h.Write(b) + if err != nil { + http.Error(w.ResponseWriter, err.Error(), http.StatusInternalServerError) + } + sign := h.Sum(nil) + encodedSign := hex.EncodeToString(sign) + w.ResponseWriter.Header().Set("HashSHA256", encodedSign) + n, err := w.ResponseWriter.Write(b) + return n, err +} + +func (md *checkSignatureMiddleware) Do(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + if len(md.signKey) == 0 || r.Header.Get("Hash") == "none" || r.Header.Get("Hashsha256") == "" { + logger.Log.Debug("key is empty") + next.ServeHTTP(w, r) + return + } + + if r.Header.Get("Hash") == "none" { + logger.Log.Debug("key is empty") + next.ServeHTTP(w, r) + return + } + + gotSign, err := hex.DecodeString(r.Header.Get("HashSHA256")) + // gotSign := r.Header.Get("HashSHA256") + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + h := hmac.New(sha256.New, md.signKey) + + data, _ := io.ReadAll(r.Body) + r.Body.Close() + _, err = h.Write(data) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + sign := h.Sum(nil) + + logger.Log.Debug("sign key is ", string(md.signKey)) + logger.Log.Debug("received body is ", string(data)) + logger.Log.Debug("received hex signature: ", r.Header.Get("HashSHA256")) + logger.Log.Debug("calculated hex signature: ", hex.EncodeToString(sign)) + logger.Log.Debug("Headers: ", r.Header) + + if !hmac.Equal(sign, []byte(gotSign)) { + logger.Log.Debug("hash signatures are not equal") + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + return + } + + r.Body = io.NopCloser(bytes.NewBuffer(data)) + srw := signingResponseWriter{ + ResponseWriter: w, + key: md.signKey, + } + next.ServeHTTP(&srw, r) + + } + return http.HandlerFunc(fn) +} diff --git a/internal/controller/http/v1/middleware/compressor.go b/internal/controller/http/v1/middleware/compressor.go new file mode 100644 index 0000000..e6fd87a --- /dev/null +++ b/internal/controller/http/v1/middleware/compressor.go @@ -0,0 +1,114 @@ +package middleware + +import ( + "compress/gzip" + "fmt" + "io" + + // "log" + "net/http" + "strings" + + "github.com/The-Gleb/go_metrics_and_alerting/internal/logger" +) + +type gzipMiddleware struct { +} + +func NewGzipMiddleware() *gzipMiddleware { + return &gzipMiddleware{} +} + +type compressWriter struct { + rw http.ResponseWriter + zw *gzip.Writer +} + +func newCompressWriter(rw http.ResponseWriter) *compressWriter { + return &compressWriter{ + rw: rw, + zw: gzip.NewWriter(rw), + } +} + +func (c *compressWriter) Header() http.Header { + return c.rw.Header() +} + +func (c *compressWriter) Write(p []byte) (int, error) { + if c.rw.Header().Get("Content-Type") == "application/json" || c.rw.Header().Get("Content-Type") == "text/html" { + c.rw.Header().Set("Content-Encoding", "gzip") + return c.zw.Write(p) + } + return c.rw.Write(p) +} +func (c *compressWriter) WriteHeader(statusCode int) { + if statusCode < 300 { + c.rw.Header().Set("Content-Encoding", "gzip") + } + c.rw.WriteHeader(statusCode) +} + +func (c *compressWriter) Close() error { + return c.zw.Close() +} + +type compressReader struct { + r io.ReadCloser + zr *gzip.Reader +} + +func newCompressReader(r io.ReadCloser) (*compressReader, error) { + zr, err := gzip.NewReader(r) + if err != nil { + return nil, fmt.Errorf("gzip.NewReader: %w", err) + } + + return &compressReader{ + r: r, + zr: zr, + }, nil +} + +func (c compressReader) Read(p []byte) (n int, err error) { + return c.zr.Read(p) +} + +func (c *compressReader) Close() error { + if err := c.r.Close(); err != nil { + return err + } + return c.zr.Close() +} + +func (md *gzipMiddleware) Do(h http.Handler) http.Handler { + gzipMiddleware := func(rw http.ResponseWriter, r *http.Request) { + ow := rw + + acceptEncoding := r.Header.Get("Accept-Encoding") + supportsGzip := strings.Contains(acceptEncoding, "gzip") + // logger.Log.Debugw("Request Accept-Encoding", "gzip", supportsGzip) + if supportsGzip { + cw := newCompressWriter(rw) + ow = cw + defer cw.Close() + } + + contentEncoding := r.Header.Get("Content-Encoding") + sendsGzip := strings.Contains(contentEncoding, "gzip") + // logger.Log.Debugw("Request Content-Encoding", "gzip", sendsGzip) + if sendsGzip { + cr, err := newCompressReader(r.Body) + if err != nil { + logger.Log.Error(err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + r.Body = cr + defer cr.Close() + } + + h.ServeHTTP(ow, r) + } + return http.HandlerFunc(gzipMiddleware) +} diff --git a/internal/controller/http/v1/middleware/logger.go b/internal/controller/http/v1/middleware/logger.go new file mode 100644 index 0000000..24c598c --- /dev/null +++ b/internal/controller/http/v1/middleware/logger.go @@ -0,0 +1,72 @@ +package middleware + +import ( + "net/http" + "time" + + "go.uber.org/zap" +) + +type loggerMiddleware struct { + log *zap.SugaredLogger +} + +func NewLoggerMiddleware(l *zap.SugaredLogger) *loggerMiddleware { + return &loggerMiddleware{l} +} + +type ( + responseData struct { + status int + size int + } + + loggingResponseWriter struct { + http.ResponseWriter + responseData *responseData + } +) + +func (r *loggingResponseWriter) Write(b []byte) (int, error) { + size, err := r.ResponseWriter.Write(b) + r.responseData.status = 200 + r.responseData.size += size + return size, err +} + +func (r *loggingResponseWriter) WriteHeader(statusCode int) { + r.ResponseWriter.WriteHeader(statusCode) + r.responseData.status = statusCode +} + +func (md *loggerMiddleware) Do(next http.Handler) http.Handler { + logFn := func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + + responseData := &responseData{ + status: 0, + size: 0, + } + lw := loggingResponseWriter{ + ResponseWriter: w, + responseData: responseData, + } + + next.ServeHTTP(&lw, r) + + buf := make([]byte, 0) + r.Body.Read(buf) + duration := time.Since(start) + + md.log.Infow( + "Got request ", + "method", r.Method, + "uri", r.RequestURI, + // "request body", string(buf), + "status", responseData.status, + "duration", duration, + "size", responseData.size, + ) + } + return http.HandlerFunc(logFn) +} diff --git a/internal/domain/entity/metric.go b/internal/domain/entity/metric.go new file mode 100644 index 0000000..fde238a --- /dev/null +++ b/internal/domain/entity/metric.go @@ -0,0 +1,12 @@ +package entity + +type Metric struct { + ID string `json:"id"` // имя метрики + MType string `json:"type"` // параметр, принимающий значение gauge или counter + Delta *int64 `json:"delta,omitempty"` // значение метрики в случае передачи counter + Value *float64 `json:"value,omitempty"` // значение метрики в случае передачи gauge +} +type MetricsMaps struct { + Gauge []Metric + Counter []Metric +} diff --git a/internal/domain/service/backup.go b/internal/domain/service/backup.go new file mode 100644 index 0000000..7acc72f --- /dev/null +++ b/internal/domain/service/backup.go @@ -0,0 +1,80 @@ +package service + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/entity" +) + +type FileStorage interface { + WriteData(data []byte) error + ReadData() ([]byte, error) +} + +type backupService struct { + metricStorage MetricStorage + backupStorage FileStorage + backupInterval int + restore bool +} + +func NewBackupService(ms MetricStorage, bs FileStorage, interval int, restore bool) *backupService { + return &backupService{ + metricStorage: ms, + backupStorage: bs, + backupInterval: interval, + restore: restore, + } +} + +func (service *backupService) LoadDataFromFile(ctx context.Context) error { + + data, err := service.backupStorage.ReadData() + if err != nil { + return fmt.Errorf("LoadDataFromFile: failed reading data: %w", err) + } + + var maps entity.MetricsMaps + + err = json.Unmarshal(data, &maps) + if err != nil { + return fmt.Errorf("LoadDataFromFile: failed unmarshalling: %w", err) + } + + _, err = service.metricStorage.UpdateMetricSet(ctx, maps.Gauge) + if err != nil { + return fmt.Errorf("LoadDataFromFile: failded updating gauge: %w", err) + } + + _, err = service.metricStorage.UpdateMetricSet(ctx, maps.Counter) + if err != nil { + return fmt.Errorf("LoadDataFromFile: failded updating counter: %w", err) + } + + return nil +} + +func (service *backupService) StoreDataToFile(ctx context.Context) error { + metricMaps, err := service.metricStorage.GetAllMetrics(ctx) + if err != nil { + return fmt.Errorf("StoreDataToFile: %w", err) + } + + data, err := json.Marshal(metricMaps) + if err != nil { + return fmt.Errorf("StoreDataToFile: %w", err) + } + + err = service.backupStorage.WriteData(data) + if err != nil { + return fmt.Errorf("StoreDataToFile: %w", err) + } + + return nil +} + +func (service *backupService) SyncWrite() bool { + return service.backupInterval == 0 +} diff --git a/internal/domain/service/metric.go b/internal/domain/service/metric.go new file mode 100644 index 0000000..4d17515 --- /dev/null +++ b/internal/domain/service/metric.go @@ -0,0 +1,85 @@ +package service + +import ( + "context" + + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/entity" +) + +type MetricStorage interface { + UpdateGauge(ctx context.Context, metric entity.Metric) (entity.Metric, error) + UpdateCounter(ctx context.Context, metric entity.Metric) (entity.Metric, error) + + UpdateMetricSet(ctx context.Context, metrics []entity.Metric) (int64, error) + + GetGauge(ctx context.Context, metric entity.Metric) (entity.Metric, error) + GetCounter(ctx context.Context, metric entity.Metric) (entity.Metric, error) + GetAllMetrics(ctx context.Context) (entity.MetricsMaps, error) + + PingDB() error +} + +type metricService struct { + storage MetricStorage +} + +func NewMetricService(s MetricStorage) *metricService { + return &metricService{s} +} + +func (service *metricService) UpdateMetric(ctx context.Context, metric entity.Metric) (entity.Metric, error) { + + var err error + switch metric.MType { + case "gauge": + metric, err = service.storage.UpdateGauge(ctx, metric) + + case "counter": + metric, err = service.storage.UpdateCounter(ctx, metric) + } + + if err != nil { + return entity.Metric{}, err + } + + return metric, nil + +} + +func (service *metricService) UpdateMetricSet(ctx context.Context, metrics []entity.Metric) (int64, error) { + + return service.storage.UpdateMetricSet(ctx, metrics) + +} + +func (service *metricService) GetMetric(ctx context.Context, metric entity.Metric) (entity.Metric, error) { + + var err error + switch metric.MType { + case "gauge": + metric, err = service.storage.GetGauge(ctx, metric) + + case "counter": + metric, err = service.storage.GetCounter(ctx, metric) + } + + if err != nil { + return entity.Metric{}, err + } + + return metric, nil + +} + +func (service *metricService) GetAllMetrics(ctx context.Context) (entity.MetricsMaps, error) { + + return service.storage.GetAllMetrics(ctx) + +} + +// why does metric service ping database??? +func (service *metricService) PingDB() error { + + return service.storage.PingDB() + +} diff --git a/internal/domain/usecase/get_all_metrics.go b/internal/domain/usecase/get_all_metrics.go new file mode 100644 index 0000000..3563f3a --- /dev/null +++ b/internal/domain/usecase/get_all_metrics.go @@ -0,0 +1,89 @@ +package usecase + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/entity" + "github.com/The-Gleb/go_metrics_and_alerting/pkg/utils/retry" +) + +type getAllMetricsUsecase struct { + metricService MetricService +} + +func NewGetAllMetricsUsecase(ms MetricService) *getAllMetricsUsecase { + return &getAllMetricsUsecase{ + metricService: ms, + } +} + +func (uc *getAllMetricsUsecase) GetAllMetricsJSON(ctx context.Context) ([]byte, error) { + + var metricMaps entity.MetricsMaps + var err error + err = retry.DefaultRetry(context.TODO(), func(ctx context.Context) error { + metricMaps, err = uc.metricService.GetAllMetrics(ctx) + return err + }) + if err != nil { + return []byte{}, fmt.Errorf("GetAllMetricsJSON: %w", err) + } + + b := new(bytes.Buffer) + + jsonMaps, err := json.Marshal(&metricMaps) + if err != nil { + return []byte{}, fmt.Errorf("GetAllMetricsJSON: %w", err) + } + + _, err = b.Write(jsonMaps) + if err != nil { + return []byte{}, fmt.Errorf("GetAllMetricsJSON: %w", err) + } + + return b.Bytes(), nil + +} + +func (uc *getAllMetricsUsecase) GetAllMetricsHTML(ctx context.Context) ([]byte, error) { + + var metricMaps entity.MetricsMaps + var err error + err = retry.DefaultRetry(ctx, func(ctx context.Context) error { + metricMaps, err = uc.metricService.GetAllMetrics(ctx) + return err + }) + if err != nil { + return []byte{}, fmt.Errorf("GetAllMetricsHTML: %w", err) + } + + b := new(bytes.Buffer) + fmt.Fprintf(b, ` + + + + list-style-type + + + + ") + + return b.Bytes(), nil + +} diff --git a/internal/domain/usecase/get_metric.go b/internal/domain/usecase/get_metric.go new file mode 100644 index 0000000..40092cf --- /dev/null +++ b/internal/domain/usecase/get_metric.go @@ -0,0 +1,27 @@ +package usecase + +import ( + "context" + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/entity" +) + +type getMetricUsecase struct { + metricService MetricService +} + +func NewGetMetricUsecase(ms MetricService) *getMetricUsecase { + return &getMetricUsecase{ + metricService: ms, + } +} + +func (uc *getMetricUsecase) GetMetric(ctx context.Context, metric entity.Metric) (entity.Metric, error) { + + metric, err := uc.metricService.GetMetric(ctx, metric) + if err != nil { + return entity.Metric{}, err + } + + return metric, nil + +} diff --git a/internal/domain/usecase/interfaces.go b/internal/domain/usecase/interfaces.go new file mode 100644 index 0000000..ef3cc8c --- /dev/null +++ b/internal/domain/usecase/interfaces.go @@ -0,0 +1,22 @@ +package usecase + +import ( + "context" + + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/entity" +) + +type MetricService interface { + UpdateMetric(ctx context.Context, metric entity.Metric) (entity.Metric, error) + UpdateMetricSet(ctx context.Context, metrics []entity.Metric) (int64, error) + GetMetric(ctx context.Context, metric entity.Metric) (entity.Metric, error) + GetAllMetrics(ctx context.Context) (entity.MetricsMaps, error) + + PingDB() error +} + +type BackupService interface { + LoadDataFromFile(ctx context.Context) error + StoreDataToFile(ctx context.Context) error + SyncWrite() bool +} diff --git a/internal/domain/usecase/update_metric.go b/internal/domain/usecase/update_metric.go new file mode 100644 index 0000000..e683cb8 --- /dev/null +++ b/internal/domain/usecase/update_metric.go @@ -0,0 +1,36 @@ +package usecase + +import ( + "context" + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/entity" +) + +type updateMetricUsecase struct { + metricService MetricService + backupService BackupService +} + +func NewUpdateMetricUsecase(ms MetricService, bs BackupService) *updateMetricUsecase { + return &updateMetricUsecase{ + metricService: ms, + backupService: bs, + } +} + +func (uc *updateMetricUsecase) UpdateMetric(ctx context.Context, metric entity.Metric) (entity.Metric, error) { + + metric, err := uc.metricService.UpdateMetric(ctx, metric) + if err != nil { + return entity.Metric{}, err + } + + if uc.backupService != nil && uc.backupService.SyncWrite() { + err := uc.backupService.StoreDataToFile(ctx) + if err != nil { + return entity.Metric{}, err + } + } + + return metric, nil + +} diff --git a/internal/domain/usecase/update_metric_set.go b/internal/domain/usecase/update_metric_set.go new file mode 100644 index 0000000..b9f29ed --- /dev/null +++ b/internal/domain/usecase/update_metric_set.go @@ -0,0 +1,36 @@ +package usecase + +import ( + "context" + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/entity" +) + +type updateMetricSetUsecase struct { + metricService MetricService + backupService BackupService +} + +func NewUpdateMetricSetUsecase(ms MetricService, bs BackupService) *updateMetricSetUsecase { + return &updateMetricSetUsecase{ + metricService: ms, + backupService: bs, + } +} + +func (uc *updateMetricSetUsecase) UpdateMetricSet(ctx context.Context, metrics []entity.Metric) (int64, error) { + + n, err := uc.metricService.UpdateMetricSet(ctx, metrics) + if err != nil { + return n, err // TODO: check what n when err != nil + } + + if uc.backupService != nil && uc.backupService.SyncWrite() { + err := uc.backupService.StoreDataToFile(ctx) + if err != nil { + return n, err + } + } + + return n, nil + +} diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 255be2b..4400765 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -12,7 +12,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/The-Gleb/go_metrics_and_alerting/internal/logger" - "github.com/The-Gleb/go_metrics_and_alerting/internal/repositories" + "github.com/The-Gleb/go_metrics_and_alerting/internal/repository" ) // App interface with all buisness logic. @@ -117,7 +117,7 @@ func (handlers *handlers) GetMetric(rw http.ResponseWriter, r *http.Request) { err = fmt.Errorf("handlers.GetMetric: %w", err) logger.Log.Error(err) - if errors.Is(err, repositories.ErrNotFound) { + if errors.Is(err, repository.ErrNotFound) { logger.Log.Debug("Yes it is NOT FOUND ERROR") rw.WriteHeader(http.StatusNotFound) http.Error(rw, err.Error(), http.StatusNotFound) @@ -142,7 +142,7 @@ func (handlers *handlers) GetMetricJSON(rw http.ResponseWriter, r *http.Request) err = fmt.Errorf("handlers.GetMetricJSON: %w", err) logger.Log.Error(err) - if !errors.Is(err, repositories.ErrNotFound) { + if !errors.Is(err, repository.ErrNotFound) { rw.WriteHeader(http.StatusBadRequest) http.Error(rw, err.Error(), http.StatusBadRequest) } else { diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go deleted file mode 100644 index 4182503..0000000 --- a/internal/handlers/handlers_test.go +++ /dev/null @@ -1,496 +0,0 @@ -package handlers - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" - "testing" - - "github.com/The-Gleb/go_metrics_and_alerting/internal/app" - "github.com/The-Gleb/go_metrics_and_alerting/internal/repositories/memory" - "github.com/go-chi/chi/v5" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func testRequest(t *testing.T, ts *httptest.Server, method, - path string, body []byte) (*http.Response, string) { - req, err := http.NewRequest(method, ts.URL+path, bytes.NewReader(body)) - require.NoError(t, err) - - resp, err := ts.Client().Do(req) - require.NoError(t, err) - defer resp.Body.Close() - - respBody, err := io.ReadAll(resp.Body) - require.NoError(t, err) - - return resp, string(respBody) -} - -func TestUpdateMetricSet(t *testing.T) { - s := memory.New() - a := app.NewApp(s, nil) - h := New(a) - - router := chi.NewRouter() - router.Post("/updates/", h.UpdateMetricSet) - ts := httptest.NewServer(router) - defer ts.Close() - - validJsonBody := `[ - { - "id": "HeapAlloc", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "PollCount", - "type": "counter", - "delta": 123 - }]` - - type want struct { - code int - } - tests := []struct { - name string - h *handlers - body json.RawMessage - want want - }{ - { - name: "normal", - h: h, - body: json.RawMessage(validJsonBody), - want: want{200}, - }, - { - name: "request with invalid body", - h: h, - body: json.RawMessage([]byte("some invalid body")), - want: want{400}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - resp, b := testRequest(t, ts, "POST", "/updates/", tt.body) - defer resp.Body.Close() - - t.Log(b) - - require.Equal(t, tt.want.code, resp.StatusCode) - if tt.want.code != 200 { - return - } - }) - } - -} - -func TestUpdateMetric(t *testing.T) { - s := memory.New() - a := app.NewApp(s, nil) - h := New(a) - - router := chi.NewRouter() - router.Post("/update/{mType}/{mName}/{mValue}", h.UpdateMetric) - router.Get("/", h.GetAllMetricsHTML) - router.Get("/value/{mType}/{mName}", h.GetMetric) - ts := httptest.NewServer(router) - defer ts.Close() - - type want struct { - code int - } - tests := []struct { - name string - h *handlers - address string - mType string - mName string - mValue string - want want - }{ - { - name: "normal gauge test #1", - address: "/update/gauge/Alloc/23.23", - mType: "gauge", - mName: "Alloc", - mValue: "23.23", - h: h, - want: want{ - code: 200, - }, - }, - { - name: "first add counter test #2", - address: "/update/counter/counter/23", - mType: "counter", - mName: "counter", - mValue: "23", - h: h, - want: want{ - code: 200, - }, - }, - { - name: "second add counter test #3", - address: "/update/counter/counter/7", - mType: "counter", - mName: "counter", - mValue: "30", - h: h, - want: want{ - code: 200, - }, - }, - { - name: "name and value not sent - test #4", - address: "/update/gauge", - h: h, - want: want{ - code: 404, - }, - }, - { - name: "value not sent - test #5", - address: "/update/gauge/nbhj", - h: h, - want: want{ - code: 404, - }, - }, - { - name: "wrong metric type- test #5", - address: "/update/gaunjh/efvefv/eefe", - h: h, - want: want{ - code: 400, - }, - }, - { - name: "nothing was sent - test #6", - address: "", - h: h, - want: want{ - code: 405, - }, - }, - { - name: "incorrect metric value type - test #6", - address: "/update/gauge/alloc/string", - h: h, - want: want{ - code: 400, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - resp, _ := testRequest(t, ts, "POST", tt.address, nil) - defer resp.Body.Close() - - require.Equal(t, tt.want.code, resp.StatusCode) - if tt.want.code != 200 { - return - } - val, _ := h.app.GetMetricFromParams(context.Background(), tt.mType, tt.mName) - assert.Equal(t, tt.mValue, string(val)) - - }) - } -} - -func TestGetMetric(t *testing.T) { - s := memory.New() - s.UpdateMetric("gauge", "Alloc", "123.4") - s.UpdateMetric("counter", "Counter", "123") - a := app.NewApp(s, nil) - h := New(a) - - router := chi.NewRouter() - router.Post("/update/{mType}/{mName}/{mValue}", h.UpdateMetric) - router.Get("/", h.GetAllMetricsHTML) - router.Get("/value/{mType}/{mName}", h.GetMetric) - ts := httptest.NewServer(router) - defer ts.Close() - - type want struct { - value string - code int - } - tests := []struct { - name string - address string - h *handlers - want want - }{ - { - name: "normal gauge test #1", - address: "/value/gauge/Alloc", - h: h, - want: want{ - value: "123.4", - code: 200, - }, - }, - { - name: "normal counter test #2", - address: "/value/counter/Counter", - h: h, - want: want{ - value: "123", - code: 200, - }, - }, - { - name: "neg counter test #3", - address: "/value/counter/erff", - h: h, - want: want{ - value: "", - code: 404, - }, - }, - { - name: "wrong metric type test #4", - address: "/value/ssds/erff", - h: h, - want: want{ - value: "", - code: 400, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - resp, val := testRequest(t, ts, "GET", tt.address, nil) - defer resp.Body.Close() - - require.Equal(t, tt.want.code, resp.StatusCode) - if tt.want.code != 200 { - return - } - assert.Equal(t, tt.want.value, string(val)) - - }) - } -} - -func Example_handlers_UpdateMetricSet() { - s := memory.New() - a := app.NewApp(s, nil) - h := New(a) - - router := chi.NewRouter() - router.Post("/updates/", h.UpdateMetricSet) - ts := httptest.NewServer(router) - defer ts.Close() - - validJsonBody := `[ - { - "id": "HeapAlloc", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "PollCount", - "type": "counter", - "delta": 123 - } - ]` - - req, _ := http.NewRequest("POST", ts.URL+"/updates/", bytes.NewReader([]byte(validJsonBody))) - - resp, _ := ts.Client().Do(req) - defer resp.Body.Close() - - fmt.Println(resp.StatusCode) - - // Output: - // 200 - -} - -func Example_handlers_UpdateMetric() { - - s := memory.New() - a := app.NewApp(s, nil) - h := New(a) - - router := chi.NewRouter() - router.Post("/update/{mType}/{mName}/{mValue}", h.UpdateMetric) - ts := httptest.NewServer(router) - defer ts.Close() - - req, _ := http.NewRequest("POST", ts.URL+"/update/gauge/Alloc/12.12", nil) - - resp, _ := ts.Client().Do(req) - defer resp.Body.Close() - - fmt.Println(resp.StatusCode) - - // Output: - // 200 - -} - -func Example_handlers_GetMetric() { - - s := memory.New() - s.UpdateMetric("gauge", "Alloc", "123.4") - a := app.NewApp(s, nil) - h := New(a) - - router := chi.NewRouter() - router.Get("/value/{mType}/{mName}", h.GetMetric) - ts := httptest.NewServer(router) - defer ts.Close() - - req, _ := http.NewRequest("GET", ts.URL+"/value/gauge/Alloc", nil) - - resp, _ := ts.Client().Do(req) - - fmt.Println(resp.StatusCode) - - // Output: - // 200 - -} - -func Example_handlers_UpdateMetricJSON() { - - s := memory.New() - s.UpdateMetric("gauge", "Alloc", "123.123") - a := app.NewApp(s, nil) - h := New(a) - - router := chi.NewRouter() - router.Post("/update", h.UpdateMetricJSON) - ts := httptest.NewServer(router) - defer ts.Close() - - validBody := `{ - "id": "Alloc", - "type": "gauge", - "value": 123.123 - }` - - req, _ := http.NewRequest("POST", ts.URL+"/update", bytes.NewReader([]byte(validBody))) - - resp, _ := ts.Client().Do(req) - - fmt.Println(resp.StatusCode) - b, _ := io.ReadAll(resp.Body) - fmt.Println(string(b)) - - // Output: - // 200 - // {"id":"Alloc","type":"gauge","value":123.123} - -} - -func Example_handlers_GetMetricJSON() { - - s := memory.New() - s.UpdateMetric("gauge", "Alloc", "3782369280") - a := app.NewApp(s, nil) - h := New(a) - - router := chi.NewRouter() - router.Get("/value", h.GetMetricJSON) - ts := httptest.NewServer(router) - defer ts.Close() - - validBody := `{ - "id": "Alloc", - "type": "gauge" - }` - - req, _ := http.NewRequest("GET", ts.URL+"/value", bytes.NewReader([]byte(validBody))) - - resp, _ := ts.Client().Do(req) - - fmt.Println(resp.StatusCode) - b, _ := io.ReadAll(resp.Body) - fmt.Println(string(b)) - - // Output: - // 200 - // {"id":"Alloc","type":"gauge","value":3782369280} - -} - -func Example_handlers_GetAllMetricJSON() { - - s := memory.New() - s.UpdateMetric("gauge", "Alloc", "123.4") - s.UpdateMetric("counter", "PollCount", "12") - a := app.NewApp(s, nil) - h := New(a) - - router := chi.NewRouter() - router.Get("/values", h.GetAllMetricsJSON) - ts := httptest.NewServer(router) - defer ts.Close() - - req, _ := http.NewRequest("GET", ts.URL+"/values", nil) - - resp, _ := ts.Client().Do(req) - - fmt.Println(resp.StatusCode) - b, _ := io.ReadAll(resp.Body) - fmt.Println(string(b)) - - // Output: - // 200 - // {"Gauge":[{"id":"Alloc","type":"gauge","value":123.4}],"Counter":[{"id":"PollCount","type":"counter","delta":12}]} - -} - -func Example_handlers_GetAllMetricHTML() { - - s := memory.New() - s.UpdateMetric("gauge", "Alloc", "123.4") - s.UpdateMetric("counter", "PollCount", "12") - a := app.NewApp(s, nil) - h := New(a) - - router := chi.NewRouter() - router.Get("/", h.GetAllMetricsHTML) - ts := httptest.NewServer(router) - defer ts.Close() - - req, _ := http.NewRequest("GET", ts.URL+"/", nil) - - resp, _ := ts.Client().Do(req) - - fmt.Println(resp.StatusCode) - - b, _ := io.ReadAll(resp.Body) - fmt.Println(string(b)) - // Output: - // 200 - // - // - // - // - // list-style-type - // - // - // - // - -} diff --git a/internal/repositories/database/sqls/schema.sql b/internal/repository/database/sqls/schema.sql similarity index 95% rename from internal/repositories/database/sqls/schema.sql rename to internal/repository/database/sqls/schema.sql index d4fae51..f0dd2f7 100644 --- a/internal/repositories/database/sqls/schema.sql +++ b/internal/repository/database/sqls/schema.sql @@ -1,8 +1,8 @@ -CREATE TABLE IF NOT EXISTS gauge_metrics ( - m_name TEXT UNIQUE, - m_value DOUBLE PRECISION - ); -CREATE TABLE IF NOT EXISTS counter_metrics ( - m_name TEXT UNIQUE, - m_value INTEGER - ); +CREATE TABLE IF NOT EXISTS gauge_metrics ( + m_name TEXT UNIQUE, + m_value DOUBLE PRECISION + ); +CREATE TABLE IF NOT EXISTS counter_metrics ( + m_name TEXT UNIQUE, + m_value INTEGER + ); diff --git a/internal/repositories/database/storage.go b/internal/repository/database/storage.go similarity index 58% rename from internal/repositories/database/storage.go rename to internal/repository/database/storage.go index c1810fd..5eb45ac 100644 --- a/internal/repositories/database/storage.go +++ b/internal/repository/database/storage.go @@ -16,9 +16,9 @@ import ( "github.com/jackc/pgx/v5/pgconn" _ "github.com/jackc/pgx/v5/stdlib" + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/entity" "github.com/The-Gleb/go_metrics_and_alerting/internal/logger" - "github.com/The-Gleb/go_metrics_and_alerting/internal/models" - "github.com/The-Gleb/go_metrics_and_alerting/internal/repositories" + "github.com/The-Gleb/go_metrics_and_alerting/internal/repository" postgresql "github.com/The-Gleb/go_metrics_and_alerting/pkg/client" ) @@ -52,11 +52,11 @@ func ConnectDB(dsn string) (*DB, error) { } type Repositiries interface { - GetAllMetrics(ctx context.Context) ([]models.Metrics, []models.Metrics) - UpdateGauge(ctx context.Context, metricObj models.Metrics) error - UpdateCounter(ctx context.Context, metricObj models.Metrics) error - GetGauge(ctx context.Context, metricObj models.Metrics) (models.Metrics, error) - GetCounter(ctx context.Context, metricObj models.Metrics) (models.Metrics, error) + GetAllMetrics(ctx context.Context) ([]entity.Metric, []entity.Metric) + UpdateGauge(ctx context.Context, metric entity.Metric) error + UpdateCounter(ctx context.Context, metric entity.Metric) error + GetGauge(ctx context.Context, metric entity.Metric) (entity.Metric, error) + GetCounter(ctx context.Context, metric entity.Metric) (entity.Metric, error) PingDB() error } @@ -69,7 +69,7 @@ func (db *DB) PingDB() error { return nil } -func (db *DB) UpdateMetricSet(ctx context.Context, metrics []models.Metrics) (int64, error) { +func (db *DB) UpdateMetricSet(ctx context.Context, metrics []entity.Metric) (int64, error) { tx, err := db.client.Begin(ctx) if err != nil { @@ -112,29 +112,29 @@ func (db *DB) UpdateMetricSet(ctx context.Context, metrics []models.Metrics) (in return updated, nil } -func (db *DB) GetAllMetrics(ctx context.Context) ([]models.Metrics, []models.Metrics, error) { +func (db *DB) GetAllMetrics(ctx context.Context) (entity.MetricsMaps, error) { tx, err := db.client.Begin(ctx) if err != nil { - return nil, nil, checkForConectionErr("GetAllMetrics", err) + return entity.MetricsMaps{}, checkForConectionErr("GetAllMetrics", err) } defer tx.Rollback(ctx) rows, err := tx.Query(ctx, `SELECT m_name, m_value FROM gauge_metrics`) if err != nil { - return nil, nil, checkForConectionErr("GetAllMetrics", err) + return entity.MetricsMaps{}, checkForConectionErr("GetAllMetrics", err) } if rows.Err() != nil { - return nil, nil, checkForConectionErr("GetAllMetrics", err) + return entity.MetricsMaps{}, checkForConectionErr("GetAllMetrics", err) } defer rows.Close() - gaugeMetrics := make([]models.Metrics, 0) + gaugeMetrics := make([]entity.Metric, 0) for rows.Next() { - var metric models.Metrics + var metric entity.Metric var value float64 err := rows.Scan(&metric.ID, &value) if err != nil { - return nil, nil, checkForConectionErr("GetAllMetrics", err) + return entity.MetricsMaps{}, checkForConectionErr("GetAllMetrics", err) } metric.Value = &value metric.MType = "gauge" @@ -143,20 +143,20 @@ func (db *DB) GetAllMetrics(ctx context.Context) ([]models.Metrics, []models.Met rows, err = tx.Query(ctx, `SELECT m_name, m_value FROM counter_metrics`) if err != nil { - return nil, nil, checkForConectionErr("GetAllMetrics", err) + return entity.MetricsMaps{}, checkForConectionErr("GetAllMetrics", err) } if rows.Err() != nil { - return nil, nil, checkForConectionErr("GetAllMetrics", err) + return entity.MetricsMaps{}, checkForConectionErr("GetAllMetrics", err) } defer rows.Close() - counterMetrics := make([]models.Metrics, 0) + counterMetrics := make([]entity.Metric, 0) for rows.Next() { - var metric models.Metrics + var metric entity.Metric var value int64 err := rows.Scan(&metric.ID, &value) if err != nil { - return nil, nil, checkForConectionErr("GetAllMetrics", err) + return entity.MetricsMaps{}, checkForConectionErr("GetAllMetrics", err) } metric.Delta = &value metric.MType = "counter" @@ -165,54 +165,69 @@ func (db *DB) GetAllMetrics(ctx context.Context) ([]models.Metrics, []models.Met tx.Commit(ctx) - return gaugeMetrics, counterMetrics, nil + return entity.MetricsMaps{ + Gauge: gaugeMetrics, + Counter: counterMetrics, + }, nil } -func (db *DB) UpdateGauge(ctx context.Context, metricObj models.Metrics) error { - _, err := db.client.Exec(ctx, ` - INSERT INTO gauge_metrics (m_name, m_value) +func (db *DB) UpdateGauge(ctx context.Context, metric entity.Metric) (entity.Metric, error) { + row := db.client.QueryRow( + ctx, + `INSERT INTO gauge_metrics (m_name, m_value) VALUES ($1, $2) ON CONFLICT (m_name) DO UPDATE - SET m_value = $2; - `, metricObj.ID, metricObj.Value) + SET m_value = $2 + RETURNING *;`, + metric.ID, metric.Value, + ) + + err := row.Scan(&metric.ID, &metric.Value) if err != nil { - return checkForConectionErr("GetAllMetrics", err) + return entity.Metric{}, checkForConectionErr("UpdateGauge", err) } - return nil + return metric, nil } -func (db *DB) UpdateCounter(ctx context.Context, metricObj models.Metrics) error { - _, err := db.client.Exec(ctx, ` - INSERT INTO counter_metrics (m_name, m_value) + +func (db *DB) UpdateCounter(ctx context.Context, mertic entity.Metric) (entity.Metric, error) { + row := db.client.QueryRow( + ctx, + `INSERT INTO counter_metrics (m_name, m_value) VALUES ($1, $2) ON CONFLICT (m_name) DO UPDATE - SET m_value = counter_metrics.m_value + EXCLUDED.m_value; - `, metricObj.ID, metricObj.Delta) + SET m_value = counter_metrics.m_value + EXCLUDED.m_value + RETURNING *;`, + mertic.ID, mertic.Delta, + ) + + err := row.Scan(&mertic.ID, &mertic.Delta) if err != nil { - return checkForConectionErr("UpdateCounter", err) + return entity.Metric{}, checkForConectionErr("UpdateCounter", err) } - return nil + + return entity.Metric{}, nil } -func (db *DB) GetGauge(ctx context.Context, metricObj models.Metrics) (models.Metrics, error) { - row := db.client.QueryRow(ctx, "SELECT m_value FROM gauge_metrics WHERE m_name = $1", metricObj.ID) +func (db *DB) GetGauge(ctx context.Context, metric entity.Metric) (entity.Metric, error) { + row := db.client.QueryRow(ctx, "SELECT m_value FROM gauge_metrics WHERE m_name = $1", metric.ID) var value float64 err := row.Scan(&value) if err != nil { - return metricObj, checkForConectionErr("GetGauge", err) + return metric, checkForConectionErr("GetGauge", err) } - metricObj.Value = &value - return metricObj, nil + metric.Value = &value + return metric, nil } -func (db *DB) GetCounter(ctx context.Context, metricObj models.Metrics) (models.Metrics, error) { - row := db.client.QueryRow(ctx, "SELECT m_value FROM counter_metrics WHERE m_name = $1", metricObj.ID) +func (db *DB) GetCounter(ctx context.Context, metric entity.Metric) (entity.Metric, error) { + row := db.client.QueryRow(ctx, "SELECT m_value FROM counter_metrics WHERE m_name = $1", metric.ID) var value int64 err := row.Scan(&value) if err != nil { - return metricObj, checkForConectionErr("GetCounter", err) + return metric, checkForConectionErr("GetCounter", err) } - metricObj.Delta = &value - return metricObj, nil + metric.Delta = &value + return metric, nil } func checkForConectionErr(funcName string, err error) error { @@ -220,9 +235,9 @@ func checkForConectionErr(funcName string, err error) error { switch { case errors.As(err, &pgErr) && pgerrcode.IsConnectionException(pgErr.Code): - err = fmt.Errorf("%s: %w: %w", funcName, repositories.ErrConnection, err) + err = fmt.Errorf("%s: %w: %w", funcName, repository.ErrConnection, err) case errors.Is(err, sql.ErrNoRows): - err = fmt.Errorf("%s: %w: %w", funcName, repositories.ErrNotFound, err) + err = fmt.Errorf("%s: %w: %w", funcName, repository.ErrNotFound, err) default: logger.Log.Debug(err) err = fmt.Errorf("%s: %w: ", funcName, err) diff --git a/internal/repository/file_storage/file_storage.go b/internal/repository/file_storage/file_storage.go new file mode 100644 index 0000000..1a0a69f --- /dev/null +++ b/internal/repository/file_storage/file_storage.go @@ -0,0 +1,47 @@ +package file_storage + +import ( + "bufio" + "os" +) + +type filestorage struct { + path string +} + +func NewFileStorage(path string) *filestorage { + return &filestorage{ + path: path, + } +} + +func (fs *filestorage) WriteData(data []byte) error { + file, err := os.Create(fs.path) + if err != nil { + return err + } + _, err = file.Write(data) + if err != nil { + return err + } + err = file.Close() + if err != nil { + return err + } + return nil +} + +func (fs *filestorage) ReadData() ([]byte, error) { + file, err := os.Open(fs.path) + if err != nil { + return make([]byte, 0), nil + } + + scanner := bufio.NewScanner(file) + scanner.Scan() + if err := scanner.Err(); err != nil { + return make([]byte, 0), err + } + data := scanner.Bytes() + return data, nil +} diff --git a/internal/repositories/memory/storage.go b/internal/repository/memory/storage.go similarity index 65% rename from internal/repositories/memory/storage.go rename to internal/repository/memory/storage.go index b337daf..6434874 100644 --- a/internal/repositories/memory/storage.go +++ b/internal/repository/memory/storage.go @@ -1,171 +1,187 @@ -package memory - -import ( - "context" - "errors" - "fmt" - "strconv" - "sync" - "sync/atomic" - - "github.com/The-Gleb/go_metrics_and_alerting/internal/models" - "github.com/The-Gleb/go_metrics_and_alerting/internal/repositories" -) - -var ( - ErrInvalidMetricValueFloat64 error = errors.New("incorrect metric value\ncannot parse to float64") - ErrInvalidMetricValueInt64 error = errors.New("incorrect metric value\ncannot parse to int64") - ErrInvalidMetricType error = errors.New("invalid mertic type") - // ErrMetricNotFound error = errors.New(("metric was not found")) -) - -type storage struct { - gauge sync.Map - counter sync.Map -} - -func New() *storage { - return &storage{ - gauge: sync.Map{}, - counter: sync.Map{}, - } -} - -func (s *storage) UpdateMetric(mType, mName, mValue string) error { - switch mType { - case "gauge": - mValue, err := strconv.ParseFloat(mValue, 64) - if err != nil { - return ErrInvalidMetricValueFloat64 - } - s.gauge.Store(mName, &mValue) - case "counter": - mValue, err := strconv.ParseInt(mValue, 10, 64) - if err != nil { - return ErrInvalidMetricValueInt64 - } - - val, _ := s.counter.LoadOrStore(mName, new(atomic.Int64)) - // atomic.AddInt64(val.(*int64), mValue) - val.(*atomic.Int64).Add(mValue) - - default: - return ErrInvalidMetricType - } - return nil -} - -func (s *storage) UpdateGauge(ctx context.Context, metricObj models.Metrics) error { - s.gauge.Store(metricObj.ID, metricObj.Value) - return nil -} - -func (s *storage) UpdateCounter(ctx context.Context, metricObj models.Metrics) error { - val, _ := s.counter.LoadOrStore(metricObj.ID, new(atomic.Int64)) - val.(*atomic.Int64).Add(*metricObj.Delta) - return nil -} - -// TODO: check -func (s *storage) GetGauge(ctx context.Context, metricObj models.Metrics) (models.Metrics, error) { - val, ok := s.gauge.Load(metricObj.ID) - if ok { - metricObj.Value = val.(*float64) - return metricObj, nil - } - return metricObj, repositories.ErrNotFound -} - -func (s *storage) GetCounter(ctx context.Context, metricObj models.Metrics) (models.Metrics, error) { - val, ok := s.counter.Load(metricObj.ID) - if ok { - v := val.(*atomic.Int64).Load() - metricObj.Delta = &v - return metricObj, nil - } - return metricObj, repositories.ErrNotFound -} - -func (s *storage) UpdateMetricSet(ctx context.Context, metrics []models.Metrics) (int64, error) { - var updated int64 - newGauge := *CopySyncMap(&s.gauge) - newCounter := *CopySyncMap(&s.counter) - // var newCounter sync.Map - for _, metric := range metrics { - switch metric.MType { - case "gauge": - newGauge.Store(metric.ID, metric.Value) - - updated++ - case "counter": - val, _ := newCounter.LoadOrStore(metric.ID, new(atomic.Int64)) - val.(*atomic.Int64).Add(*metric.Delta) - updated++ - default: - return 0, fmt.Errorf("invalid mertic type: %s", metric.MType) - } - } - s.gauge = *CopySyncMap(&newGauge) - s.counter = *CopySyncMap(&newCounter) - return updated, nil -} - -func (s *storage) GetAllMetrics(ctx context.Context) ([]models.Metrics, []models.Metrics, error) { - newGauge := make([]models.Metrics, 0) - s.gauge.Range(func(key any, value any) bool { - metric := models.Metrics{ - MType: "gauge", - ID: key.(string), - Value: value.(*float64), - } - newGauge = append(newGauge, metric) - return true - }) - newCounter := make([]models.Metrics, 0) - s.counter.Range(func(key any, value any) bool { - v := value.(*atomic.Int64).Load() - metric := models.Metrics{ - MType: "counter", - ID: key.(string), - Delta: &v, - } - newCounter = append(newCounter, metric) - return true - }) - return newGauge, newCounter, nil -} - -func (s *storage) GetMetric(mType, mName string) (string, error) { - switch mType { - case "gauge": - val, ok := s.gauge.Load(mName) - if ok { - return fmt.Sprintf("%v", val), nil - } - case "counter": - val, ok := s.counter.Load(mName) - if ok { - v := val.(*atomic.Int64).Load() - return fmt.Sprintf("%d", v), nil - } - default: - return "", ErrInvalidMetricType - } - return "", repositories.ErrNotFound -} - -func (s *storage) PingDB() error { - return nil -} - -func CopySyncMap(m *sync.Map) *sync.Map { - var cp sync.Map - - m.Range(func(k, v any) bool { - cp.Store(k, v) - - return true - }) - - return &cp -} +package memory + +import ( + "context" + "errors" + "fmt" + "strconv" + "sync" + "sync/atomic" + + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/entity" + "github.com/The-Gleb/go_metrics_and_alerting/internal/repository" +) + +var ( + ErrInvalidMetricValueFloat64 error = errors.New("incorrect metric value\ncannot parse to float64") + ErrInvalidMetricValueInt64 error = errors.New("incorrect metric value\ncannot parse to int64") + ErrInvalidMetricType error = errors.New("invalid mertic type") + // ErrMetricNotFound error = errors.New(("metric was not found")) +) + +type storage struct { + gauge sync.Map + counter sync.Map +} + +func New() *storage { + return &storage{ + gauge: sync.Map{}, + counter: sync.Map{}, + } +} + +func (s *storage) UpdateGauge(ctx context.Context, metric entity.Metric) (entity.Metric, error) { + s.gauge.Store(metric.ID, metric.Value) + + return metric, nil +} + +func (s *storage) UpdateCounter(ctx context.Context, metric entity.Metric) (entity.Metric, error) { + val, _ := s.counter.LoadOrStore(metric.ID, new(atomic.Int64)) + val.(*atomic.Int64).Add(*metric.Delta) + + valPtr := val.(*atomic.Int64).Load() + metric.Delta = &valPtr + return metric, nil +} + +// TODO: check +func (s *storage) GetGauge(ctx context.Context, metric entity.Metric) (entity.Metric, error) { + val, ok := s.gauge.Load(metric.ID) + if ok { + metric.Value = val.(*float64) + return metric, nil + } + return metric, repository.ErrNotFound +} + +func (s *storage) GetCounter(ctx context.Context, metric entity.Metric) (entity.Metric, error) { + val, ok := s.counter.Load(metric.ID) + if ok { + v := val.(*atomic.Int64).Load() + metric.Delta = &v + return metric, nil + } + return metric, repository.ErrNotFound +} + +func (s *storage) UpdateMetricSet(ctx context.Context, metrics []entity.Metric) (int64, error) { + var updated int64 + newGauge := *CopySyncMap(&s.gauge) + newCounter := *CopySyncMap(&s.counter) + // var newCounter sync.Map + for _, metric := range metrics { + switch metric.MType { + case "gauge": + newGauge.Store(metric.ID, metric.Value) + + updated++ + case "counter": + val, _ := newCounter.LoadOrStore(metric.ID, new(atomic.Int64)) + val.(*atomic.Int64).Add(*metric.Delta) + updated++ + default: + return 0, fmt.Errorf("invalid mertic type: %s", metric.MType) + } + } + s.gauge = *CopySyncMap(&newGauge) + s.counter = *CopySyncMap(&newCounter) + return updated, nil +} + +func (s *storage) GetAllMetrics(ctx context.Context) (entity.MetricsMaps, error) { + newGauge := make([]entity.Metric, 0) + s.gauge.Range(func(key any, value any) bool { + + metric := entity.Metric{ + MType: "gauge", + ID: key.(string), + Value: value.(*float64), + } + + newGauge = append(newGauge, metric) + + return true + + }) + + newCounter := make([]entity.Metric, 0) + s.counter.Range(func(key any, value any) bool { + + v := value.(*atomic.Int64).Load() + metric := entity.Metric{ + MType: "counter", + ID: key.(string), + Delta: &v, + } + + newCounter = append(newCounter, metric) + + return true + }) + + return entity.MetricsMaps{ + Gauge: newGauge, + Counter: newCounter, + }, nil +} + +func (s *storage) GetMetric(mType, mName string) (string, error) { + switch mType { + case "gauge": + val, ok := s.gauge.Load(mName) + if ok { + return fmt.Sprintf("%v", val), nil + } + case "counter": + val, ok := s.counter.Load(mName) + if ok { + v := val.(*atomic.Int64).Load() + return fmt.Sprintf("%d", v), nil + } + default: + return "", ErrInvalidMetricType + } + return "", repository.ErrNotFound +} + +func (s *storage) PingDB() error { + return nil +} + +func CopySyncMap(m *sync.Map) *sync.Map { + var cp sync.Map + + m.Range(func(k, v any) bool { + cp.Store(k, v) + + return true + }) + + return &cp +} + +func (s *storage) UpdateMetric(mType, mName, mValue string) error { + switch mType { + case "gauge": + mValue, err := strconv.ParseFloat(mValue, 64) + if err != nil { + return ErrInvalidMetricValueFloat64 + } + s.gauge.Store(mName, &mValue) + case "counter": + mValue, err := strconv.ParseInt(mValue, 10, 64) + if err != nil { + return ErrInvalidMetricValueInt64 + } + + val, _ := s.counter.LoadOrStore(mName, new(atomic.Int64)) + // atomic.AddInt64(val.(*int64), mValue) + val.(*atomic.Int64).Add(mValue) + + default: + return ErrInvalidMetricType + } + return nil +} diff --git a/internal/repositories/memory/storage_test.go b/internal/repository/memory/storage_test.go similarity index 77% rename from internal/repositories/memory/storage_test.go rename to internal/repository/memory/storage_test.go index adbbee9..e111e8b 100644 --- a/internal/repositories/memory/storage_test.go +++ b/internal/repository/memory/storage_test.go @@ -1,12 +1,14 @@ package memory import ( + "context" + "reflect" "sync/atomic" "testing" + "github.com/The-Gleb/go_metrics_and_alerting/internal/domain/entity" + "github.com/The-Gleb/go_metrics_and_alerting/internal/repository" "github.com/stretchr/testify/assert" - - "github.com/The-Gleb/go_metrics_and_alerting/internal/repositories" ) func Test_storage_GetMetric(t *testing.T) { @@ -45,7 +47,7 @@ func Test_storage_GetMetric(t *testing.T) { s: &s, args: args{"gauge", "Malloc"}, want: "", - err: repositories.ErrNotFound, + err: repository.ErrNotFound, }, { name: "neg bad request test #4", @@ -134,3 +136,30 @@ func Test_storage_UpdateMetric(t *testing.T) { }) } } + +func Test_storage_GetAllMetrics(t *testing.T) { + type args struct { + ctx context.Context + } + tests := []struct { + name string + s *storage + args args + want entity.MetricsMaps + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.GetAllMetrics(tt.args.ctx) + if (err != nil) != tt.wantErr { + t.Errorf("storage.GetAllMetrics() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("storage.GetAllMetrics() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/repositories/repositories.go b/internal/repository/repositories.go similarity index 97% rename from internal/repositories/repositories.go rename to internal/repository/repositories.go index 45d724b..4bcc8d1 100644 --- a/internal/repositories/repositories.go +++ b/internal/repository/repositories.go @@ -1,4 +1,4 @@ -package repositories +package repository import ( "context" @@ -17,6 +17,7 @@ type Repositiries interface { UpdateGauge(ctx context.Context, metricObj models.Metrics) error UpdateCounter(ctx context.Context, metricObj models.Metrics) error UpdateMetricSet(ctx context.Context, metrics []models.Metrics) (int64, error) + GetGauge(ctx context.Context, metricObj models.Metrics) (models.Metrics, error) GetCounter(ctx context.Context, metricObj models.Metrics) (models.Metrics, error) PingDB() error diff --git a/metricset.json b/metricset.json index 232f241..38b917c 100644 --- a/metricset.json +++ b/metricset.json @@ -1,162 +1,162 @@ -[ - { - "id": "HeapAlloc", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "NextGC", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "MCacheSys", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "OtherSys", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "MCacheInuse", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "StackSys", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "Frees", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "GCSys", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "HeapObjects", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "Sys", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "FreeMemory", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "LastGC", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "MSpanSys", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "PauseTotalNs", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "Lookups", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "TotalAlloc", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "CPUutilization1", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "HeapInuse", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "HeapReleased", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "HeapSys", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "Mallocs", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "NumGC", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "Alloc", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "GCCPUFraction", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "HeapIdle", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "StackInuse", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "RandomValue", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "BuckHashSys", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "MSpanInuse", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "NumForcedGC", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "TotalMemory", - "type": "gauge", - "value": 3782369280 - }, - { - "id": "PollCount", - "type": "counter", - "delta": 1 - } +[ + { + "id": "HeapAlloc", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "NextGC", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "MCacheSys", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "OtherSys", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "MCacheInuse", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "StackSys", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "Frees", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "GCSys", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "HeapObjects", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "Sys", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "FreeMemory", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "LastGC", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "MSpanSys", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "PauseTotalNs", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "Lookups", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "TotalAlloc", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "CPUutilization1", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "HeapInuse", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "HeapReleased", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "HeapSys", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "Mallocs", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "NumGC", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "Alloc", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "GCCPUFraction", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "HeapIdle", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "StackInuse", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "RandomValue", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "BuckHashSys", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "MSpanInuse", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "NumForcedGC", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "TotalMemory", + "type": "gauge", + "value": 3782369280 + }, + { + "id": "PollCount", + "type": "counter", + "delta": 1 + } ] \ No newline at end of file diff --git a/pkg/utils/retry/retry.go b/pkg/utils/retry/retry.go index dd7232a..8a7e7ba 100644 --- a/pkg/utils/retry/retry.go +++ b/pkg/utils/retry/retry.go @@ -6,7 +6,7 @@ import ( "time" "github.com/The-Gleb/go_metrics_and_alerting/internal/logger" - "github.com/The-Gleb/go_metrics_and_alerting/internal/repositories" + "github.com/The-Gleb/go_metrics_and_alerting/internal/repository" ) var RetryConfig retryConfig @@ -16,7 +16,7 @@ func init() { waitTime: time.Duration(1), waitTimeDiff: time.Duration(2), retryCount: 3, - retryErrors: []error{repositories.ErrConnection}, + retryErrors: []error{repository.ErrConnection}, } } diff --git a/result.bin b/result.bin index 50648c1..bf0fa49 100644 Binary files a/result.bin and b/result.bin differ