From c282b8151ba2eda1a16c1762427c27d6cb61f8e8 Mon Sep 17 00:00:00 2001 From: Pavel Date: Sat, 27 Jan 2024 15:51:32 +0300 Subject: [PATCH] add stats route --- docs/openapi.yaml | 35 ++++++++++- internal/app/app.go | 2 + .../promocode/promocode_controller.go | 9 +++ internal/models/bonus.go | 6 ++ internal/repository/promocode_stats.go | 59 +++++++++++++++++++ internal/server/http/http_server.go | 1 + internal/services/promocode_service.go | 25 +++++++- tests/e2e/promo_test.go | 20 +++++++ 8 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 internal/repository/promocode_stats.go diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 49b562b..ba2a82d 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -312,7 +312,25 @@ paths: properties: error: type: string - + /promocode/stats: + get: + summary: Получить статистику промокода + responses: + '200': + description: Статистика промокода успешно получена + content: + application/json: + schema: + $ref: '#/components/schemas/PromoCodeStats' + '500': + description: Внутренняя ошибка сервера + content: + application/json: + schema: + type: object + properties: + error: + type: string components: schemas: @@ -505,4 +523,17 @@ components: properties: greetings: type: string - description: Поле из активированного промокода \ No newline at end of file + description: Поле из активированного промокода + + PromoCodeStats: + type: object + properties: + id: + type: string + description: ID промокода + usageCount: + type: object + description: Количество использований промокода для каждого пользователя + usageHistory: + type: object + description: История использования промокода для каждого пользователя diff --git a/internal/app/app.go b/internal/app/app.go index 3f1b003..4dc2cb7 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -67,6 +67,7 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { encrypt := initialize.Encrypt(cfg) promoCodeRepo := repository.NewPromoCodeRepository(mdb.Collection("promoCodes")) + statsRepo := repository.NewStatsRepository(repository.Deps{Rdb: nil, Mdb: mdb.Collection("promoStats")}) codewordRepo := repository.NewCodewordRepository(repository.Deps{Rdb: rdb, Mdb: mdb.Collection("codeword")}) userRepo := repository.NewUserRepository(repository.Deps{Rdb: nil, Mdb: mdb.Collection("users")}) @@ -84,6 +85,7 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { promoService := services.NewPromoCodeService(services.PromoDeps{ Logger: logger, PromoCodeRepo: promoCodeRepo, + StatsRepo: statsRepo, Kafka: brokers.TariffProducer, DiscountClient: discountRpcClient, }) diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index a83c9a1..ad799a3 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -165,3 +165,12 @@ func (p *PromoCodeController) CreateFastLink(c *fiber.Ctx) error { return c.Status(fiber.StatusCreated).JSON(fiber.Map{"fastlink": fastLink}) } + +func (p *PromoCodeController) GetAllStats(c *fiber.Ctx) error { + promoStats, err := p.promoCodeService.GetAllStats(c.Context()) + if err != nil { + p.logger.Error("Failed getting promo stats", zap.Error(err)) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) + } + return c.Status(fiber.StatusOK).JSON(promoStats) +} diff --git a/internal/models/bonus.go b/internal/models/bonus.go index 5612739..31bfc8d 100644 --- a/internal/models/bonus.go +++ b/internal/models/bonus.go @@ -67,3 +67,9 @@ type ActivateReq struct { type ActivateResp struct { Greetings string `json:"greetings"` // поле из активированного промокода } + +type PromoCodeStats struct { + ID string `bson:"_id,omitempty" json:"id,omitempty"` + UsageCount map[string]int `bson:"usageCount" json:"usageCount"` + UsageHistory map[string][]time.Time `bson:"usageHistory" json:"usageHistory"` +} diff --git a/internal/repository/promocode_stats.go b/internal/repository/promocode_stats.go new file mode 100644 index 0000000..1f0a215 --- /dev/null +++ b/internal/repository/promocode_stats.go @@ -0,0 +1,59 @@ +package repository + +import ( + "codeword/internal/models" + "context" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "time" +) + +type StatsRepository struct { + mdb *mongo.Collection +} + +func NewStatsRepository(deps Deps) *StatsRepository { + + return &StatsRepository{mdb: deps.Mdb} +} + +func (r *StatsRepository) UpdateStatistics(ctx context.Context, key, userID string) error { + update := bson.M{ + "$inc": bson.M{"usageCount." + userID: 1}, + "$push": bson.M{"usageHistory." + userID: time.Now()}, + } + + opts := options.Update().SetUpsert(true) + filter := bson.M{"_id": key} + + _, err := r.mdb.UpdateOne(ctx, filter, update, opts) + return err +} + +func (r *StatsRepository) GetAllStatistics(ctx context.Context) ([]models.PromoCodeStats, error) { + filter := bson.M{} + + opts := options.Find() + + cursor, err := r.mdb.Find(ctx, filter, opts) + if err != nil { + return nil, err + } + defer cursor.Close(ctx) + + var promoCodeStatsList []models.PromoCodeStats + for cursor.Next(ctx) { + var promoCodeStats models.PromoCodeStats + if err := cursor.Decode(&promoCodeStats); err != nil { + return nil, err + } + promoCodeStatsList = append(promoCodeStatsList, promoCodeStats) + } + + if err := cursor.Err(); err != nil { + return nil, err + } + + return promoCodeStatsList, nil +} diff --git a/internal/server/http/http_server.go b/internal/server/http/http_server.go index cadc234..6800a91 100644 --- a/internal/server/http/http_server.go +++ b/internal/server/http/http_server.go @@ -61,6 +61,7 @@ func (s *Server) registerRoutes() { s.app.Post("/promocode/activate", s.PromoCodeController.Activate) s.app.Delete("/promocode/:promocodeID", s.PromoCodeController.Delete) s.app.Post("/promocode/fastlink", s.PromoCodeController.CreateFastLink) + s.app.Get("/promocode/stats", s.PromoCodeController.GetAllStats) //... other } diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index c438345..cca3a5e 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -21,9 +21,15 @@ type PromoCodeRepository interface { AddFastLink(ctx context.Context, promoCodeID primitive.ObjectID, xid string) error } +type PromoStatsRepository interface { + UpdateStatistics(ctx context.Context, key, userID string) error + GetAllStatistics(ctx context.Context) ([]models.PromoCodeStats, error) +} + type PromoDeps struct { Logger *zap.Logger PromoCodeRepo PromoCodeRepository + StatsRepo PromoStatsRepository Kafka *tariff.Producer DiscountClient discount.DiscountServiceClient } @@ -31,6 +37,7 @@ type PromoDeps struct { type PromoCodeService struct { logger *zap.Logger promoCodeRepo PromoCodeRepository + statsRepo PromoStatsRepository kafka *tariff.Producer discountClient discount.DiscountServiceClient } @@ -39,6 +46,7 @@ func NewPromoCodeService(deps PromoDeps) *PromoCodeService { return &PromoCodeService{ logger: deps.Logger, promoCodeRepo: deps.PromoCodeRepo, + statsRepo: deps.StatsRepo, kafka: deps.Kafka, discountClient: deps.DiscountClient, } @@ -76,7 +84,7 @@ func (s *PromoCodeService) GetPromoCodesList(ctx context.Context, req *models.Ge // todo одумать еще реализацию этого дела, надо уточнить как разделяется ответственность в бонусе между привилегией и скидкой // разделяется ли она или они всегда вместе, если разделяются то что-то из этого может быть пустым либо все заполеннное, -//соответсвенно надо сделать соответствующие проверки до записи в кафку и до отправки в дискаунт сервис +// соответсвенно надо сделать соответствующие проверки до записи в кафку и до отправки в дискаунт сервис func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.ActivateReq) (string, error) { promoCode, err := s.promoCodeRepo.ActivatePromo(ctx, req) @@ -85,6 +93,12 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa return "", err } + err = s.statsRepo.UpdateStatistics(ctx, promoCode.ID.Hex(), req.UserID) + if err != nil { + s.logger.Error("Failed add in stats", zap.Error(err)) + return "", err + } + var postfix string if req.FastLink != "" { @@ -159,3 +173,12 @@ func (s *PromoCodeService) CreateFastLink(ctx context.Context, promoCodeID strin return xid, nil } + +func (s *PromoCodeService) GetAllStats(ctx context.Context) ([]models.PromoCodeStats, error) { + promoStats, err := s.statsRepo.GetAllStatistics(ctx) + if err != nil { + s.logger.Error("Failed getting promo stats", zap.Error(err)) + return nil, err + } + return promoStats, nil +} diff --git a/tests/e2e/promo_test.go b/tests/e2e/promo_test.go index df1f2a0..c85b866 100644 --- a/tests/e2e/promo_test.go +++ b/tests/e2e/promo_test.go @@ -533,6 +533,26 @@ func TestActivatePromoCode(t *testing.T) { }) } +// GetPromoStats +func TestGetPromoStats(t *testing.T) { + client := fiber.AcquireClient() + + t.Run("GetAllStats", func(t *testing.T) { + req := client.Get(BaseUrl + "/promocode/stats") + + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } + assert.Equal(t, fiber.StatusOK, statusCode) + + var response []models.PromoCodeStats + err := json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fmt.Println(response) + }) +} + // DeletePromoCode func TestDeletePromoCode(t *testing.T) { client := fiber.AcquireClient()