From 95559a0fdad64239fa9ce326b0cfe84e2502ef60 Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 11 Jan 2024 19:29:53 +0300 Subject: [PATCH 01/66] init base struct for promocode --- internal/app/app.go | 19 +++++++--- .../promocode/promocode_controller.go | 35 +++++++++++++++++++ internal/models/bonus.go | 28 +++++++++++++++ internal/repository/codeword_repository.go | 14 ++++---- internal/repository/promocode_repository.go | 25 +++++++++++++ internal/repository/user_repository.go | 8 ++--- internal/server/http/http_server.go | 17 +++++---- internal/services/promocode_service.go | 32 +++++++++++++++++ 8 files changed, 157 insertions(+), 21 deletions(-) create mode 100644 internal/controller/promocode/promocode_controller.go create mode 100644 internal/models/bonus.go create mode 100644 internal/repository/promocode_repository.go create mode 100644 internal/services/promocode_service.go diff --git a/internal/app/app.go b/internal/app/app.go index 112ded4..80368e6 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -1,7 +1,8 @@ package app import ( - controller "codeword/internal/controller/recovery" + promocontroller "codeword/internal/controller/promocode" + reccontroller "codeword/internal/controller/recovery" "codeword/internal/initialize" "codeword/internal/repository" httpserver "codeword/internal/server/http" @@ -24,8 +25,11 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { rdb, err := initialize.Redis(ctx, cfg) encrypt := initialize.Encrypt(cfg) + codewordRepo := repository.NewCodewordRepository(repository.Deps{Rdb: rdb, Mdb: mdb.Collection("codeword")}) userRepo := repository.NewUserRepository(repository.Deps{Rdb: nil, Mdb: mdb.Collection("users")}) + promoCodeRepo := repository.NewPromoCodeRepository(mdb.Collection("promocodes")) + recoveryEmailSender := initialize.RecoveryEmailSender(cfg, logger) authClient := initialize.AuthClient(cfg, logger) @@ -37,7 +41,13 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { AuthClient: authClient, }) - recoveryController := controller.NewRecoveryController(logger, recoveryService, cfg.DefaultRedirectionURL) + promoService := services.NewPromoCodeService(services.PromoDeps{ + Logger: logger, + PromoCodeRepo: promoCodeRepo, + }) + + recoveryController := reccontroller.NewRecoveryController(logger, recoveryService, cfg.DefaultRedirectionURL) + promoCodeController := promocontroller.NewPromoCodeController(logger, promoService) recoveryWC := recovery_worker.NewRecoveryWC(recovery_worker.Deps{ Logger: logger, @@ -55,8 +65,9 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { go purgeWC.Start(ctx) server := httpserver.NewServer(httpserver.ServerConfig{ - Logger: logger, - RecoveryController: recoveryController, + Logger: logger, + RecoveryController: recoveryController, + PromoCodeController: promoCodeController, }) go func() { diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go new file mode 100644 index 0000000..415cb5f --- /dev/null +++ b/internal/controller/promocode/promocode_controller.go @@ -0,0 +1,35 @@ +package controller + +import ( + "codeword/internal/models" + "codeword/internal/services" + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" +) + +type PromoCodeController struct { + logger *zap.Logger + promoCodeService *services.PromoCodeService +} + +func NewPromoCodeController(logger *zap.Logger, promoCodeService *services.PromoCodeService) *PromoCodeController { + return &PromoCodeController{ + logger: logger, + promoCodeService: promoCodeService, + } +} + +func (p *PromoCodeController) CreatePromoCode(c *fiber.Ctx) error { + var reqCreatePromoCode models.PromoCode + if err := c.BodyParser(&reqCreatePromoCode); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) + } + + createdPromoCode, err := p.promoCodeService.CreatePromoCode(c.Context(), &reqCreatePromoCode) + if err != nil { + p.logger.Error("Failed to create promocode", zap.Error(err)) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) + } + + return c.Status(fiber.StatusCreated).JSON(createdPromoCode) +} diff --git a/internal/models/bonus.go b/internal/models/bonus.go new file mode 100644 index 0000000..394c844 --- /dev/null +++ b/internal/models/bonus.go @@ -0,0 +1,28 @@ +package models + +import "time" + +type PromoCode struct { + ID string `json:"id"` + Codeword string `json:"codeword"` // то, что будет вводить пользователь, чтобы получить плюшки + Description string `json:"description"` // описание, необходимое менеджеру в админке + Greetings string `json:"greetings"` // текст, выдаваемый пользователю в ответ на активацию промокода + DueTo int64 `json:"dueTo"` // таймштамп времени окончания работы активации промокода + ActivationCount int64 `json:"activationCount"` // предел количества активаций промокода + Bonus struct { + Privilege struct { + PrivilegeID string `json:"privilegeID"` // айдишник привилегии, которая будет выдаваться + Amount uint64 `json:"amount"` // количество + } `json:"privilege"` + Discount struct { + Layer int `json:"layer"` // 1|2 + Factor float64 `json:"factor"` // процент скидки, вернее множитель, при котором достигается этот процент скидки + Target string `json:"target"` // PrivilegeID или ServiceKey в зависимости от слоя + Threshold int64 `json:"threshold"` // граничное значение, при пересечении которого применяется эта скидка + } `json:"discount"` + } `json:"bonus"` + Outdated bool `json:"outdated"` + OffLimit bool `json:"offLimit"` + Delete bool `json:"delete"` + CreatedAt time.Time `json:"createdAt"` +} diff --git a/internal/repository/codeword_repository.go b/internal/repository/codeword_repository.go index 75d38e1..1b134fa 100644 --- a/internal/repository/codeword_repository.go +++ b/internal/repository/codeword_repository.go @@ -12,18 +12,18 @@ import ( "time" ) -type codewordRepository struct { +type CodewordRepository struct { mdb *mongo.Collection rdb *redis.Client } -func NewCodewordRepository(deps Deps) *codewordRepository { +func NewCodewordRepository(deps Deps) *CodewordRepository { - return &codewordRepository{mdb: deps.Mdb, rdb: deps.Rdb} + return &CodewordRepository{mdb: deps.Mdb, rdb: deps.Rdb} } // сохраняем полученные данные о пользователе и подписи в бд -func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, deps models.StoreRecDeps) (string, error) { +func (r *CodewordRepository) StoreRecoveryRecord(ctx context.Context, deps models.StoreRecDeps) (string, error) { newID := primitive.NewObjectID() signID := deps.Key + newID.Hex() record := models.RestoreRequest{ @@ -45,7 +45,7 @@ func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, deps model } // добавляем в очередь данные для отправки на почту в редис -func (r *codewordRepository) InsertToQueue(ctx context.Context, deps models.RecEmailDeps) error { +func (r *CodewordRepository) InsertToQueue(ctx context.Context, deps models.RecEmailDeps) error { task := models.RecoveryRecord{ ID: deps.ID, UserID: deps.UserID, @@ -66,7 +66,7 @@ func (r *codewordRepository) InsertToQueue(ctx context.Context, deps models.RecE } // получаем данные юзера по подписи -func (r *codewordRepository) GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) { +func (r *CodewordRepository) GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) { var restoreRequest models.RestoreRequest filter := bson.M{"sign_id": key} @@ -80,7 +80,7 @@ func (r *codewordRepository) GetRecoveryRecord(ctx context.Context, key string) } // пингует в монгу чтобы проверить подключение -func (r *codewordRepository) Ping(ctx context.Context) error { +func (r *CodewordRepository) Ping(ctx context.Context) error { if err := r.mdb.Database().Client().Ping(ctx, readpref.Primary()); err != nil { return err } diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go new file mode 100644 index 0000000..0b3dff3 --- /dev/null +++ b/internal/repository/promocode_repository.go @@ -0,0 +1,25 @@ +package repository + +import ( + "codeword/internal/models" + "context" + "go.mongodb.org/mongo-driver/mongo" + "time" +) + +type PromoCodeRepository struct { + mdb *mongo.Collection +} + +func NewPromoCodeRepository(mdb *mongo.Collection) *PromoCodeRepository { + return &PromoCodeRepository{mdb: mdb} +} + +func (r *PromoCodeRepository) CreatePromoCode(ctx context.Context, promoCode *models.PromoCode) (*models.PromoCode, error) { + promoCode.CreatedAt = time.Now() + _, err := r.mdb.InsertOne(ctx, promoCode) + if err != nil { + return nil, err + } + return promoCode, nil +} diff --git a/internal/repository/user_repository.go b/internal/repository/user_repository.go index 3b2e2c7..3403afb 100644 --- a/internal/repository/user_repository.go +++ b/internal/repository/user_repository.go @@ -13,17 +13,17 @@ type Deps struct { Rdb *redis.Client } -type userRepository struct { +type UserRepository struct { mdb *mongo.Collection } -func NewUserRepository(deps Deps) *userRepository { +func NewUserRepository(deps Deps) *UserRepository { - return &userRepository{mdb: deps.Mdb} + return &UserRepository{mdb: deps.Mdb} } // ищем пользователя по мейлу в коллекции users -func (r *userRepository) FindByEmail(ctx context.Context, email string) (*models.User, error) { +func (r *UserRepository) FindByEmail(ctx context.Context, email string) (*models.User, error) { var user models.User err := r.mdb.FindOne(ctx, bson.M{"email": email}).Decode(&user) diff --git a/internal/server/http/http_server.go b/internal/server/http/http_server.go index ca397ff..8326c9b 100644 --- a/internal/server/http/http_server.go +++ b/internal/server/http/http_server.go @@ -1,7 +1,8 @@ package http import ( - controller "codeword/internal/controller/recovery" + promocontroller "codeword/internal/controller/promocode" + reccontroller "codeword/internal/controller/recovery" "context" "fmt" "github.com/gofiber/fiber/v2" @@ -10,14 +11,16 @@ import ( ) type ServerConfig struct { - Logger *zap.Logger - RecoveryController *controller.RecoveryController + Logger *zap.Logger + RecoveryController *reccontroller.RecoveryController + PromoCodeController *promocontroller.PromoCodeController } type Server struct { - Logger *zap.Logger - RecoveryController *controller.RecoveryController - app *fiber.App + Logger *zap.Logger + RecoveryController *reccontroller.RecoveryController + PromoCodeController *promocontroller.PromoCodeController + app *fiber.App } func NewServer(config ServerConfig) *Server { @@ -52,6 +55,8 @@ func (s *Server) registerRoutes() { s.app.Post("/recover", s.RecoveryController.HandleRecoveryRequest) s.app.Get("/recover/:sign", s.RecoveryController.HandleRecoveryLink) + + s.app.Post("/promocode/create", s.PromoCodeController.CreatePromoCode) //... other } diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go new file mode 100644 index 0000000..119714d --- /dev/null +++ b/internal/services/promocode_service.go @@ -0,0 +1,32 @@ +package services + +import ( + "codeword/internal/models" + "context" + "go.uber.org/zap" +) + +type PromoCodeRepository interface { + CreatePromoCode(ctx context.Context, promoCode *models.PromoCode) (*models.PromoCode, error) +} + +type PromoDeps struct { + Logger *zap.Logger + PromoCodeRepo PromoCodeRepository +} + +type PromoCodeService struct { + logger *zap.Logger + promoCodeRepo PromoCodeRepository +} + +func NewPromoCodeService(deps PromoDeps) *PromoCodeService { + return &PromoCodeService{ + logger: deps.Logger, + promoCodeRepo: deps.PromoCodeRepo, + } +} + +func (s *PromoCodeService) CreatePromoCode(ctx context.Context, req *models.PromoCode) (*models.PromoCode, error) { + return nil, nil +} From f3df811c9b6ce9a1c5198e1fc509c6e9a01b3554 Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 11 Jan 2024 21:20:33 +0300 Subject: [PATCH 02/66] add base logic for promo created --- internal/app/app.go | 10 ++--- .../promocode/promocode_controller.go | 9 +++- .../recovery/recovery_controller.go | 2 +- internal/models/bonus.go | 43 ++++++++++--------- internal/repository/promocode_repository.go | 31 ++++++++++++- internal/server/http/http_server.go | 19 ++++---- internal/services/promocode_service.go | 10 ++++- 7 files changed, 85 insertions(+), 39 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 80368e6..ef17660 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -1,8 +1,8 @@ package app import ( - promocontroller "codeword/internal/controller/promocode" - reccontroller "codeword/internal/controller/recovery" + "codeword/internal/controller/promocode" + "codeword/internal/controller/recovery" "codeword/internal/initialize" "codeword/internal/repository" httpserver "codeword/internal/server/http" @@ -26,9 +26,9 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { rdb, err := initialize.Redis(ctx, cfg) encrypt := initialize.Encrypt(cfg) + promoCodeRepo := repository.NewPromoCodeRepository(mdb.Collection("promoCodes")) codewordRepo := repository.NewCodewordRepository(repository.Deps{Rdb: rdb, Mdb: mdb.Collection("codeword")}) userRepo := repository.NewUserRepository(repository.Deps{Rdb: nil, Mdb: mdb.Collection("users")}) - promoCodeRepo := repository.NewPromoCodeRepository(mdb.Collection("promocodes")) recoveryEmailSender := initialize.RecoveryEmailSender(cfg, logger) authClient := initialize.AuthClient(cfg, logger) @@ -46,8 +46,8 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { PromoCodeRepo: promoCodeRepo, }) - recoveryController := reccontroller.NewRecoveryController(logger, recoveryService, cfg.DefaultRedirectionURL) - promoCodeController := promocontroller.NewPromoCodeController(logger, promoService) + recoveryController := recovery.NewRecoveryController(logger, recoveryService, cfg.DefaultRedirectionURL) + promoCodeController := promocode.NewPromoCodeController(logger, promoService) recoveryWC := recovery_worker.NewRecoveryWC(recovery_worker.Deps{ Logger: logger, diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index 415cb5f..6bd5d29 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -1,8 +1,10 @@ -package controller +package promocode import ( "codeword/internal/models" + "codeword/internal/repository" "codeword/internal/services" + "errors" "github.com/gofiber/fiber/v2" "go.uber.org/zap" ) @@ -28,6 +30,11 @@ func (p *PromoCodeController) CreatePromoCode(c *fiber.Ctx) error { createdPromoCode, err := p.promoCodeService.CreatePromoCode(c.Context(), &reqCreatePromoCode) if err != nil { p.logger.Error("Failed to create promocode", zap.Error(err)) + + if errors.Is(err, repository.ErrDuplicateCodeword) { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Duplicate Codeword"}) + } + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } diff --git a/internal/controller/recovery/recovery_controller.go b/internal/controller/recovery/recovery_controller.go index cd10b1d..3ced796 100644 --- a/internal/controller/recovery/recovery_controller.go +++ b/internal/controller/recovery/recovery_controller.go @@ -1,4 +1,4 @@ -package controller +package recovery import ( "codeword/internal/models" diff --git a/internal/models/bonus.go b/internal/models/bonus.go index 394c844..da48c38 100644 --- a/internal/models/bonus.go +++ b/internal/models/bonus.go @@ -1,28 +1,31 @@ package models -import "time" +import ( + "go.mongodb.org/mongo-driver/bson/primitive" + "time" +) type PromoCode struct { - ID string `json:"id"` - Codeword string `json:"codeword"` // то, что будет вводить пользователь, чтобы получить плюшки - Description string `json:"description"` // описание, необходимое менеджеру в админке - Greetings string `json:"greetings"` // текст, выдаваемый пользователю в ответ на активацию промокода - DueTo int64 `json:"dueTo"` // таймштамп времени окончания работы активации промокода - ActivationCount int64 `json:"activationCount"` // предел количества активаций промокода + ID primitive.ObjectID `json:"id" bson:"_id"` + Codeword string `json:"codeword" bson:"codeword"` // то, что будет вводить пользователь, чтобы получить плюшки + Description string `json:"description" bson:"description"` // описание, необходимое менеджеру в админке + Greetings string `json:"greetings" bson:"greetings"` // текст, выдаваемый пользователю в ответ на активацию промокода + DueTo int64 `json:"dueTo" bson:"dueTo"` // таймштамп времени окончания работы активации промокода + ActivationCount int64 `json:"activationCount" bson:"activationCount"` // предел количества активаций промокода Bonus struct { Privilege struct { - PrivilegeID string `json:"privilegeID"` // айдишник привилегии, которая будет выдаваться - Amount uint64 `json:"amount"` // количество - } `json:"privilege"` + PrivilegeID string `json:"privilegeID" bson:"privilegeID"` // айдишник привилегии, которая будет выдаваться + Amount uint64 `json:"amount" bson:"amount"` // количество + } `json:"privilege" bson:"privilege"` Discount struct { - Layer int `json:"layer"` // 1|2 - Factor float64 `json:"factor"` // процент скидки, вернее множитель, при котором достигается этот процент скидки - Target string `json:"target"` // PrivilegeID или ServiceKey в зависимости от слоя - Threshold int64 `json:"threshold"` // граничное значение, при пересечении которого применяется эта скидка - } `json:"discount"` - } `json:"bonus"` - Outdated bool `json:"outdated"` - OffLimit bool `json:"offLimit"` - Delete bool `json:"delete"` - CreatedAt time.Time `json:"createdAt"` + Layer int `json:"layer" bson:"layer"` // 1|2 + Factor float64 `json:"factor" bson:"factor"` // процент скидки, вернее множитель, при котором достигается этот процент скидки + Target string `json:"target" bson:"target"` // PrivilegeID или ServiceKey в зависимости от слоя + Threshold int64 `json:"threshold" bson:"threshold"` // граничное значение, при пересечении которого применяется эта скидка + } `json:"discount" bson:"discount"` + } `json:"bonus" bson:"bonus"` + Outdated bool `json:"outdated" bson:"outdated"` + OffLimit bool `json:"offLimit" bson:"offLimit"` + Delete bool `json:"delete" bson:"delete"` + CreatedAt time.Time `json:"createdAt" bson:"createdAt"` } diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index 0b3dff3..739bb15 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -3,23 +3,52 @@ package repository import ( "codeword/internal/models" "context" + "errors" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" "time" ) +var ErrDuplicateCodeword = errors.New("duplicate codeword") + type PromoCodeRepository struct { mdb *mongo.Collection } func NewPromoCodeRepository(mdb *mongo.Collection) *PromoCodeRepository { + indexModel := mongo.IndexModel{ + Keys: bson.D{ + {"codeword", 1}, + {"delete", 1}, + }, + Options: options.Index().SetUnique(true).SetPartialFilterExpression(bson.M{"delete": false}), + } + _, err := mdb.Indexes().CreateOne(context.Background(), indexModel) + if err != nil { + panic(err) + } + return &PromoCodeRepository{mdb: mdb} } -func (r *PromoCodeRepository) CreatePromoCode(ctx context.Context, promoCode *models.PromoCode) (*models.PromoCode, error) { +func (r *PromoCodeRepository) CreatePromo(ctx context.Context, promoCode *models.PromoCode) (*models.PromoCode, error) { promoCode.CreatedAt = time.Now() + promoCode.ID = primitive.NewObjectID() + _, err := r.mdb.InsertOne(ctx, promoCode) if err != nil { + if writeErr, ok := err.(mongo.WriteException); ok { + for _, writeError := range writeErr.WriteErrors { + if writeError.Code == 11000 { + return nil, ErrDuplicateCodeword + } + } + } + return nil, err } + return promoCode, nil } diff --git a/internal/server/http/http_server.go b/internal/server/http/http_server.go index 8326c9b..cf118b8 100644 --- a/internal/server/http/http_server.go +++ b/internal/server/http/http_server.go @@ -1,8 +1,8 @@ package http import ( - promocontroller "codeword/internal/controller/promocode" - reccontroller "codeword/internal/controller/recovery" + "codeword/internal/controller/promocode" + "codeword/internal/controller/recovery" "context" "fmt" "github.com/gofiber/fiber/v2" @@ -12,14 +12,14 @@ import ( type ServerConfig struct { Logger *zap.Logger - RecoveryController *reccontroller.RecoveryController - PromoCodeController *promocontroller.PromoCodeController + RecoveryController *recovery.RecoveryController + PromoCodeController *promocode.PromoCodeController } type Server struct { Logger *zap.Logger - RecoveryController *reccontroller.RecoveryController - PromoCodeController *promocontroller.PromoCodeController + RecoveryController *recovery.RecoveryController + PromoCodeController *promocode.PromoCodeController app *fiber.App } @@ -27,9 +27,10 @@ func NewServer(config ServerConfig) *Server { app := fiber.New() s := &Server{ - Logger: config.Logger, - RecoveryController: config.RecoveryController, - app: app, + Logger: config.Logger, + RecoveryController: config.RecoveryController, + PromoCodeController: config.PromoCodeController, + app: app, } s.registerRoutes() diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index 119714d..33f7b71 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -7,7 +7,7 @@ import ( ) type PromoCodeRepository interface { - CreatePromoCode(ctx context.Context, promoCode *models.PromoCode) (*models.PromoCode, error) + CreatePromo(ctx context.Context, promoCode *models.PromoCode) (*models.PromoCode, error) } type PromoDeps struct { @@ -28,5 +28,11 @@ func NewPromoCodeService(deps PromoDeps) *PromoCodeService { } func (s *PromoCodeService) CreatePromoCode(ctx context.Context, req *models.PromoCode) (*models.PromoCode, error) { - return nil, nil + promoCode, err := s.promoCodeRepo.CreatePromo(ctx, req) + if err != nil { + s.logger.Error("Failed to add promocode in database", zap.Error(err)) + return nil, err + } + + return promoCode, nil } From 02f85f6fe3880640ed8e65ee8d4890b3d888b6fc Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 11 Jan 2024 21:23:15 +0300 Subject: [PATCH 03/66] some fix names methods --- internal/repository/promocode_repository.go | 2 +- internal/services/promocode_service.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index 739bb15..8e5a6dc 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -33,7 +33,7 @@ func NewPromoCodeRepository(mdb *mongo.Collection) *PromoCodeRepository { return &PromoCodeRepository{mdb: mdb} } -func (r *PromoCodeRepository) CreatePromo(ctx context.Context, promoCode *models.PromoCode) (*models.PromoCode, error) { +func (r *PromoCodeRepository) CreatePromoCode(ctx context.Context, promoCode *models.PromoCode) (*models.PromoCode, error) { promoCode.CreatedAt = time.Now() promoCode.ID = primitive.NewObjectID() diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index 33f7b71..20af7b1 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -7,7 +7,7 @@ import ( ) type PromoCodeRepository interface { - CreatePromo(ctx context.Context, promoCode *models.PromoCode) (*models.PromoCode, error) + CreatePromoCode(ctx context.Context, promoCode *models.PromoCode) (*models.PromoCode, error) } type PromoDeps struct { @@ -28,7 +28,7 @@ func NewPromoCodeService(deps PromoDeps) *PromoCodeService { } func (s *PromoCodeService) CreatePromoCode(ctx context.Context, req *models.PromoCode) (*models.PromoCode, error) { - promoCode, err := s.promoCodeRepo.CreatePromo(ctx, req) + promoCode, err := s.promoCodeRepo.CreatePromoCode(ctx, req) if err != nil { s.logger.Error("Failed to add promocode in database", zap.Error(err)) return nil, err From 2924f7ebeeee1763c80ed410c21800bb81b34d0b Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 11 Jan 2024 21:46:47 +0300 Subject: [PATCH 04/66] update openapi --- docs/openapi.yaml | 148 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index dcbd082..f8ce1bf 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -85,3 +85,151 @@ paths: description: NotAcceptable - срок действия ссылки для восстановления истек или она недействительна '500': description: Внутренняя ошибка сервера – разные причины + + /promocode/create: + post: + summary: Создать новый промокод + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PromoCodeRequest' + responses: + '201': + description: Новый промокод успешно создан + content: + application/json: + schema: + $ref: '#/components/schemas/PromoCodeResponse' + '400': + description: Invalid request payload / Duplicate Codeword + content: + application/json: + schema: + type: object + properties: + error: + type: string + '500': + description: Внутренняя ошибка сервера + content: + application/json: + schema: + type: object + properties: + error: + type: string + +components: + schemas: + PromoCodeRequest: + type: object + properties: + codeword: + type: string + description: Кодовое слово, которое должен ввести пользователь + description: + type: string + description: Описание, необходимое для администратора в панели управления + greetings: + type: string + description: Текст, который будет отправлен пользователю в ответ на активацию кода + dueTo: + type: integer + format: int64 + description: Временная метка окончания активации кода + activationCount: + type: integer + format: int64 + description: Лимит активации кода + bonus: + type: object + properties: + privilege: + type: object + properties: + privilegeID: + type: string + description: Идентификатор привилегии, которую необходимо предоставить + amount: + type: integer + format: uint64 + description: Размер привилегии + discount: + type: object + properties: + layer: + type: integer + factor: + type: number + target: + type: string + threshold: + type: integer + description: Информация о бонусах + outdated: + type: boolean + offLimit: + type: boolean + delete: + type: boolean + + PromoCodeResponse: + type: object + properties: + id: + type: string + description: ID созданного промокода + codeword: + type: string + description: Кодовое слово промокода + description: + type: string + description: Описание промокода + greetings: + type: string + description: Текст, который будет отправлен пользователю в ответ на активацию кода + dueTo: + type: integer + format: int64 + description: Временная метка окончания активации кода + activationCount: + type: integer + format: int64 + description: Лимит активации кода + bonus: + type: object + properties: + privilege: + type: object + properties: + privilegeID: + type: string + description: Идентификатор привилегии, которую необходимо предоставить + amount: + type: integer + format: uint64 + description: Размер привилегии + discount: + type: object + properties: + layer: + type: integer + factor: + type: number + target: + type: string + threshold: + type: integer + description: Информация о бонусах + outdated: + type: boolean + offLimit: + type: boolean + delete: + type: boolean + createdAt: + type: string + format: date-time + description: Время создания промокода \ No newline at end of file From 5ff557c2c2907ddab54c514258425fe2a70012f1 Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 11 Jan 2024 22:47:54 +0300 Subject: [PATCH 05/66] add edit promo route --- docs/openapi.yaml | 73 ++++++++++++++++++- .../promocode/promocode_controller.go | 20 +++++ internal/models/bonus.go | 11 +++ internal/repository/promocode_repository.go | 48 +++++++++++- internal/server/http/http_server.go | 1 + internal/services/promocode_service.go | 11 +++ 6 files changed, 162 insertions(+), 2 deletions(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index f8ce1bf..8769379 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -121,6 +121,51 @@ paths: error: type: string + /promocode/edit: + put: + summary: Обновить существующий промокод + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ReqEditPromoCode' + responses: + '200': + description: Промокод успешно обновлен + content: + application/json: + schema: + $ref: '#/components/schemas/PromoCodeResponse' + '400': + description: Неверный формат запроса + content: + application/json: + schema: + type: object + properties: + error: + type: string + '404': + description: Промокод не найден + content: + application/json: + schema: + type: object + properties: + error: + type: string + '500': + description: Внутренняя ошибка сервера + content: + application/json: + schema: + type: object + properties: + error: + type: string + + components: schemas: PromoCodeRequest: @@ -232,4 +277,30 @@ components: createdAt: type: string format: date-time - description: Время создания промокода \ No newline at end of file + description: Время создания промокода + + ReqEditPromoCode: + type: object + properties: + id: + type: string + description: ID промокода, который обновляем + description: + type: string + description: Описание, необходимое менеджеру в админке + greetings: + type: string + description: Текст, выдаваемый пользователю в ответ на активацию промокода + dueTo: + type: integer + format: int64 + description: Временная метка окончания активации кода + activationCount: + type: integer + format: int64 + description: Предел количества активаций промокода + delete: + type: boolean + description: Флаг удаления промокода + required: + - id diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index 6bd5d29..7bd0014 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -40,3 +40,23 @@ func (p *PromoCodeController) CreatePromoCode(c *fiber.Ctx) error { return c.Status(fiber.StatusCreated).JSON(createdPromoCode) } + +func (p *PromoCodeController) EditPromoCode(c *fiber.Ctx) error { + var reqEditPromoCode models.ReqEditPromoCode + if err := c.BodyParser(&reqEditPromoCode); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) + } + + editedPromoCode, err := p.promoCodeService.EditPromoCode(c.Context(), &reqEditPromoCode) + if err != nil { + p.logger.Error("Failed to edit promocode", zap.Error(err)) + + if errors.Is(err, repository.ErrPromoCodeNotFound) { + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "PromoCode not found"}) + } + + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) + } + + return c.Status(fiber.StatusOK).JSON(editedPromoCode) +} diff --git a/internal/models/bonus.go b/internal/models/bonus.go index da48c38..ff7cf8e 100644 --- a/internal/models/bonus.go +++ b/internal/models/bonus.go @@ -29,3 +29,14 @@ type PromoCode struct { Delete bool `json:"delete" bson:"delete"` CreatedAt time.Time `json:"createdAt" bson:"createdAt"` } + +type ReqEditPromoCode struct { + ID string `json:"id" bson:"_id"` //айдишник промокода, который обновляем + Description string `json:"description" bson:"description"` // описание, необходимое менеджеру в админке + Greetings string `json:"greetings" bson:"greetings"` // текст, выдаваемый пользователю в ответ на активацию промокода + + DueTo int64 `json:"dueTo" bson:"dueTo"` // таймштамп времени окончания работы активации промокода + ActivationCount int64 `json:"activationCount" bson:"activationCount"` // предел количества активаций промокода + + Delete bool `json:"delete" bson:"delete"` +} diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index 8e5a6dc..11a209f 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -11,7 +11,10 @@ import ( "time" ) -var ErrDuplicateCodeword = errors.New("duplicate codeword") +var ( + ErrDuplicateCodeword = errors.New("duplicate codeword") + ErrPromoCodeNotFound = errors.New("promo code not found") +) type PromoCodeRepository struct { mdb *mongo.Collection @@ -52,3 +55,46 @@ func (r *PromoCodeRepository) CreatePromoCode(ctx context.Context, promoCode *mo return promoCode, nil } + +func (r *PromoCodeRepository) EditPromoCode(ctx context.Context, EditPromoCode *models.ReqEditPromoCode) (*models.PromoCode, error) { + promoCodeID, err := primitive.ObjectIDFromHex(EditPromoCode.ID) + if err != nil { + return nil, err + } + + filter := bson.M{"_id": promoCodeID} + update := bson.M{ + "$set": bson.M{ + "description": EditPromoCode.Description, + "greetings": EditPromoCode.Greetings, + "dueTo": EditPromoCode.DueTo, + "activationCount": EditPromoCode.ActivationCount, + "delete": EditPromoCode.Delete, + }, + } + + result, err := r.mdb.UpdateOne(ctx, filter, update) + if err != nil { + return nil, err + } + + if result.MatchedCount == 0 { + return nil, ErrPromoCodeNotFound + } + + return r.GetPromoCodeByID(ctx, promoCodeID) +} + +func (r *PromoCodeRepository) GetPromoCodeByID(ctx context.Context, promoCodeID primitive.ObjectID) (*models.PromoCode, error) { + var promoCode models.PromoCode + err := r.mdb.FindOne(ctx, bson.M{"_id": promoCodeID}).Decode(&promoCode) + if err != nil { + if err == mongo.ErrNoDocuments { + return nil, ErrPromoCodeNotFound + } + + return nil, err + } + + return &promoCode, nil +} diff --git a/internal/server/http/http_server.go b/internal/server/http/http_server.go index cf118b8..9c70564 100644 --- a/internal/server/http/http_server.go +++ b/internal/server/http/http_server.go @@ -58,6 +58,7 @@ func (s *Server) registerRoutes() { s.app.Get("/recover/:sign", s.RecoveryController.HandleRecoveryLink) s.app.Post("/promocode/create", s.PromoCodeController.CreatePromoCode) + s.app.Put("/promocode/edit", s.PromoCodeController.EditPromoCode) //... other } diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index 20af7b1..e8f92cf 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -8,6 +8,7 @@ import ( type PromoCodeRepository interface { CreatePromoCode(ctx context.Context, promoCode *models.PromoCode) (*models.PromoCode, error) + EditPromoCode(ctx context.Context, reqEditPromoCode *models.ReqEditPromoCode) (*models.PromoCode, error) } type PromoDeps struct { @@ -36,3 +37,13 @@ func (s *PromoCodeService) CreatePromoCode(ctx context.Context, req *models.Prom return promoCode, nil } + +func (s *PromoCodeService) EditPromoCode(ctx context.Context, req *models.ReqEditPromoCode) (*models.PromoCode, error) { + editedPromoCode, err := s.promoCodeRepo.EditPromoCode(ctx, req) + if err != nil { + s.logger.Error("Failed to edit promocode in database", zap.Error(err)) + return nil, err + } + + return editedPromoCode, nil +} From db372231ff2d076f4ace6bb20fed6ad5ee305b45 Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 12 Jan 2024 12:42:56 +0300 Subject: [PATCH 06/66] add update restrictions --- .../promocode/promocode_controller.go | 4 +++ internal/models/bonus.go | 12 +++---- internal/repository/promocode_repository.go | 35 ++++++++++++------- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index 7bd0014..8c854cb 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -47,6 +47,10 @@ func (p *PromoCodeController) EditPromoCode(c *fiber.Ctx) error { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) } + if reqEditPromoCode.ID == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "promocode ID is required"}) + } + editedPromoCode, err := p.promoCodeService.EditPromoCode(c.Context(), &reqEditPromoCode) if err != nil { p.logger.Error("Failed to edit promocode", zap.Error(err)) diff --git a/internal/models/bonus.go b/internal/models/bonus.go index ff7cf8e..1f3158d 100644 --- a/internal/models/bonus.go +++ b/internal/models/bonus.go @@ -31,12 +31,12 @@ type PromoCode struct { } type ReqEditPromoCode struct { - ID string `json:"id" bson:"_id"` //айдишник промокода, который обновляем - Description string `json:"description" bson:"description"` // описание, необходимое менеджеру в админке - Greetings string `json:"greetings" bson:"greetings"` // текст, выдаваемый пользователю в ответ на активацию промокода + ID string `json:"id" bson:"_id"` //айдишник промокода, который обновляем + Description *string `json:"description,omitempty" bson:"description"` // описание, необходимое менеджеру в админке + Greetings *string `json:"greetings,omitempty" bson:"greetings"` // текст, выдаваемый пользователю в ответ на активацию промокода - DueTo int64 `json:"dueTo" bson:"dueTo"` // таймштамп времени окончания работы активации промокода - ActivationCount int64 `json:"activationCount" bson:"activationCount"` // предел количества активаций промокода + DueTo *int64 `json:"dueTo,omitempty" bson:"dueTo"` // таймштамп времени окончания работы активации промокода + ActivationCount *int64 `json:"activationCount,omitempty" bson:"activationCount"` // предел количества активаций промокода - Delete bool `json:"delete" bson:"delete"` + Delete *bool `json:"delete,omitempty" bson:"delete"` } diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index 11a209f..841537b 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -56,24 +56,35 @@ func (r *PromoCodeRepository) CreatePromoCode(ctx context.Context, promoCode *mo return promoCode, nil } -func (r *PromoCodeRepository) EditPromoCode(ctx context.Context, EditPromoCode *models.ReqEditPromoCode) (*models.PromoCode, error) { - promoCodeID, err := primitive.ObjectIDFromHex(EditPromoCode.ID) +func (r *PromoCodeRepository) EditPromoCode(ctx context.Context, editPromoCode *models.ReqEditPromoCode) (*models.PromoCode, error) { + promoCodeID, err := primitive.ObjectIDFromHex(editPromoCode.ID) if err != nil { return nil, err } - filter := bson.M{"_id": promoCodeID} - update := bson.M{ - "$set": bson.M{ - "description": EditPromoCode.Description, - "greetings": EditPromoCode.Greetings, - "dueTo": EditPromoCode.DueTo, - "activationCount": EditPromoCode.ActivationCount, - "delete": EditPromoCode.Delete, - }, + updateFields := bson.M{} + if editPromoCode.Description != nil { + updateFields["description"] = *editPromoCode.Description + } + if editPromoCode.Greetings != nil { + updateFields["greetings"] = *editPromoCode.Greetings + } + if editPromoCode.DueTo != nil { + updateFields["dueTo"] = *editPromoCode.DueTo + } + if editPromoCode.ActivationCount != nil { + updateFields["activationCount"] = *editPromoCode.ActivationCount + } + if editPromoCode.Delete != nil { + updateFields["delete"] = *editPromoCode.Delete } - result, err := r.mdb.UpdateOne(ctx, filter, update) + if len(updateFields) == 0 { + return r.GetPromoCodeByID(ctx, promoCodeID) + } + + update := bson.M{"$set": updateFields} + result, err := r.mdb.UpdateOne(ctx, bson.M{"_id": promoCodeID}, update) if err != nil { return nil, err } From a74a149d06c69d2ee7f8a64187c471044d877299 Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 12 Jan 2024 14:28:22 +0300 Subject: [PATCH 07/66] add base logic for getList --- docs/openapi.yaml | 65 ++++++++++ .../promocode/promocode_controller.go | 33 ++++- internal/models/bonus.go | 16 +++ internal/repository/promocode_repository.go | 121 +++++++++++++++--- internal/server/http/http_server.go | 1 + internal/services/promocode_service.go | 15 ++- 6 files changed, 221 insertions(+), 30 deletions(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 8769379..4c5383f 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -165,6 +165,34 @@ paths: error: type: string + /promocode/getList: + post: + summary: Получить список промокодов + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GetPromoCodesListReq' + responses: + '200': + description: Список промокодов и общее количество успешно получены + content: + application/json: + schema: + $ref: '#/components/schemas/GetPromoCodesListResp' + '400': + description: Неверный запрос из-за невалидных данных + content: + application/json: + schema: + type: object + properties: + error: + type: string + '500': + description: Внутренняя ошибка сервера + components: schemas: @@ -304,3 +332,40 @@ components: description: Флаг удаления промокода required: - id + GetPromoCodesListReq: + type: object + required: + - page + - limit + - filter + properties: + page: + type: integer + description: Номер страницы выборки, начиная с 0 + limit: + type: integer + description: Размер страницы выборки + filter: + $ref: '#/components/schemas/GetPromoCodesListReqFilter' + + GetPromoCodesListReqFilter: + type: object + properties: + text: + type: string + description: Полнотекстовый поиск по полям Codeword, Description, Greetings + active: + type: boolean + description: Если true, выбираются записи, где delete, outdated и offLimit равны false + + GetPromoCodesListResp: + type: object + properties: + count: + type: integer + format: int64 + description: Общее количество промокодов в выборке + items: + type: array + items: + $ref: '#/components/schemas/PromoCodeResponse' \ No newline at end of file diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index 8c854cb..783d4ea 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -22,12 +22,12 @@ func NewPromoCodeController(logger *zap.Logger, promoCodeService *services.Promo } func (p *PromoCodeController) CreatePromoCode(c *fiber.Ctx) error { - var reqCreatePromoCode models.PromoCode - if err := c.BodyParser(&reqCreatePromoCode); err != nil { + var req models.PromoCode + if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) } - createdPromoCode, err := p.promoCodeService.CreatePromoCode(c.Context(), &reqCreatePromoCode) + createdPromoCode, err := p.promoCodeService.CreatePromoCode(c.Context(), &req) if err != nil { p.logger.Error("Failed to create promocode", zap.Error(err)) @@ -42,16 +42,16 @@ func (p *PromoCodeController) CreatePromoCode(c *fiber.Ctx) error { } func (p *PromoCodeController) EditPromoCode(c *fiber.Ctx) error { - var reqEditPromoCode models.ReqEditPromoCode - if err := c.BodyParser(&reqEditPromoCode); err != nil { + var req models.ReqEditPromoCode + if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) } - if reqEditPromoCode.ID == "" { + if req.ID == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "promocode ID is required"}) } - editedPromoCode, err := p.promoCodeService.EditPromoCode(c.Context(), &reqEditPromoCode) + editedPromoCode, err := p.promoCodeService.EditPromoCode(c.Context(), &req) if err != nil { p.logger.Error("Failed to edit promocode", zap.Error(err)) @@ -64,3 +64,22 @@ func (p *PromoCodeController) EditPromoCode(c *fiber.Ctx) error { return c.Status(fiber.StatusOK).JSON(editedPromoCode) } + +func (p *PromoCodeController) GetList(c *fiber.Ctx) error { + var req models.GetPromoCodesListReq + if err := c.BodyParser(&req); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) + } + + promoCodes, count, err := p.promoCodeService.GetPromoCodesList(c.Context(), &req) + if err != nil { + p.logger.Error("Failed to retrieve promocode list", zap.Error(err)) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) + } + + resp := models.GetPromoCodesListResp{ + Count: count, + Items: promoCodes, + } + return c.Status(fiber.StatusOK).JSON(resp) +} diff --git a/internal/models/bonus.go b/internal/models/bonus.go index 1f3158d..7a4dc9e 100644 --- a/internal/models/bonus.go +++ b/internal/models/bonus.go @@ -40,3 +40,19 @@ type ReqEditPromoCode struct { Delete *bool `json:"delete,omitempty" bson:"delete"` } + +type GetPromoCodesListReqFilter struct { + Text string `json:"text"` // полнотекстовый поиск пo Codeword, Decription, Greetings полям + Active bool `json:"active"` // если true, то выбирать deleted==false && outdated== false && offlimit == false +} + +type GetPromoCodesListReq struct { + Page int `json:"page"` //номер страницы выборки. начинается с 0. по сути, skip для выборки из mongodb + Limit int `json:"limit"` //размер страницы выборки. больше 10, меньше 250. отвечает за skip = page*limit, и за limit + Filter GetPromoCodesListReqFilter `json:"filter"` +} + +type GetPromoCodesListResp struct { + Count int64 `json:"count"` // количество в выборке всего + Items []PromoCode `json:"items"` // "страница" промокодов +} diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index 841537b..eecc193 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -4,6 +4,7 @@ import ( "codeword/internal/models" "context" "errors" + "github.com/pioz/faker" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" @@ -16,19 +17,39 @@ var ( ErrPromoCodeNotFound = errors.New("promo code not found") ) +// структура для горутины чтобы ошибки не пропускать +type countResult struct { + count int64 + err error +} + type PromoCodeRepository struct { mdb *mongo.Collection } func NewPromoCodeRepository(mdb *mongo.Collection) *PromoCodeRepository { - indexModel := mongo.IndexModel{ + // todo заменить паники вроде как в роде не круто их юзать + uniqueIndexModel := mongo.IndexModel{ Keys: bson.D{ - {"codeword", 1}, - {"delete", 1}, + {Key: "codeword", Value: 1}, + {Key: "delete", Value: 1}, }, Options: options.Index().SetUnique(true).SetPartialFilterExpression(bson.M{"delete": false}), } - _, err := mdb.Indexes().CreateOne(context.Background(), indexModel) + _, err := mdb.Indexes().CreateOne(context.Background(), uniqueIndexModel) + if err != nil { + panic(err) + } + + textIndexModel := mongo.IndexModel{ + Keys: bson.D{ + {Key: "codeword", Value: "text"}, + {Key: "description", Value: "text"}, + {Key: "greetings", Value: "text"}, + }, + Options: options.Index().SetName("TextIndex"), + } + _, err = mdb.Indexes().CreateOne(context.Background(), textIndexModel) if err != nil { panic(err) } @@ -36,11 +57,13 @@ func NewPromoCodeRepository(mdb *mongo.Collection) *PromoCodeRepository { return &PromoCodeRepository{mdb: mdb} } -func (r *PromoCodeRepository) CreatePromoCode(ctx context.Context, promoCode *models.PromoCode) (*models.PromoCode, error) { - promoCode.CreatedAt = time.Now() - promoCode.ID = primitive.NewObjectID() +func (r *PromoCodeRepository) CreatePromoCode(ctx context.Context, req *models.PromoCode) (*models.PromoCode, error) { - _, err := r.mdb.InsertOne(ctx, promoCode) + req.Codeword = req.Codeword + faker.String() + req.CreatedAt = time.Now() + req.ID = primitive.NewObjectID() + + _, err := r.mdb.InsertOne(ctx, req) if err != nil { if writeErr, ok := err.(mongo.WriteException); ok { for _, writeError := range writeErr.WriteErrors { @@ -53,30 +76,30 @@ func (r *PromoCodeRepository) CreatePromoCode(ctx context.Context, promoCode *mo return nil, err } - return promoCode, nil + return req, nil } -func (r *PromoCodeRepository) EditPromoCode(ctx context.Context, editPromoCode *models.ReqEditPromoCode) (*models.PromoCode, error) { - promoCodeID, err := primitive.ObjectIDFromHex(editPromoCode.ID) +func (r *PromoCodeRepository) EditPromoCode(ctx context.Context, req *models.ReqEditPromoCode) (*models.PromoCode, error) { + promoCodeID, err := primitive.ObjectIDFromHex(req.ID) if err != nil { return nil, err } updateFields := bson.M{} - if editPromoCode.Description != nil { - updateFields["description"] = *editPromoCode.Description + if req.Description != nil { + updateFields["description"] = *req.Description } - if editPromoCode.Greetings != nil { - updateFields["greetings"] = *editPromoCode.Greetings + if req.Greetings != nil { + updateFields["greetings"] = *req.Greetings } - if editPromoCode.DueTo != nil { - updateFields["dueTo"] = *editPromoCode.DueTo + if req.DueTo != nil { + updateFields["dueTo"] = *req.DueTo } - if editPromoCode.ActivationCount != nil { - updateFields["activationCount"] = *editPromoCode.ActivationCount + if req.ActivationCount != nil { + updateFields["activationCount"] = *req.ActivationCount } - if editPromoCode.Delete != nil { - updateFields["delete"] = *editPromoCode.Delete + if req.Delete != nil { + updateFields["delete"] = *req.Delete } if len(updateFields) == 0 { @@ -109,3 +132,59 @@ func (r *PromoCodeRepository) GetPromoCodeByID(ctx context.Context, promoCodeID return &promoCode, nil } + +func (r *PromoCodeRepository) GetPromoCodesList(ctx context.Context, req *models.GetPromoCodesListReq) ([]models.PromoCode, int64, error) { + filter := bson.M{} + + if req.Filter.Text != "" { + filter["$text"] = bson.M{"$search": req.Filter.Text} + } + + if req.Filter.Active { + filter["delete"] = false + filter["outdated"] = false + filter["offLimit"] = false + } else { + filter["$or"] = []interface{}{ + bson.M{"delete": true}, + bson.M{"outdated": true}, + bson.M{"offLimit": true}, + } + } + + opt := options.Find().SetSkip(int64(req.Page * req.Limit)).SetLimit(int64(req.Limit)) + + var countChan = make(chan countResult) + go func() { + defer close(countChan) + count, err := r.mdb.CountDocuments(ctx, filter) + countChan <- countResult{count, err} + }() + + cursor, err := r.mdb.Find(ctx, filter, opt) + if err != nil { + return nil, 0, err + } + defer cursor.Close(ctx) + + var promoCodes = make([]models.PromoCode, 0) + for cursor.Next(ctx) { + var p models.PromoCode + if err := cursor.Decode(&p); err != nil { + return nil, 0, err + } + promoCodes = append(promoCodes, p) + } + + if err := cursor.Err(); err != nil { + return nil, 0, err + } + + result := <-countChan + if result.err != nil { + return nil, 0, result.err + } + count := result.count + + return promoCodes, count, nil +} diff --git a/internal/server/http/http_server.go b/internal/server/http/http_server.go index 9c70564..43e75cd 100644 --- a/internal/server/http/http_server.go +++ b/internal/server/http/http_server.go @@ -59,6 +59,7 @@ func (s *Server) registerRoutes() { s.app.Post("/promocode/create", s.PromoCodeController.CreatePromoCode) s.app.Put("/promocode/edit", s.PromoCodeController.EditPromoCode) + s.app.Post("/promocode/getList", s.PromoCodeController.GetList) //... other } diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index e8f92cf..11cd344 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -7,8 +7,9 @@ import ( ) type PromoCodeRepository interface { - CreatePromoCode(ctx context.Context, promoCode *models.PromoCode) (*models.PromoCode, error) - EditPromoCode(ctx context.Context, reqEditPromoCode *models.ReqEditPromoCode) (*models.PromoCode, error) + CreatePromoCode(ctx context.Context, req *models.PromoCode) (*models.PromoCode, error) + EditPromoCode(ctx context.Context, req *models.ReqEditPromoCode) (*models.PromoCode, error) + GetPromoCodesList(ctx context.Context, req *models.GetPromoCodesListReq) ([]models.PromoCode, int64, error) } type PromoDeps struct { @@ -47,3 +48,13 @@ func (s *PromoCodeService) EditPromoCode(ctx context.Context, req *models.ReqEdi return editedPromoCode, nil } + +func (s *PromoCodeService) GetPromoCodesList(ctx context.Context, req *models.GetPromoCodesListReq) ([]models.PromoCode, int64, error) { + promoCodes, count, err := s.promoCodeRepo.GetPromoCodesList(ctx, req) + if err != nil { + s.logger.Error("Failed to get list promocodes from database", zap.Error(err)) + return nil, 0, err + } + + return promoCodes, count, nil +} From b31c3a81c6118ef32d908742c788ebb2d511ca0e Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 12 Jan 2024 14:32:04 +0300 Subject: [PATCH 08/66] replace faker --- internal/repository/promocode_repository.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index eecc193..83944ef 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -4,7 +4,6 @@ import ( "codeword/internal/models" "context" "errors" - "github.com/pioz/faker" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" @@ -58,8 +57,6 @@ func NewPromoCodeRepository(mdb *mongo.Collection) *PromoCodeRepository { } func (r *PromoCodeRepository) CreatePromoCode(ctx context.Context, req *models.PromoCode) (*models.PromoCode, error) { - - req.Codeword = req.Codeword + faker.String() req.CreatedAt = time.Now() req.ID = primitive.NewObjectID() From 483eb2772365879c6e29cf0874ebee704fb5d427 Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 12 Jan 2024 16:46:36 +0300 Subject: [PATCH 09/66] add base logic for activate promo --- .../promocode/promocode_controller.go | 27 ++++++++++++ internal/models/bonus.go | 8 ++++ internal/repository/promocode_repository.go | 44 +++++++++++++++++++ internal/server/http/http_server.go | 1 + internal/services/promocode_service.go | 11 +++++ 5 files changed, 91 insertions(+) diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index 783d4ea..75fe521 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -83,3 +83,30 @@ func (p *PromoCodeController) GetList(c *fiber.Ctx) error { } return c.Status(fiber.StatusOK).JSON(resp) } + +func (p *PromoCodeController) Activate(c *fiber.Ctx) error { + var req models.ActivateReq + if err := c.BodyParser(&req); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) + } + + if req.Codeword == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "codeword is required"}) + } + + greetings, err := p.promoCodeService.ActivatePromo(c.Context(), &req) + if err != nil { + p.logger.Error("Failed to activate promocode", zap.Error(err)) + + if errors.Is(err, repository.ErrPromoCodeNotFound) { + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "PromoCode not found"}) + } + + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) + } + + resp := models.ActivateResp{ + Greetings: greetings, + } + return c.Status(fiber.StatusOK).JSON(resp) +} diff --git a/internal/models/bonus.go b/internal/models/bonus.go index 7a4dc9e..4bec834 100644 --- a/internal/models/bonus.go +++ b/internal/models/bonus.go @@ -56,3 +56,11 @@ type GetPromoCodesListResp struct { Count int64 `json:"count"` // количество в выборке всего Items []PromoCode `json:"items"` // "страница" промокодов } + +type ActivateReq struct { + Codeword string `json:"codeword"` +} + +type ActivateResp struct { + Greetings string `json:"greetings"` // поле из активированного промокода +} diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index 83944ef..1e6fab5 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -185,3 +185,47 @@ func (r *PromoCodeRepository) GetPromoCodesList(ctx context.Context, req *models return promoCodes, count, nil } + +func (r *PromoCodeRepository) ActivatePromo(ctx context.Context, req *models.ActivateReq) (string, error) { + session, err := r.mdb.Database().Client().StartSession() + if err != nil { + return "", err + } + defer session.EndSession(ctx) + + var greetings string + + transactionErr := mongo.WithSession(ctx, session, func(sc mongo.SessionContext) error { + filter := bson.M{ + "codeword": req.Codeword, + "delete": false, + "outdated": false, + "offLimit": false, + "activationCount": bson.M{"$gt": 0}, + "dueTo": bson.M{"$gt": time.Now().Unix()}, + } + update := bson.M{ + "$inc": bson.M{"activationCount": -1}, + } + opts := options.FindOneAndUpdate().SetReturnDocument(options.After) + + var updatedPromoCode models.PromoCode + err := r.mdb.FindOneAndUpdate(sc, filter, update, opts).Decode(&updatedPromoCode) + if err != nil { + if err == mongo.ErrNoDocuments { + return ErrPromoCodeNotFound + } + return err + } + + greetings = updatedPromoCode.Greetings + + return nil + }) + + if transactionErr != nil { + return "", transactionErr + } + + return greetings, nil +} diff --git a/internal/server/http/http_server.go b/internal/server/http/http_server.go index 43e75cd..ce4a0c3 100644 --- a/internal/server/http/http_server.go +++ b/internal/server/http/http_server.go @@ -60,6 +60,7 @@ func (s *Server) registerRoutes() { s.app.Post("/promocode/create", s.PromoCodeController.CreatePromoCode) s.app.Put("/promocode/edit", s.PromoCodeController.EditPromoCode) s.app.Post("/promocode/getList", s.PromoCodeController.GetList) + s.app.Post("/promocode/activate", s.PromoCodeController.Activate) //... other } diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index 11cd344..a016f4a 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -10,6 +10,7 @@ type PromoCodeRepository interface { CreatePromoCode(ctx context.Context, req *models.PromoCode) (*models.PromoCode, error) EditPromoCode(ctx context.Context, req *models.ReqEditPromoCode) (*models.PromoCode, error) GetPromoCodesList(ctx context.Context, req *models.GetPromoCodesListReq) ([]models.PromoCode, int64, error) + ActivatePromo(ctx context.Context, req *models.ActivateReq) (string, error) } type PromoDeps struct { @@ -58,3 +59,13 @@ func (s *PromoCodeService) GetPromoCodesList(ctx context.Context, req *models.Ge return promoCodes, count, nil } + +func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.ActivateReq) (string, error) { + greetings, err := s.promoCodeRepo.ActivatePromo(ctx, req) + if err != nil { + s.logger.Error("Failed to activate promocode", zap.Error(err)) + return "", err + } + + return greetings, nil +} From 45e833a74b840b4913b0188ed7d0ea964eaf8b02 Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 12 Jan 2024 16:57:51 +0300 Subject: [PATCH 10/66] add some docs --- docs/openapi.yaml | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 4c5383f..48a4d6a 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -193,6 +193,29 @@ paths: '500': description: Внутренняя ошибка сервера + /promocode/activate: + post: + summary: Активировать промокод + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ActivateReq' + responses: + '200': + description: Промокод успешно активирован + content: + application/json: + schema: + $ref: '#/components/schemas/ActivateResp' + '400': + description: Невалидный запрос или отсутствует обязательное поле codeword + '404': + description: Промокод не найден + '500': + description: Внутренняя ошибка сервера + components: schemas: @@ -368,4 +391,20 @@ components: items: type: array items: - $ref: '#/components/schemas/PromoCodeResponse' \ No newline at end of file + $ref: '#/components/schemas/PromoCodeResponse' + + ActivateReq: + type: object + required: + - codeword + properties: + codeword: + type: string + description: Кодовое слово промокода, которое требуется активировать + + ActivateResp: + type: object + properties: + greetings: + type: string + description: Поле из активированного промокода \ No newline at end of file From 1019fd3f831e86fd7692f211544833b0caa46fb3 Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 12 Jan 2024 17:50:53 +0300 Subject: [PATCH 11/66] add base logic for delete promocode --- docs/openapi.yaml | 41 +++++++++++++++++++ .../promocode/promocode_controller.go | 23 ++++++++++- internal/repository/promocode_repository.go | 22 ++++++++++ internal/server/http/http_server.go | 1 + internal/services/promocode_service.go | 11 +++++ 5 files changed, 97 insertions(+), 1 deletion(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 48a4d6a..6604ac8 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -216,6 +216,47 @@ paths: '500': description: Внутренняя ошибка сервера + /promocode/{promocodeID}: + delete: + summary: Мягко удалить промокод по его id + parameters: + - in: path + name: promocodeID + required: true + schema: + type: string + description: Id промокода для удаления + responses: + '204': + description: Промокод успешно помечен как удаленный + '400': + description: Неверный запрос, отсутствует идентификатор промокода + content: + application/json: + schema: + type: object + properties: + error: + type: string + '404': + description: Промокод не найден + content: + application/json: + schema: + type: object + properties: + error: + type: string + '500': + description: Внутренняя ошибка сервера + content: + application/json: + schema: + type: object + properties: + error: + type: string + components: schemas: diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index 75fe521..7f0b358 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -101,7 +101,7 @@ func (p *PromoCodeController) Activate(c *fiber.Ctx) error { if errors.Is(err, repository.ErrPromoCodeNotFound) { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "PromoCode not found"}) } - + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } @@ -110,3 +110,24 @@ func (p *PromoCodeController) Activate(c *fiber.Ctx) error { } return c.Status(fiber.StatusOK).JSON(resp) } + +func (p *PromoCodeController) Delete(c *fiber.Ctx) error { + promoCodeID := c.Params("promocodeID") + + if promoCodeID == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "PromoCode ID is required"}) + } + + err := p.promoCodeService.DeletePromoCode(c.Context(), promoCodeID) + if err != nil { + p.logger.Error("Failed to delete promocode", zap.Error(err)) + + if errors.Is(err, repository.ErrPromoCodeNotFound) { + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "PromoCode not found"}) + } + + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) + } + + return c.SendStatus(fiber.StatusOK) +} diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index 1e6fab5..fbfb6d8 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -229,3 +229,25 @@ func (r *PromoCodeRepository) ActivatePromo(ctx context.Context, req *models.Act return greetings, nil } + +func (r *PromoCodeRepository) DeletePromoCode(ctx context.Context, promoCodeID string) error { + id, err := primitive.ObjectIDFromHex(promoCodeID) + if err != nil { + return err + } + + result, err := r.mdb.UpdateOne( + ctx, + bson.M{"_id": id, "delete": false}, + bson.M{"$set": bson.M{"delete": true}}, + ) + if err != nil { + return err + } + + if result.MatchedCount == 0 { + return ErrPromoCodeNotFound + } + + return nil +} diff --git a/internal/server/http/http_server.go b/internal/server/http/http_server.go index ce4a0c3..984ef7d 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.Put("/promocode/edit", s.PromoCodeController.EditPromoCode) s.app.Post("/promocode/getList", s.PromoCodeController.GetList) s.app.Post("/promocode/activate", s.PromoCodeController.Activate) + s.app.Delete("/promocode/:promocodeID", s.PromoCodeController.Delete) //... other } diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index a016f4a..2197c85 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -11,6 +11,7 @@ type PromoCodeRepository interface { EditPromoCode(ctx context.Context, req *models.ReqEditPromoCode) (*models.PromoCode, error) GetPromoCodesList(ctx context.Context, req *models.GetPromoCodesListReq) ([]models.PromoCode, int64, error) ActivatePromo(ctx context.Context, req *models.ActivateReq) (string, error) + DeletePromoCode(ctx context.Context, promoCodeID string) error } type PromoDeps struct { @@ -69,3 +70,13 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa return greetings, nil } + +func (s *PromoCodeService) DeletePromoCode(ctx context.Context, promoCodeID string) error { + err := s.promoCodeRepo.DeletePromoCode(ctx, promoCodeID) + if err != nil { + s.logger.Error("Failed simple delete promocode from database", zap.Error(err)) + return err + } + + return nil +} From c583494fe7bf36a56fc49ebdcfdb484a905d3636 Mon Sep 17 00:00:00 2001 From: Pavel Date: Sun, 14 Jan 2024 17:18:36 +0300 Subject: [PATCH 12/66] add base logic for falstlinks --- go.mod | 1 + go.sum | 2 + .../promocode/promocode_controller.go | 29 +++++++++++- internal/models/bonus.go | 2 + internal/repository/promocode_repository.go | 46 ++++++++++++++++--- internal/server/http/http_server.go | 1 + internal/services/promocode_service.go | 21 +++++++++ internal/utils/genID/gen_id.go | 8 ++++ 8 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 internal/utils/genID/gen_id.go diff --git a/go.mod b/go.mod index 3dbfa38..f9eb5a8 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/rs/xid v1.5.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.50.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect diff --git a/go.sum b/go.sum index 05dc7a4..8e1373d 100644 --- a/go.sum +++ b/go.sum @@ -47,6 +47,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index 7f0b358..fecee9b 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -84,14 +84,15 @@ func (p *PromoCodeController) GetList(c *fiber.Ctx) error { return c.Status(fiber.StatusOK).JSON(resp) } +// todo затестить и обновить в opnapi func (p *PromoCodeController) Activate(c *fiber.Ctx) error { var req models.ActivateReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) } - if req.Codeword == "" { - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "codeword is required"}) + if req.Codeword == "" && req.FastLink == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "codeword or fastlink is required"}) } greetings, err := p.promoCodeService.ActivatePromo(c.Context(), &req) @@ -131,3 +132,27 @@ func (p *PromoCodeController) Delete(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) } + +// todo затестить и добавить в opnapi +func (p *PromoCodeController) CreateFastLink(c *fiber.Ctx) error { + // нужно что-то получать из Localstorage например id или codeword + var req struct { + PromoCodeID string `json:"promoCodeID"` + } + if err := c.BodyParser(&req); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) + } + + fastLink, err := p.promoCodeService.CreateFastLink(c.Context(), req.PromoCodeID) + if err != nil { + p.logger.Error("Failed to create fastlink", zap.Error(err)) + + if errors.Is(err, repository.ErrPromoCodeNotFound) { + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "PromoCode not found"}) + } + + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) + } + + return c.Status(fiber.StatusCreated).JSON(fiber.Map{"fastlink": fastLink}) +} diff --git a/internal/models/bonus.go b/internal/models/bonus.go index 4bec834..f6edeb4 100644 --- a/internal/models/bonus.go +++ b/internal/models/bonus.go @@ -28,6 +28,7 @@ type PromoCode struct { OffLimit bool `json:"offLimit" bson:"offLimit"` Delete bool `json:"delete" bson:"delete"` CreatedAt time.Time `json:"createdAt" bson:"createdAt"` + FastLinks []string `json:"fastLinks" bson:"fastLinks"` } type ReqEditPromoCode struct { @@ -59,6 +60,7 @@ type GetPromoCodesListResp struct { type ActivateReq struct { Codeword string `json:"codeword"` + FastLink string `json:"fastLink"` } type ActivateResp struct { diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index fbfb6d8..69984eb 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -196,14 +196,30 @@ func (r *PromoCodeRepository) ActivatePromo(ctx context.Context, req *models.Act var greetings string transactionErr := mongo.WithSession(ctx, session, func(sc mongo.SessionContext) error { - filter := bson.M{ - "codeword": req.Codeword, - "delete": false, - "outdated": false, - "offLimit": false, - "activationCount": bson.M{"$gt": 0}, - "dueTo": bson.M{"$gt": time.Now().Unix()}, + var filter bson.M + + if req.Codeword != "" { + filter = bson.M{ + "codeword": req.Codeword, + "delete": false, + "outdated": false, + "offLimit": false, + "activationCount": bson.M{"$gt": 0}, + "dueTo": bson.M{"$gt": time.Now().Unix()}, + } + } else if req.FastLink != "" { + filter = bson.M{ + "fastLinks": req.FastLink, + "delete": false, + "outdated": false, + "offLimit": false, + "activationCount": bson.M{"$gt": 0}, + "dueTo": bson.M{"$gt": time.Now().Unix()}, + } + } else { + return errors.New("codeword or xid is required") } + update := bson.M{ "$inc": bson.M{"activationCount": -1}, } @@ -251,3 +267,19 @@ func (r *PromoCodeRepository) DeletePromoCode(ctx context.Context, promoCodeID s return nil } + +func (r *PromoCodeRepository) AddFastLink(ctx context.Context, promoCodeID primitive.ObjectID, xid string) error { + filter := bson.M{"_id": promoCodeID, "delete": false} + update := bson.M{"$push": bson.M{"fastLinks": xid}} + + result, err := r.mdb.UpdateOne(ctx, filter, update) + if err != nil { + return err + } + + if result.MatchedCount == 0 { + return ErrPromoCodeNotFound + } + + return nil +} diff --git a/internal/server/http/http_server.go b/internal/server/http/http_server.go index 984ef7d..b88d824 100644 --- a/internal/server/http/http_server.go +++ b/internal/server/http/http_server.go @@ -62,6 +62,7 @@ func (s *Server) registerRoutes() { s.app.Post("/promocode/getList", s.PromoCodeController.GetList) s.app.Post("/promocode/activate", s.PromoCodeController.Activate) s.app.Delete("/promocode/:promocodeID", s.PromoCodeController.Delete) + s.app.Post("/promocode/fastlink", s.PromoCodeController.CreateFastLink) //... other } diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index 2197c85..cbdd179 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -2,7 +2,9 @@ package services import ( "codeword/internal/models" + "codeword/internal/utils/genID" "context" + "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" ) @@ -12,6 +14,8 @@ type PromoCodeRepository interface { GetPromoCodesList(ctx context.Context, req *models.GetPromoCodesListReq) ([]models.PromoCode, int64, error) ActivatePromo(ctx context.Context, req *models.ActivateReq) (string, error) DeletePromoCode(ctx context.Context, promoCodeID string) error + GetPromoCodeByID(ctx context.Context, promoCodeID primitive.ObjectID) (*models.PromoCode, error) + AddFastLink(ctx context.Context, promoCodeID primitive.ObjectID, xid string) error } type PromoDeps struct { @@ -80,3 +84,20 @@ func (s *PromoCodeService) DeletePromoCode(ctx context.Context, promoCodeID stri return nil } + +func (s *PromoCodeService) CreateFastLink(ctx context.Context, promoCodeID string) (string, error) { + xid := genID.GenerateXID() + promoID, err := primitive.ObjectIDFromHex(promoCodeID) + if err != nil { + s.logger.Error("Failed conversion promoCodeID to ObjectID", zap.Error(err)) + return "", err + } + + err = s.promoCodeRepo.AddFastLink(ctx, promoID, xid) + if err != nil { + s.logger.Error("Failed to add fastlink for promocode by promocode id", zap.Error(err)) + return "", err + } + + return xid, nil +} diff --git a/internal/utils/genID/gen_id.go b/internal/utils/genID/gen_id.go new file mode 100644 index 0000000..2a6610f --- /dev/null +++ b/internal/utils/genID/gen_id.go @@ -0,0 +1,8 @@ +package genID + +import "github.com/rs/xid" + +func GenerateXID() string { + id := xid.New() + return id.String() +} From 881add8f6d2af2d9418e63bb2b56f8b627c32b07 Mon Sep 17 00:00:00 2001 From: Pavel Date: Sun, 14 Jan 2024 17:22:09 +0300 Subject: [PATCH 13/66] remove unnecessary else --- internal/repository/promocode_repository.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index 69984eb..d1622d4 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -216,8 +216,6 @@ func (r *PromoCodeRepository) ActivatePromo(ctx context.Context, req *models.Act "activationCount": bson.M{"$gt": 0}, "dueTo": bson.M{"$gt": time.Now().Unix()}, } - } else { - return errors.New("codeword or xid is required") } update := bson.M{ From e5c3b3786d61b6d5f211c693a722c2e2b5afe510 Mon Sep 17 00:00:00 2001 From: Pavel Date: Mon, 15 Jan 2024 11:43:55 +0300 Subject: [PATCH 14/66] some ref after review --- go.mod | 2 +- internal/app/app.go | 5 +- .../recovery/recovery_controller.go | 65 +++++++++++++------ internal/initialize/mongo.go | 3 + internal/models/user.go | 9 +++ internal/repository/codeword_repository.go | 2 + internal/repository/user_repository.go | 5 +- internal/server/http/http_server.go | 18 +---- internal/services/recovery_service.go | 4 -- 9 files changed, 68 insertions(+), 45 deletions(-) diff --git a/go.mod b/go.mod index f9eb5a8..4aa1db1 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/gofiber/fiber/v2 v2.51.0 github.com/joho/godotenv v1.5.1 github.com/pioz/faker v1.7.3 + github.com/rs/xid v1.5.0 github.com/stretchr/testify v1.8.1 go.mongodb.org/mongo-driver v1.13.1 go.uber.org/zap v1.26.0 @@ -27,7 +28,6 @@ require ( github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/rs/xid v1.5.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.50.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect diff --git a/internal/app/app.go b/internal/app/app.go index ef17660..edc0344 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -17,10 +17,13 @@ import ( func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { logger.Info("Запуск приложения", zap.String("AppName", cfg.AppName)) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + mdb, err := initialize.MongoDB(ctx, cfg) if err != nil { logger.Error("Failed to initialize MongoDB", zap.Error(err)) - return err + cancel() } rdb, err := initialize.Redis(ctx, cfg) diff --git a/internal/controller/recovery/recovery_controller.go b/internal/controller/recovery/recovery_controller.go index 3ced796..3fb7eb2 100644 --- a/internal/controller/recovery/recovery_controller.go +++ b/internal/controller/recovery/recovery_controller.go @@ -2,8 +2,11 @@ package recovery import ( "codeword/internal/models" + "codeword/internal/repository" "codeword/internal/services" "encoding/base64" + "errors" + "fmt" "github.com/gofiber/fiber/v2" "go.uber.org/zap" "time" @@ -24,25 +27,44 @@ func NewRecoveryController(logger *zap.Logger, service *services.RecoveryService } func (r *RecoveryController) HandlePingDB(c *fiber.Ctx) error { - return r.service.Ping(c.Context()) + startTime := time.Now() + if err := r.service.Ping(c.Context()); err != nil { + r.logger.Error("Failed to ping the database", zap.Error(err)) + return c.Status(fiber.StatusServiceUnavailable).SendString("DB ping failed") + } + duration := time.Since(startTime) + + durationMillis := duration.Milliseconds() + responseMessage := fmt.Sprintf("DB ping success - Time taken: %d ms", durationMillis) + + return c.Status(fiber.StatusOK).SendString(responseMessage) } -// HandleRecoveryRequest обрабатывает запрос на восстановление пароля +// todo update docs func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { - email := c.FormValue("email") - referralURL := c.Get("Referrer") - redirectionURL := c.FormValue("RedirectionURL") - - if redirectionURL == "" && referralURL != "" { - redirectionURL = referralURL - } else if redirectionURL == "" { - redirectionURL = r.defaultURL + var req models.RecoveryRequest + if err := c.BodyParser(&req); err != nil { + r.logger.Error("Failed to parse recovery request", zap.Error(err)) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Bad Request"}) } - user, err := r.service.FindUserByEmail(c.Context(), email) - if err != nil || user == nil { + referralURL := c.Get("Referrer") + + if req.RedirectionURL == "" && referralURL != "" { + req.RedirectionURL = referralURL + } else if req.RedirectionURL == "" { + req.RedirectionURL = r.defaultURL + } + + user, err := r.service.FindUserByEmail(c.Context(), req.Email) + if err != nil { r.logger.Error("Failed to find user by email", zap.Error(err)) - return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"}) + + if errors.Is(err, repository.ErrPromoUserNotFound) { + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"}) + } + + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } key, err := r.service.GenerateKey() @@ -51,7 +73,7 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } - signUrl := redirectionURL + base64.URLEncoding.EncodeToString(key) + signUrl := req.RedirectionURL + base64.URLEncoding.EncodeToString(key) sign := base64.URLEncoding.EncodeToString(key) id, err := r.service.StoreRecoveryRecord(c.Context(), models.StoreRecDeps{UserID: user.ID.Hex(), Email: user.Email, Key: sign, Url: signUrl}) @@ -62,7 +84,7 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { signWithID := sign + id // подпись с id записи - err = r.service.RecoveryEmailTask(c.Context(), models.RecEmailDeps{UserID: user.ID.Hex(), Email: email, SignWithID: signWithID, ID: id}) + err = r.service.RecoveryEmailTask(c.Context(), models.RecEmailDeps{UserID: user.ID.Hex(), Email: req.Email, SignWithID: signWithID, ID: id}) if err != nil { r.logger.Error("Failed to send recovery email", zap.Error(err)) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) @@ -73,20 +95,21 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { }) } -// todo тут скорее всего помимо подписи будет передаваться еще что-то, например email пользователя от фронта для поиска в бд - -// HandleRecoveryLink обрабатывает ссылку восстановления и обменивает ее на токены func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error { - key := c.Params("sign") + var req models.RecoveryLinkRequest + if err := c.BodyParser(&req); err != nil { + r.logger.Error("Failed to parse recovery link request", zap.Error(err)) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Bad Request"}) + } - record, err := r.service.GetRecoveryRecord(c.Context(), key) + record, err := r.service.GetRecoveryRecord(c.Context(), req.Sign) if err != nil { r.logger.Error("Failed to get recovery record", zap.Error(err)) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } if time.Since(record.CreatedAt) > 15*time.Minute { - r.logger.Error("Recovery link expired", zap.String("signature", key)) + r.logger.Error("Recovery link expired", zap.String("signature", req.Sign)) return c.Status(fiber.StatusNotAcceptable).JSON(fiber.Map{"error": "Recovery link expired"}) } diff --git a/internal/initialize/mongo.go b/internal/initialize/mongo.go index 64fea74..fb24b43 100644 --- a/internal/initialize/mongo.go +++ b/internal/initialize/mongo.go @@ -17,6 +17,9 @@ func MongoDB(ctx context.Context, cfg Config) (*mongo.Database, error) { MongoAuth: cfg.MongoAuth, } + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + mongoDeps := &mdb.ConnectDeps{ Configuration: dbConfig, Timeout: 10 * time.Second, diff --git a/internal/models/user.go b/internal/models/user.go index dfe0a2f..b1e32bf 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -35,3 +35,12 @@ type RecoveryRecord struct { Email string Key string } + +type RecoveryRequest struct { + Email string `json:"email"` + RedirectionURL string `json:"redirectionURL"` +} + +type RecoveryLinkRequest struct { + Sign string `json:"sign"` +} diff --git a/internal/repository/codeword_repository.go b/internal/repository/codeword_repository.go index 1b134fa..fc8ba1b 100644 --- a/internal/repository/codeword_repository.go +++ b/internal/repository/codeword_repository.go @@ -81,6 +81,8 @@ func (r *CodewordRepository) GetRecoveryRecord(ctx context.Context, key string) // пингует в монгу чтобы проверить подключение func (r *CodewordRepository) Ping(ctx context.Context) error { + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() if err := r.mdb.Database().Client().Ping(ctx, readpref.Primary()); err != nil { return err } diff --git a/internal/repository/user_repository.go b/internal/repository/user_repository.go index 3403afb..8c67287 100644 --- a/internal/repository/user_repository.go +++ b/internal/repository/user_repository.go @@ -3,6 +3,7 @@ package repository import ( "codeword/internal/models" "context" + "errors" "github.com/go-redis/redis/v8" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" @@ -17,6 +18,8 @@ type UserRepository struct { mdb *mongo.Collection } +var ErrPromoUserNotFound = errors.New("user not found") + func NewUserRepository(deps Deps) *UserRepository { return &UserRepository{mdb: deps.Mdb} @@ -31,7 +34,7 @@ func (r *UserRepository) FindByEmail(ctx context.Context, email string) (*models if err == mongo.ErrNoDocuments { return nil, nil } - return nil, err + return nil, ErrPromoUserNotFound } return &user, nil } diff --git a/internal/server/http/http_server.go b/internal/server/http/http_server.go index b88d824..cadc234 100644 --- a/internal/server/http/http_server.go +++ b/internal/server/http/http_server.go @@ -4,10 +4,8 @@ import ( "codeword/internal/controller/promocode" "codeword/internal/controller/recovery" "context" - "fmt" "github.com/gofiber/fiber/v2" "go.uber.org/zap" - "time" ) type ServerConfig struct { @@ -52,7 +50,7 @@ func (s *Server) Shutdown(ctx context.Context) error { func (s *Server) registerRoutes() { s.app.Get("/liveness", s.handleLiveness) - s.app.Get("/readiness", s.handleReadiness) + s.app.Get("/readiness", s.RecoveryController.HandlePingDB) s.app.Post("/recover", s.RecoveryController.HandleRecoveryRequest) s.app.Get("/recover/:sign", s.RecoveryController.HandleRecoveryLink) @@ -69,17 +67,3 @@ func (s *Server) registerRoutes() { func (s *Server) handleLiveness(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) } - -func (s *Server) handleReadiness(c *fiber.Ctx) error { - startTime := time.Now() - if err := s.RecoveryController.HandlePingDB(c); err != nil { - s.Logger.Error("Failed to ping the database", zap.Error(err)) - return c.Status(fiber.StatusServiceUnavailable).SendString("DB ping failed") - } - duration := time.Since(startTime) - - durationMillis := duration.Milliseconds() - responseMessage := fmt.Sprintf("DB ping success - Time taken: %d ms", durationMillis) - - return c.Status(fiber.StatusOK).SendString(responseMessage) -} diff --git a/internal/services/recovery_service.go b/internal/services/recovery_service.go index 664d447..348b834 100644 --- a/internal/services/recovery_service.go +++ b/internal/services/recovery_service.go @@ -73,10 +73,6 @@ func (s *RecoveryService) FindUserByEmail(ctx context.Context, email string) (*m s.logger.Error("Failed to find user by email", zap.String("email", email), zap.Error(err)) return nil, err } - if user == nil { - s.logger.Info("No user found with email", zap.String("email", email)) - return nil, nil - } return user, nil } From 33d0c25397d5fc2b8a455ff0b04bab0839bc6820 Mon Sep 17 00:00:00 2001 From: Pavel Date: Mon, 15 Jan 2024 16:32:46 +0300 Subject: [PATCH 15/66] add docs --- docs/openapi.yaml | 63 ++++++++++++++++++- internal/app/app.go | 6 ++ .../promocode/promocode_controller.go | 3 +- .../recovery/recovery_controller.go | 20 ++---- internal/initialize/mongo.go | 17 ++++- internal/repository/promocode_repository.go | 18 ++++-- 6 files changed, 98 insertions(+), 29 deletions(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 6604ac8..49b562b 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -28,9 +28,11 @@ paths: requestBody: required: true content: - application/x-www-form-urlencoded: + application/json: schema: type: object + required: + - email properties: email: type: string @@ -257,6 +259,60 @@ paths: error: type: string + /promocode/fastlink: + post: + summary: Создать быструю ссылку для промокода + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - id + properties: + id: + type: string + description: ID промокода, для которого нужно создать быструю ссылку + responses: + '201': + description: Быстрая ссылка для промокода успешно создана + content: + application/json: + schema: + type: object + properties: + fastlink: + type: string + description: Быстрая ссылка для активации промокода + '400': + description: Неверный запрос, отсутствует идентификатор промокода + content: + application/json: + schema: + type: object + properties: + error: + type: string + '404': + description: Промокод не найден + content: + application/json: + schema: + type: object + properties: + error: + type: string + '500': + description: Внутренняя ошибка сервера + content: + application/json: + schema: + type: object + properties: + error: + type: string + components: schemas: @@ -436,12 +492,13 @@ components: ActivateReq: type: object - required: - - codeword properties: codeword: type: string description: Кодовое слово промокода, которое требуется активировать + fastLink: + type: string + description: Быстрая ссылка для активации промокода ActivateResp: type: object diff --git a/internal/app/app.go b/internal/app/app.go index edc0344..503c2c8 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -26,6 +26,12 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { cancel() } + if err = initialize.InitDatabaseIndexes(ctx, mdb, logger); err != nil { + logger.Error("Failed to initialize db indexes", zap.Error(err)) + cancel() + return err + } + rdb, err := initialize.Redis(ctx, cfg) encrypt := initialize.Encrypt(cfg) diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index fecee9b..0f7f577 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -135,9 +135,8 @@ func (p *PromoCodeController) Delete(c *fiber.Ctx) error { // todo затестить и добавить в opnapi func (p *PromoCodeController) CreateFastLink(c *fiber.Ctx) error { - // нужно что-то получать из Localstorage например id или codeword var req struct { - PromoCodeID string `json:"promoCodeID"` + PromoCodeID string `json:"id"` } if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) diff --git a/internal/controller/recovery/recovery_controller.go b/internal/controller/recovery/recovery_controller.go index 3fb7eb2..5059a72 100644 --- a/internal/controller/recovery/recovery_controller.go +++ b/internal/controller/recovery/recovery_controller.go @@ -2,10 +2,8 @@ package recovery import ( "codeword/internal/models" - "codeword/internal/repository" "codeword/internal/services" "encoding/base64" - "errors" "fmt" "github.com/gofiber/fiber/v2" "go.uber.org/zap" @@ -57,14 +55,10 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { } user, err := r.service.FindUserByEmail(c.Context(), req.Email) - if err != nil { + if err != nil || user == nil { r.logger.Error("Failed to find user by email", zap.Error(err)) - if errors.Is(err, repository.ErrPromoUserNotFound) { - return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"}) - } - - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"}) } key, err := r.service.GenerateKey() @@ -96,20 +90,16 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { } func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error { - var req models.RecoveryLinkRequest - if err := c.BodyParser(&req); err != nil { - r.logger.Error("Failed to parse recovery link request", zap.Error(err)) - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Bad Request"}) - } + sign := c.Params("sign") - record, err := r.service.GetRecoveryRecord(c.Context(), req.Sign) + record, err := r.service.GetRecoveryRecord(c.Context(), sign) if err != nil { r.logger.Error("Failed to get recovery record", zap.Error(err)) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } if time.Since(record.CreatedAt) > 15*time.Minute { - r.logger.Error("Recovery link expired", zap.String("signature", req.Sign)) + r.logger.Error("Recovery link expired", zap.String("signature", sign)) return c.Status(fiber.StatusNotAcceptable).JSON(fiber.Map{"error": "Recovery link expired"}) } diff --git a/internal/initialize/mongo.go b/internal/initialize/mongo.go index fb24b43..fe98615 100644 --- a/internal/initialize/mongo.go +++ b/internal/initialize/mongo.go @@ -1,9 +1,11 @@ package initialize import ( + "codeword/internal/repository" mdb "codeword/pkg/mongo" "context" "go.mongodb.org/mongo-driver/mongo" + "go.uber.org/zap" "time" ) @@ -17,7 +19,7 @@ func MongoDB(ctx context.Context, cfg Config) (*mongo.Database, error) { MongoAuth: cfg.MongoAuth, } - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + newCtx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() mongoDeps := &mdb.ConnectDeps{ @@ -25,15 +27,24 @@ func MongoDB(ctx context.Context, cfg Config) (*mongo.Database, error) { Timeout: 10 * time.Second, } - db, err := mdb.Connect(ctx, mongoDeps) + db, err := mdb.Connect(newCtx, mongoDeps) if err != nil { return nil, err } - err = db.Client().Ping(ctx, nil) + err = db.Client().Ping(newCtx, nil) if err != nil { return nil, err } return db, nil } + +func InitDatabaseIndexes(ctx context.Context, mdb *mongo.Database, logger *zap.Logger) error { + if err := repository.InitPromoCodeIndexes(ctx, mdb.Collection("promoCodes")); err != nil { + logger.Error("Failed to initialize promoCodes indexes", zap.Error(err)) + return err + } + + return nil +} diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index d1622d4..60c7f6c 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -27,7 +27,10 @@ type PromoCodeRepository struct { } func NewPromoCodeRepository(mdb *mongo.Collection) *PromoCodeRepository { - // todo заменить паники вроде как в роде не круто их юзать + return &PromoCodeRepository{mdb: mdb} +} + +func InitPromoCodeIndexes(ctx context.Context, mdb *mongo.Collection) error { uniqueIndexModel := mongo.IndexModel{ Keys: bson.D{ {Key: "codeword", Value: 1}, @@ -35,9 +38,9 @@ func NewPromoCodeRepository(mdb *mongo.Collection) *PromoCodeRepository { }, Options: options.Index().SetUnique(true).SetPartialFilterExpression(bson.M{"delete": false}), } - _, err := mdb.Indexes().CreateOne(context.Background(), uniqueIndexModel) + _, err := mdb.Indexes().CreateOne(ctx, uniqueIndexModel) if err != nil { - panic(err) + return err } textIndexModel := mongo.IndexModel{ @@ -48,17 +51,20 @@ func NewPromoCodeRepository(mdb *mongo.Collection) *PromoCodeRepository { }, Options: options.Index().SetName("TextIndex"), } - _, err = mdb.Indexes().CreateOne(context.Background(), textIndexModel) + _, err = mdb.Indexes().CreateOne(ctx, textIndexModel) if err != nil { - panic(err) + return err } - return &PromoCodeRepository{mdb: mdb} + return nil } func (r *PromoCodeRepository) CreatePromoCode(ctx context.Context, req *models.PromoCode) (*models.PromoCode, error) { req.CreatedAt = time.Now() req.ID = primitive.NewObjectID() + if req.FastLinks == nil { + req.FastLinks = []string{} + } _, err := r.mdb.InsertOne(ctx, req) if err != nil { From 67877e84bd19e5721417bbc3cc50b4a428de8a95 Mon Sep 17 00:00:00 2001 From: Pavel Date: Mon, 15 Jan 2024 16:34:55 +0300 Subject: [PATCH 16/66] add docs --- internal/controller/promocode/promocode_controller.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index 0f7f577..e15d9ff 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -84,7 +84,6 @@ func (p *PromoCodeController) GetList(c *fiber.Ctx) error { return c.Status(fiber.StatusOK).JSON(resp) } -// todo затестить и обновить в opnapi func (p *PromoCodeController) Activate(c *fiber.Ctx) error { var req models.ActivateReq if err := c.BodyParser(&req); err != nil { @@ -133,7 +132,6 @@ func (p *PromoCodeController) Delete(c *fiber.Ctx) error { return c.SendStatus(fiber.StatusOK) } -// todo затестить и добавить в opnapi func (p *PromoCodeController) CreateFastLink(c *fiber.Ctx) error { var req struct { PromoCodeID string `json:"id"` From 978d4a025f0fee6267fd7e95cb632b280bab9df1 Mon Sep 17 00:00:00 2001 From: Pavel Date: Mon, 15 Jan 2024 23:21:16 +0300 Subject: [PATCH 17/66] some refactor --- internal/app/app.go | 11 ++++++++--- internal/controller/recovery/recovery_controller.go | 1 - 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 503c2c8..d5a1459 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -15,7 +15,13 @@ import ( ) func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { - logger.Info("Запуск приложения", zap.String("AppName", cfg.AppName)) + defer func() { + if r := recover(); r != nil { + logger.Error("Recovered from a panic", zap.Any("error", r)) + } + }() + + logger.Info("Starting application", zap.String("AppName", cfg.AppName)) ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -23,12 +29,11 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { mdb, err := initialize.MongoDB(ctx, cfg) if err != nil { logger.Error("Failed to initialize MongoDB", zap.Error(err)) - cancel() + return err } if err = initialize.InitDatabaseIndexes(ctx, mdb, logger); err != nil { logger.Error("Failed to initialize db indexes", zap.Error(err)) - cancel() return err } diff --git a/internal/controller/recovery/recovery_controller.go b/internal/controller/recovery/recovery_controller.go index 5059a72..98769f2 100644 --- a/internal/controller/recovery/recovery_controller.go +++ b/internal/controller/recovery/recovery_controller.go @@ -38,7 +38,6 @@ func (r *RecoveryController) HandlePingDB(c *fiber.Ctx) error { return c.Status(fiber.StatusOK).SendString(responseMessage) } -// todo update docs func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { var req models.RecoveryRequest if err := c.BodyParser(&req); err != nil { From 9a6e596071fc6a7fae0dca23234d4c846d290d4a Mon Sep 17 00:00:00 2001 From: Pavel Date: Tue, 16 Jan 2024 18:10:16 +0300 Subject: [PATCH 18/66] add closer --- internal/app/app.go | 56 ++++++++----------- internal/worker/purge_worker/purge_worker.go | 6 +- .../worker/recovery_worker/recovery_worker.go | 4 ++ pkg/closer/closer.go | 37 ++++++++++++ 4 files changed, 68 insertions(+), 35 deletions(-) create mode 100644 pkg/closer/closer.go diff --git a/internal/app/app.go b/internal/app/app.go index d5a1459..bc59aca 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -9,9 +9,11 @@ import ( "codeword/internal/services" "codeword/internal/worker/purge_worker" "codeword/internal/worker/recovery_worker" + "codeword/pkg/closer" "context" - "go.mongodb.org/mongo-driver/mongo" + "errors" "go.uber.org/zap" + "time" ) func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { @@ -26,6 +28,8 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { ctx, cancel := context.WithCancel(ctx) defer cancel() + shutdownGroup := closer.NewCloserGroup() + mdb, err := initialize.MongoDB(ctx, cfg) if err != nil { logger.Error("Failed to initialize MongoDB", zap.Error(err)) @@ -70,7 +74,7 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { Mongo: mdb.Collection("codeword"), }) - purgeWC := purge_worker.NewRecoveryWC(purge_worker.Deps{ + purgeWC := purge_worker.NewPurgeWC(purge_worker.Deps{ Logger: logger, Mongo: mdb.Collection("codeword"), }) @@ -87,44 +91,28 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { go func() { if err := server.Start(cfg.HTTPHost + ":" + cfg.HTTPPort); err != nil { logger.Error("Server startup error", zap.Error(err)) + cancel() } }() + shutdownGroup.Add(closer.CloserFunc(server.Shutdown)) + shutdownGroup.Add(closer.CloserFunc(mdb.Client().Disconnect)) + shutdownGroup.Add(closer.CloserFunc(recoveryWC.Stop)) + shutdownGroup.Add(closer.CloserFunc(purgeWC.Stop)) + <-ctx.Done() - if err := shutdownApp(ctx, server, mdb, logger); err != nil { - return err - } - logger.Info("The application has stopped") - return nil -} - -// TODO возможно стоит вынести в отдельные файлы или отказаться от разделения на отдельные методы - -func shutdownApp(ctx context.Context, server *httpserver.Server, mdb *mongo.Database, logger *zap.Logger) error { - if err := shutdownHTTPServer(ctx, server, logger); err != nil { - return err - } - - if err := shutdownMongoDB(ctx, mdb, logger); err != nil { - return err - } - - return nil -} - -func shutdownHTTPServer(ctx context.Context, server *httpserver.Server, logger *zap.Logger) error { - if err := server.Shutdown(ctx); err != nil { - logger.Error("Error stopping HTTP server", zap.Error(err)) - return err - } - return nil -} - -func shutdownMongoDB(ctx context.Context, mdb *mongo.Database, logger *zap.Logger) error { - if err := mdb.Client().Disconnect(ctx); err != nil { - logger.Error("Error when closing MongoDB connection", zap.Error(err)) + timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), 10*time.Second) + defer timeoutCancel() + if err := shutdownGroup.Call(timeoutCtx); err != nil { + if errors.Is(err, context.DeadlineExceeded) { + logger.Error("Shutdown timed out", zap.Error(err)) + } else { + logger.Error("Failed to shutdown services gracefully", zap.Error(err)) + } return err } + + logger.Info("Application has stopped") return nil } diff --git a/internal/worker/purge_worker/purge_worker.go b/internal/worker/purge_worker/purge_worker.go index e621641..017c7f5 100644 --- a/internal/worker/purge_worker/purge_worker.go +++ b/internal/worker/purge_worker/purge_worker.go @@ -18,7 +18,7 @@ type PurgeWorker struct { mongo *mongo.Collection } -func NewRecoveryWC(deps Deps) *PurgeWorker { +func NewPurgeWC(deps Deps) *PurgeWorker { return &PurgeWorker{ logger: deps.Logger, mongo: deps.Mongo, @@ -54,3 +54,7 @@ func (wc *PurgeWorker) processTasks(ctx context.Context) { wc.logger.Info("Deleted documents", zap.Int64("count", result.DeletedCount)) } } + +func (wc *PurgeWorker) Stop(ctx context.Context) error { + return nil +} diff --git a/internal/worker/recovery_worker/recovery_worker.go b/internal/worker/recovery_worker/recovery_worker.go index 7237278..72e1933 100644 --- a/internal/worker/recovery_worker/recovery_worker.go +++ b/internal/worker/recovery_worker/recovery_worker.go @@ -114,3 +114,7 @@ func (wc *RecoveryWorker) sendRecoveryTask(ctx context.Context, task models.Reco //wc.logger.Info("Recovery email sent and restore request updated successfully", zap.String("email", task.Email)) return nil } + +func (wc *RecoveryWorker) Stop(ctx context.Context) error { + return nil +} diff --git a/pkg/closer/closer.go b/pkg/closer/closer.go new file mode 100644 index 0000000..fdfbaf1 --- /dev/null +++ b/pkg/closer/closer.go @@ -0,0 +1,37 @@ +package closer + +import ( + "context" +) + +type Closer interface { + Close(ctx context.Context) error +} + +type CloserFunc func(ctx context.Context) error + +func (cf CloserFunc) Close(ctx context.Context) error { + return cf(ctx) +} + +type CloserGroup struct { + closers []Closer +} + +func NewCloserGroup() *CloserGroup { + return &CloserGroup{} +} + +func (cg *CloserGroup) Add(c Closer) { + cg.closers = append(cg.closers, c) +} + +func (cg *CloserGroup) Call(ctx context.Context) error { + var closeErr error + for i := len(cg.closers) - 1; i >= 0; i-- { + if err := cg.closers[i].Close(ctx); err != nil && closeErr == nil { + closeErr = err + } + } + return closeErr +} From 13147de3d2f27c792871a005da89358e1966b550 Mon Sep 17 00:00:00 2001 From: Pavel Date: Wed, 17 Jan 2024 11:28:33 +0300 Subject: [PATCH 19/66] replace http sending id --- internal/controller/recovery/recovery_controller.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/controller/recovery/recovery_controller.go b/internal/controller/recovery/recovery_controller.go index 98769f2..c5d8fab 100644 --- a/internal/controller/recovery/recovery_controller.go +++ b/internal/controller/recovery/recovery_controller.go @@ -83,9 +83,8 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } - return c.Status(fiber.StatusOK).JSON(fiber.Map{ - "id": id, - }) + return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Recovery email sent successfully"}) + } func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error { From 19af554a16deabdaaa9a34052b49dbf8827bbe44 Mon Sep 17 00:00:00 2001 From: Pavel Date: Wed, 17 Jan 2024 12:37:41 +0300 Subject: [PATCH 20/66] change logic for sendig recovery to mail --- .../recovery/recovery_controller.go | 7 +++ internal/repository/codeword_repository.go | 17 +++++-- internal/repository/errors.go | 10 ++++ internal/repository/promocode_repository.go | 6 --- internal/repository/user_repository.go | 3 -- .../worker/recovery_worker/recovery_worker.go | 51 +++++++++++-------- 6 files changed, 59 insertions(+), 35 deletions(-) create mode 100644 internal/repository/errors.go diff --git a/internal/controller/recovery/recovery_controller.go b/internal/controller/recovery/recovery_controller.go index c5d8fab..b55b2b7 100644 --- a/internal/controller/recovery/recovery_controller.go +++ b/internal/controller/recovery/recovery_controller.go @@ -2,8 +2,10 @@ package recovery import ( "codeword/internal/models" + "codeword/internal/repository" "codeword/internal/services" "encoding/base64" + "errors" "fmt" "github.com/gofiber/fiber/v2" "go.uber.org/zap" @@ -80,6 +82,11 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { err = r.service.RecoveryEmailTask(c.Context(), models.RecEmailDeps{UserID: user.ID.Hex(), Email: req.Email, SignWithID: signWithID, ID: id}) if err != nil { r.logger.Error("Failed to send recovery email", zap.Error(err)) + + if errors.Is(err, repository.ErrAlreadyReported) { + return c.Status(fiber.StatusAlreadyReported).JSON(fiber.Map{"error": "already reported"}) + } + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } diff --git a/internal/repository/codeword_repository.go b/internal/repository/codeword_repository.go index fc8ba1b..9d3bb40 100644 --- a/internal/repository/codeword_repository.go +++ b/internal/repository/codeword_repository.go @@ -46,6 +46,17 @@ func (r *CodewordRepository) StoreRecoveryRecord(ctx context.Context, deps model // добавляем в очередь данные для отправки на почту в редис func (r *CodewordRepository) InsertToQueue(ctx context.Context, deps models.RecEmailDeps) error { + sendLockKey := "email:sendLock:" + deps.Email + ttl := 5 * time.Minute + + lockSuccess, err := r.rdb.SetNX(ctx, sendLockKey, "1", ttl).Result() + if err != nil { + return err + } + if !lockSuccess { + return ErrAlreadyReported + } + task := models.RecoveryRecord{ ID: deps.ID, UserID: deps.UserID, @@ -58,11 +69,7 @@ func (r *CodewordRepository) InsertToQueue(ctx context.Context, deps models.RecE return err } - if err := r.rdb.LPush(ctx, "recoveryQueue", taskBytes).Err(); err != nil { - return err - } - - return nil + return r.rdb.Set(ctx, "email:task:"+deps.Email, taskBytes, ttl).Err() } // получаем данные юзера по подписи diff --git a/internal/repository/errors.go b/internal/repository/errors.go new file mode 100644 index 0000000..d61a2da --- /dev/null +++ b/internal/repository/errors.go @@ -0,0 +1,10 @@ +package repository + +import "errors" + +var ( + ErrPromoUserNotFound = errors.New("user not found") + ErrAlreadyReported = errors.New("already reported") + ErrDuplicateCodeword = errors.New("duplicate codeword") + ErrPromoCodeNotFound = errors.New("promo code not found") +) diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index 60c7f6c..5f2d9f6 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -3,7 +3,6 @@ package repository import ( "codeword/internal/models" "context" - "errors" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" @@ -11,11 +10,6 @@ import ( "time" ) -var ( - ErrDuplicateCodeword = errors.New("duplicate codeword") - ErrPromoCodeNotFound = errors.New("promo code not found") -) - // структура для горутины чтобы ошибки не пропускать type countResult struct { count int64 diff --git a/internal/repository/user_repository.go b/internal/repository/user_repository.go index 8c67287..efdc1b3 100644 --- a/internal/repository/user_repository.go +++ b/internal/repository/user_repository.go @@ -3,7 +3,6 @@ package repository import ( "codeword/internal/models" "context" - "errors" "github.com/go-redis/redis/v8" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" @@ -18,8 +17,6 @@ type UserRepository struct { mdb *mongo.Collection } -var ErrPromoUserNotFound = errors.New("user not found") - func NewUserRepository(deps Deps) *UserRepository { return &UserRepository{mdb: deps.Mdb} diff --git a/internal/worker/recovery_worker/recovery_worker.go b/internal/worker/recovery_worker/recovery_worker.go index 72e1933..df79493 100644 --- a/internal/worker/recovery_worker/recovery_worker.go +++ b/internal/worker/recovery_worker/recovery_worker.go @@ -37,14 +37,13 @@ func NewRecoveryWC(deps Deps) *RecoveryWorker { } func (wc *RecoveryWorker) Start(ctx context.Context) { - ticker := time.NewTicker(1 * time.Second) + ticker := time.NewTicker(10 * time.Second) defer ticker.Stop() for { select { case <-ticker.C: wc.processTasks(ctx) - case <-ctx.Done(): return } @@ -52,29 +51,39 @@ func (wc *RecoveryWorker) Start(ctx context.Context) { } func (wc *RecoveryWorker) processTasks(ctx context.Context) { - result, err := wc.redis.BRPop(ctx, 1*time.Second, "recoveryQueue").Result() - if err != nil { - if err != redis.Nil { - wc.logger.Error("Failed to BRPop from the recovery queue", zap.Error(err)) + var cursor uint64 + for { + var keys []string + var err error + keys, cursor, err = wc.redis.Scan(ctx, cursor, "email:task:*", 0).Result() + if err != nil { + wc.logger.Error("Failed to scan for email tasks", zap.Error(err)) + break } - return - } - if len(result) < 2 { - wc.logger.Error("Received unexpected number of elements from BRPop", zap.Strings("result", result)) - return - } + for _, key := range keys { + taskBytes, err := wc.redis.GetDel(ctx, key).Result() + if err == redis.Nil { + continue + } else if err != nil { + wc.logger.Error("Failed to getdel recovery task", zap.String("key", key), zap.Error(err)) + continue + } - var task models.RecoveryRecord - if err = json.Unmarshal([]byte(result[1]), &task); err != nil { - wc.logger.Error("Failed to unmarshal recovery task", zap.String("key", result[0]), zap.Error(err)) - return - } + var task models.RecoveryRecord + if json.Unmarshal([]byte(taskBytes), &task) != nil { + wc.logger.Error("Failed to unmarshal recovery task", zap.String("key", key), zap.String("task", taskBytes)) + continue + } - err = wc.sendRecoveryTask(ctx, task) - if err != nil { - wc.logger.Error("Failed to send recovery task", zap.String("key", result[0]), zap.Error(err)) - return + err = wc.sendRecoveryTask(ctx, task) + if err != nil { + wc.logger.Error("Failed to send recovery task", zap.String("key", key), zap.Error(err)) + } + } + if cursor == 0 { + break + } } } From e35e189c2f6d9f4ff40832fabb91723606a70c6c Mon Sep 17 00:00:00 2001 From: Pavel Date: Wed, 17 Jan 2024 16:51:42 +0300 Subject: [PATCH 21/66] add kafka producer for fake tariff --- .env | 6 +- go.mod | 20 +- go.sum | 49 +- internal/app/app.go | 17 + .../promocode/promocode_controller.go | 4 + internal/initialize/config.go | 2 + internal/initialize/kafka.go | 27 + internal/kafka/tariff/producer.go | 56 + internal/models/bonus.go | 1 + internal/models/tariff.go | 46 + internal/proto/broker/models.pb.go | 314 +++++ internal/proto/discount/audit.model.pb.go | 192 +++ internal/proto/discount/discount.model.pb.go | 1132 +++++++++++++++++ internal/proto/discount/service.pb.go | 524 ++++++++ internal/proto/discount/service_grpc.pb.go | 404 ++++++ internal/repository/promocode_repository.go | 12 +- internal/services/promocode_service.go | 31 +- internal/utils/transfer/privilege.go | 31 + internal/utils/transfer/tariff.go | 17 + 19 files changed, 2857 insertions(+), 28 deletions(-) create mode 100644 internal/initialize/kafka.go create mode 100644 internal/kafka/tariff/producer.go create mode 100644 internal/models/tariff.go create mode 100644 internal/proto/broker/models.pb.go create mode 100644 internal/proto/discount/audit.model.pb.go create mode 100644 internal/proto/discount/discount.model.pb.go create mode 100644 internal/proto/discount/service.pb.go create mode 100644 internal/proto/discount/service_grpc.pb.go create mode 100644 internal/utils/transfer/privilege.go create mode 100644 internal/utils/transfer/tariff.go diff --git a/.env b/.env index ec4d988..cd26e2f 100644 --- a/.env +++ b/.env @@ -36,4 +36,8 @@ SMTP_SENDER="noreply@mailing.pena.digital" # URL settings DEFAULT_REDIRECTION_URL = "def.url" -AUTH_EXCHANGE_URL = "http://localhost:8000/auth/exchange" \ No newline at end of file +AUTH_EXCHANGE_URL = "http://localhost:8000/auth/exchange" + +# Kafka settings +KAFKA_BROKERS="localhost:9092" +KAFKA_TOPIC_TARIFF="tariffs" \ No newline at end of file diff --git a/go.mod b/go.mod index 4aa1db1..042e172 100644 --- a/go.mod +++ b/go.mod @@ -10,15 +10,20 @@ require ( github.com/pioz/faker v1.7.3 github.com/rs/xid v1.5.0 github.com/stretchr/testify v1.8.1 + github.com/twmb/franz-go v1.15.4 go.mongodb.org/mongo-driver v1.13.1 go.uber.org/zap v1.26.0 + google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac + google.golang.org/grpc v1.60.1 + google.golang.org/protobuf v1.32.0 ) require ( github.com/andybalholm/brotli v1.0.5 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.1 // indirect github.com/google/uuid v1.4.0 // indirect github.com/klauspost/compress v1.16.7 // indirect @@ -26,8 +31,10 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect + github.com/pierrec/lz4/v4 v4.1.19 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/twmb/franz-go/pkg/kmsg v1.7.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.50.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect @@ -36,9 +43,12 @@ require ( github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect go.uber.org/multierr v1.10.0 // indirect - golang.org/x/crypto v0.7.0 // indirect - golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect - golang.org/x/sys v0.14.0 // indirect - golang.org/x/text v0.8.0 // indirect + golang.org/x/crypto v0.17.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sync v0.4.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 8e1373d..731f00a 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/ github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0= github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -15,10 +15,15 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/gofiber/fiber/v2 v2.51.0 h1:JNACcZy5e2tGApWB2QrRpenTWn0fq0hkFm6k0C86gKQ= github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= @@ -41,6 +46,8 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/pierrec/lz4/v4 v4.1.19 h1:tYLzDnjDXh9qIxSTKHwXwOYmm9d887Y7Y1ZkyXYHAN4= +github.com/pierrec/lz4/v4 v4.1.19/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pioz/faker v1.7.3 h1:Tez8Emuq0UN+/d6mo3a9m/9ZZ/zdfJk0c5RtRatrceM= github.com/pioz/faker v1.7.3/go.mod h1:xSpay5w/oz1a6+ww0M3vfpe40pSIykeUPeWEc3TvVlc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -57,6 +64,10 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/twmb/franz-go v1.15.4 h1:qBCkHaiutetnrXjAUWA99D9FEcZVMt2AYwkH3vWEQTw= +github.com/twmb/franz-go v1.15.4/go.mod h1:rC18hqNmfo8TMc1kz7CQmHL74PLNF8KVvhflxiiJZCU= +github.com/twmb/franz-go/pkg/kmsg v1.7.0 h1:a457IbvezYfA5UkiBvyV3zj0Is3y1i8EJgqjJYoij2E= +github.com/twmb/franz-go/pkg/kmsg v1.7.0/go.mod h1:se9Mjdt0Nwzc9lnjJ0HyDtLyBnaBDAd7pCje47OhSyw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M= @@ -83,18 +94,19 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -103,8 +115,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -113,14 +125,25 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 h1:nz5NESFLZbJGPFxDT/HCn+V1mZ8JGNoY4nUpmW/Y2eg= +google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0= +google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac h1:OZkkudMUu9LVQMCoRUbI/1p5VCo9BOrlvkqMvWtqa6s= +google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= +google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= diff --git a/internal/app/app.go b/internal/app/app.go index bc59aca..a5bb8db 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -12,6 +12,7 @@ import ( "codeword/pkg/closer" "context" "errors" + "github.com/twmb/franz-go/pkg/kgo" "go.uber.org/zap" "time" ) @@ -41,6 +42,21 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { return err } + kafkaTariffClient, err := kgo.NewClient( + kgo.SeedBrokers(cfg.KafkaBrokers), + kgo.ConsumeResetOffset(kgo.NewOffset().AtStart()), + kgo.DefaultProduceTopic(cfg.KafkaTopic), + ) + if err != nil { + return err + } + + brokers := initialize.NewBrokers(initialize.BrokersDeps{ + Logger: logger, + TariffClient: kafkaTariffClient, + Topic: cfg.KafkaTopic, + }) + rdb, err := initialize.Redis(ctx, cfg) encrypt := initialize.Encrypt(cfg) @@ -62,6 +78,7 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { promoService := services.NewPromoCodeService(services.PromoDeps{ Logger: logger, PromoCodeRepo: promoCodeRepo, + Kafka: brokers.TariffProducer, }) recoveryController := recovery.NewRecoveryController(logger, recoveryService, cfg.DefaultRedirectionURL) diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index e15d9ff..e1eb1b1 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -90,6 +90,10 @@ func (p *PromoCodeController) Activate(c *fiber.Ctx) error { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) } + if req.UserID == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "userid is required"}) + } + if req.Codeword == "" && req.FastLink == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "codeword or fastlink is required"}) } diff --git a/internal/initialize/config.go b/internal/initialize/config.go index d2205b0..002156c 100644 --- a/internal/initialize/config.go +++ b/internal/initialize/config.go @@ -31,6 +31,8 @@ type Config struct { SmtpSender string `env:"SMTP_SENDER"` DefaultRedirectionURL string `env:"DEFAULT_REDIRECTION_URL"` AuthURL string `env:"AUTH_EXCHANGE_URL"` + KafkaBrokers string `env:"KAFKA_BROKERS"` + KafkaTopic string `env:"KAFKA_TOPIC_TARIFF"` } func LoadConfig() (*Config, error) { diff --git a/internal/initialize/kafka.go b/internal/initialize/kafka.go new file mode 100644 index 0000000..8b4019d --- /dev/null +++ b/internal/initialize/kafka.go @@ -0,0 +1,27 @@ +package initialize + +import ( + "codeword/internal/kafka/tariff" + "github.com/twmb/franz-go/pkg/kgo" + "go.uber.org/zap" +) + +type BrokersDeps struct { + Logger *zap.Logger + TariffClient *kgo.Client + Topic string +} + +type Brokers struct { + TariffProducer *tariff.Producer +} + +func NewBrokers(deps BrokersDeps) *Brokers { + return &Brokers{ + TariffProducer: tariff.NewProducer(tariff.ProducerDeps{ + Logger: deps.Logger, + Client: deps.TariffClient, + Topic: deps.Topic, + }), + } +} diff --git a/internal/kafka/tariff/producer.go b/internal/kafka/tariff/producer.go new file mode 100644 index 0000000..69a5e92 --- /dev/null +++ b/internal/kafka/tariff/producer.go @@ -0,0 +1,56 @@ +package tariff + +import ( + "codeword/internal/models" + "codeword/internal/utils/transfer" + "context" + "github.com/twmb/franz-go/pkg/kgo" + "go.uber.org/zap" + "google.golang.org/protobuf/proto" + "log" +) + +type ProducerDeps struct { + Logger *zap.Logger + Client *kgo.Client + Topic string +} + +type Producer struct { + logger *zap.Logger + client *kgo.Client + topic string +} + +func NewProducer(deps ProducerDeps) *Producer { + if deps.Logger == nil { + log.Panicln("logger is nil on ") + } + + if deps.Client == nil { + log.Panicln("Kafka client is nil on ") + } + + return &Producer{ + logger: deps.Logger, + client: deps.Client, + topic: deps.Topic, + } +} + +func (p *Producer) Send(ctx context.Context, userID string, tariff *models.Tariff) error { + bytes, err := proto.Marshal(transfer.TariffModelToProtoMessage(userID, tariff)) + if err != nil { + p.logger.Error("failed to marshal tariff model", zap.Error(err)) + return err + } + + // упростил, возможно зря, но теперь возвращаем одну ошибку, просто прерываем цикл при первой встретившейся ошибке + err = p.client.ProduceSync(ctx, &kgo.Record{Topic: p.topic, Value: bytes}).FirstErr() + if err != nil { + p.logger.Error("failed to send tariff to Kafka", zap.Error(err)) + return err + } + + return nil +} diff --git a/internal/models/bonus.go b/internal/models/bonus.go index f6edeb4..cdba331 100644 --- a/internal/models/bonus.go +++ b/internal/models/bonus.go @@ -59,6 +59,7 @@ type GetPromoCodesListResp struct { } type ActivateReq struct { + UserID string `json:"userID"` // для кого активировать нужно для кафки Codeword string `json:"codeword"` FastLink string `json:"fastLink"` } diff --git a/internal/models/tariff.go b/internal/models/tariff.go new file mode 100644 index 0000000..00da357 --- /dev/null +++ b/internal/models/tariff.go @@ -0,0 +1,46 @@ +package models + +import ( + "codeword/internal/proto/broker" + "time" +) + +type Tariff struct { + ID string `json:"_id"` + Name string `json:"name"` + Price uint64 `json:"price,omitempty"` + IsCustom bool `json:"isCustom"` + Privileges []Privilege `json:"privileges"` + Deleted bool `json:"isDeleted"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt *time.Time `json:"deletedAt,omitempty"` +} + +type Privilege struct { + ID string `json:"_id"` + Name string `json:"name"` + PrivilegeID string `json:"privilegeId"` + ServiceKey string `json:"serviceKey"` + Description string `json:"description"` + Amount uint64 `json:"amount"` + Type PrivilegeType `json:"type"` + Value string `json:"value"` + Price uint64 `json:"price"` +} + +type PrivilegeType string + +const ( + PrivilegeTypeCount = "count" + PrivilegeTypeDay = "day" + PrivilegeTypeFull = "full" +) + +var ( + PrivilegeBrokerTypeMap = map[PrivilegeType]broker.PrivilegeType{ + PrivilegeTypeFull: broker.PrivilegeType_Full, + PrivilegeTypeDay: broker.PrivilegeType_Day, + PrivilegeTypeCount: broker.PrivilegeType_Count, + } +) diff --git a/internal/proto/broker/models.pb.go b/internal/proto/broker/models.pb.go new file mode 100644 index 0000000..cb81617 --- /dev/null +++ b/internal/proto/broker/models.pb.go @@ -0,0 +1,314 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc (unknown) +// source: kafka/models.proto + +package broker + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type PrivilegeType int32 + +const ( + PrivilegeType_Full PrivilegeType = 0 + PrivilegeType_Day PrivilegeType = 1 + PrivilegeType_Count PrivilegeType = 2 +) + +// Enum value maps for PrivilegeType. +var ( + PrivilegeType_name = map[int32]string{ + 0: "Full", + 1: "Day", + 2: "Count", + } + PrivilegeType_value = map[string]int32{ + "Full": 0, + "Day": 1, + "Count": 2, + } +) + +func (x PrivilegeType) Enum() *PrivilegeType { + p := new(PrivilegeType) + *p = x + return p +} + +func (x PrivilegeType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (PrivilegeType) Descriptor() protoreflect.EnumDescriptor { + return file_broker_models_proto_enumTypes[0].Descriptor() +} + +func (PrivilegeType) Type() protoreflect.EnumType { + return &file_broker_models_proto_enumTypes[0] +} + +func (x PrivilegeType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use PrivilegeType.Descriptor instead. +func (PrivilegeType) EnumDescriptor() ([]byte, []int) { + return file_broker_models_proto_rawDescGZIP(), []int{0} +} + +type PrivilegeMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PrivilegeID string `protobuf:"bytes,1,opt,name=PrivilegeID,proto3" json:"PrivilegeID,omitempty"` + ServiceKey string `protobuf:"bytes,2,opt,name=ServiceKey,proto3" json:"ServiceKey,omitempty"` + Type PrivilegeType `protobuf:"varint,3,opt,name=Type,proto3,enum=kafka.PrivilegeType" json:"Type,omitempty"` + Value string `protobuf:"bytes,4,opt,name=Value,proto3" json:"Value,omitempty"` + Amount uint64 `protobuf:"varint,5,opt,name=Amount,proto3" json:"Amount,omitempty"` +} + +func (x *PrivilegeMessage) Reset() { + *x = PrivilegeMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_broker_models_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PrivilegeMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PrivilegeMessage) ProtoMessage() {} + +func (x *PrivilegeMessage) ProtoReflect() protoreflect.Message { + mi := &file_broker_models_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PrivilegeMessage.ProtoReflect.Descriptor instead. +func (*PrivilegeMessage) Descriptor() ([]byte, []int) { + return file_broker_models_proto_rawDescGZIP(), []int{0} +} + +func (x *PrivilegeMessage) GetPrivilegeID() string { + if x != nil { + return x.PrivilegeID + } + return "" +} + +func (x *PrivilegeMessage) GetServiceKey() string { + if x != nil { + return x.ServiceKey + } + return "" +} + +func (x *PrivilegeMessage) GetType() PrivilegeType { + if x != nil { + return x.Type + } + return PrivilegeType_Full +} + +func (x *PrivilegeMessage) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +func (x *PrivilegeMessage) GetAmount() uint64 { + if x != nil { + return x.Amount + } + return 0 +} + +type TariffMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Privileges []*PrivilegeMessage `protobuf:"bytes,1,rep,name=Privileges,proto3" json:"Privileges,omitempty"` + UserID string `protobuf:"bytes,2,opt,name=UserID,proto3" json:"UserID,omitempty"` +} + +func (x *TariffMessage) Reset() { + *x = TariffMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_broker_models_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TariffMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TariffMessage) ProtoMessage() {} + +func (x *TariffMessage) ProtoReflect() protoreflect.Message { + mi := &file_broker_models_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TariffMessage.ProtoReflect.Descriptor instead. +func (*TariffMessage) Descriptor() ([]byte, []int) { + return file_broker_models_proto_rawDescGZIP(), []int{1} +} + +func (x *TariffMessage) GetPrivileges() []*PrivilegeMessage { + if x != nil { + return x.Privileges + } + return nil +} + +func (x *TariffMessage) GetUserID() string { + if x != nil { + return x.UserID + } + return "" +} + +var File_broker_models_proto protoreflect.FileDescriptor + +var file_broker_models_proto_rawDesc = []byte{ + 0x0a, 0x13, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x22, 0xad, 0x01, + 0x0a, 0x10, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x49, + 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, + 0x67, 0x65, 0x49, 0x44, 0x12, 0x1e, 0x0a, 0x0a, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4b, + 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x4b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x76, + 0x69, 0x6c, 0x65, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x61, 0x0a, + 0x0d, 0x54, 0x61, 0x72, 0x69, 0x66, 0x66, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x38, + 0x0a, 0x0a, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69, 0x76, + 0x69, 0x6c, 0x65, 0x67, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0a, 0x50, 0x72, + 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x55, 0x73, 0x65, 0x72, + 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, + 0x2a, 0x2d, 0x0a, 0x0d, 0x50, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x75, 0x6c, 0x6c, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x44, + 0x61, 0x79, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x10, 0x02, 0x42, + 0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x62, 0x72, 0x6f, 0x6b, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_broker_models_proto_rawDescOnce sync.Once + file_broker_models_proto_rawDescData = file_broker_models_proto_rawDesc +) + +func file_broker_models_proto_rawDescGZIP() []byte { + file_broker_models_proto_rawDescOnce.Do(func() { + file_broker_models_proto_rawDescData = protoimpl.X.CompressGZIP(file_broker_models_proto_rawDescData) + }) + return file_broker_models_proto_rawDescData +} + +var file_broker_models_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_broker_models_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_broker_models_proto_goTypes = []interface{}{ + (PrivilegeType)(0), // 0: kafka.PrivilegeType + (*PrivilegeMessage)(nil), // 1: kafka.PrivilegeMessage + (*TariffMessage)(nil), // 2: kafka.TariffMessage +} +var file_broker_models_proto_depIdxs = []int32{ + 0, // 0: kafka.PrivilegeMessage.Type:type_name -> kafka.PrivilegeType + 1, // 1: kafka.TariffMessage.Privileges:type_name -> kafka.PrivilegeMessage + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_broker_models_proto_init() } +func file_broker_models_proto_init() { + if File_broker_models_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_broker_models_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PrivilegeMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_broker_models_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TariffMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_broker_models_proto_rawDesc, + NumEnums: 1, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_broker_models_proto_goTypes, + DependencyIndexes: file_broker_models_proto_depIdxs, + EnumInfos: file_broker_models_proto_enumTypes, + MessageInfos: file_broker_models_proto_msgTypes, + }.Build() + File_broker_models_proto = out.File + file_broker_models_proto_rawDesc = nil + file_broker_models_proto_goTypes = nil + file_broker_models_proto_depIdxs = nil +} diff --git a/internal/proto/discount/audit.model.pb.go b/internal/proto/discount/audit.model.pb.go new file mode 100644 index 0000000..7c2347e --- /dev/null +++ b/internal/proto/discount/audit.model.pb.go @@ -0,0 +1,192 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc (unknown) +// source: discount/audit.model.proto + +package discount + +import ( + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + _ "google.golang.org/protobuf/types/known/emptypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Audit struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=UpdatedAt,proto3" json:"UpdatedAt,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"` + DeletedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=DeletedAt,proto3,oneof" json:"DeletedAt,omitempty"` + Deleted bool `protobuf:"varint,4,opt,name=Deleted,proto3" json:"Deleted,omitempty"` +} + +func (x *Audit) Reset() { + *x = Audit{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_audit_model_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Audit) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Audit) ProtoMessage() {} + +func (x *Audit) ProtoReflect() protoreflect.Message { + mi := &file_discount_audit_model_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Audit.ProtoReflect.Descriptor instead. +func (*Audit) Descriptor() ([]byte, []int) { + return file_discount_audit_model_proto_rawDescGZIP(), []int{0} +} + +func (x *Audit) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + +func (x *Audit) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *Audit) GetDeletedAt() *timestamppb.Timestamp { + if x != nil { + return x.DeletedAt + } + return nil +} + +func (x *Audit) GetDeleted() bool { + if x != nil { + return x.Deleted + } + return false +} + +var File_discount_audit_model_proto protoreflect.FileDescriptor + +var file_discount_audit_model_proto_rawDesc = []byte{ + 0x0a, 0x1a, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x61, 0x75, 0x64, 0x69, 0x74, + 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x64, 0x69, + 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x22, 0xe2, 0x01, 0x0a, 0x05, 0x41, 0x75, 0x64, 0x69, 0x74, 0x12, 0x38, 0x0a, 0x09, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x12, 0x3d, 0x0a, 0x09, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, + 0x00, 0x52, 0x09, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x88, 0x01, 0x01, 0x12, + 0x18, 0x0a, 0x07, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x07, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x42, 0x0c, 0x5a, 0x0a, 0x2e, 0x2f, 0x64, 0x69, 0x73, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_discount_audit_model_proto_rawDescOnce sync.Once + file_discount_audit_model_proto_rawDescData = file_discount_audit_model_proto_rawDesc +) + +func file_discount_audit_model_proto_rawDescGZIP() []byte { + file_discount_audit_model_proto_rawDescOnce.Do(func() { + file_discount_audit_model_proto_rawDescData = protoimpl.X.CompressGZIP(file_discount_audit_model_proto_rawDescData) + }) + return file_discount_audit_model_proto_rawDescData +} + +var file_discount_audit_model_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_discount_audit_model_proto_goTypes = []interface{}{ + (*Audit)(nil), // 0: discount.Audit + (*timestamppb.Timestamp)(nil), // 1: google.protobuf.Timestamp +} +var file_discount_audit_model_proto_depIdxs = []int32{ + 1, // 0: discount.Audit.UpdatedAt:type_name -> google.protobuf.Timestamp + 1, // 1: discount.Audit.CreatedAt:type_name -> google.protobuf.Timestamp + 1, // 2: discount.Audit.DeletedAt:type_name -> google.protobuf.Timestamp + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_discount_audit_model_proto_init() } +func file_discount_audit_model_proto_init() { + if File_discount_audit_model_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_discount_audit_model_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Audit); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_discount_audit_model_proto_msgTypes[0].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_discount_audit_model_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_discount_audit_model_proto_goTypes, + DependencyIndexes: file_discount_audit_model_proto_depIdxs, + MessageInfos: file_discount_audit_model_proto_msgTypes, + }.Build() + File_discount_audit_model_proto = out.File + file_discount_audit_model_proto_rawDesc = nil + file_discount_audit_model_proto_goTypes = nil + file_discount_audit_model_proto_depIdxs = nil +} diff --git a/internal/proto/discount/discount.model.pb.go b/internal/proto/discount/discount.model.pb.go new file mode 100644 index 0000000..c0fce2b --- /dev/null +++ b/internal/proto/discount/discount.model.pb.go @@ -0,0 +1,1132 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc (unknown) +// source: discount/discount.model.proto + +package discount + +import ( + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + _ "google.golang.org/protobuf/types/known/emptypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type TargetScope int32 + +const ( + TargetScope_Sum TargetScope = 0 + TargetScope_Group TargetScope = 1 + TargetScope_Each TargetScope = 2 +) + +// Enum value maps for TargetScope. +var ( + TargetScope_name = map[int32]string{ + 0: "Sum", + 1: "Group", + 2: "Each", + } + TargetScope_value = map[string]int32{ + "Sum": 0, + "Group": 1, + "Each": 2, + } +) + +func (x TargetScope) Enum() *TargetScope { + p := new(TargetScope) + *p = x + return p +} + +func (x TargetScope) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (TargetScope) Descriptor() protoreflect.EnumDescriptor { + return file_discount_discount_model_proto_enumTypes[0].Descriptor() +} + +func (TargetScope) Type() protoreflect.EnumType { + return &file_discount_discount_model_proto_enumTypes[0] +} + +func (x TargetScope) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use TargetScope.Descriptor instead. +func (TargetScope) EnumDescriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{0} +} + +type DiscountOptional struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` + Name *string `protobuf:"bytes,2,opt,name=Name,proto3,oneof" json:"Name,omitempty"` + Layer *uint32 `protobuf:"varint,3,opt,name=Layer,proto3,oneof" json:"Layer,omitempty"` + Description *string `protobuf:"bytes,4,opt,name=Description,proto3,oneof" json:"Description,omitempty"` + Condition *DiscountCondition `protobuf:"bytes,5,opt,name=Condition,proto3,oneof" json:"Condition,omitempty"` + Target *DiscountCalculationTarget `protobuf:"bytes,6,opt,name=Target,proto3,oneof" json:"Target,omitempty"` + Deprecated *bool `protobuf:"varint,7,opt,name=Deprecated,proto3,oneof" json:"Deprecated,omitempty"` +} + +func (x *DiscountOptional) Reset() { + *x = DiscountOptional{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_discount_model_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DiscountOptional) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DiscountOptional) ProtoMessage() {} + +func (x *DiscountOptional) ProtoReflect() protoreflect.Message { + mi := &file_discount_discount_model_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DiscountOptional.ProtoReflect.Descriptor instead. +func (*DiscountOptional) Descriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{0} +} + +func (x *DiscountOptional) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *DiscountOptional) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +func (x *DiscountOptional) GetLayer() uint32 { + if x != nil && x.Layer != nil { + return *x.Layer + } + return 0 +} + +func (x *DiscountOptional) GetDescription() string { + if x != nil && x.Description != nil { + return *x.Description + } + return "" +} + +func (x *DiscountOptional) GetCondition() *DiscountCondition { + if x != nil { + return x.Condition + } + return nil +} + +func (x *DiscountOptional) GetTarget() *DiscountCalculationTarget { + if x != nil { + return x.Target + } + return nil +} + +func (x *DiscountOptional) GetDeprecated() bool { + if x != nil && x.Deprecated != nil { + return *x.Deprecated + } + return false +} + +type Discounts struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Discounts []*Discount `protobuf:"bytes,1,rep,name=Discounts,proto3" json:"Discounts,omitempty"` +} + +func (x *Discounts) Reset() { + *x = Discounts{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_discount_model_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Discounts) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Discounts) ProtoMessage() {} + +func (x *Discounts) ProtoReflect() protoreflect.Message { + mi := &file_discount_discount_model_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Discounts.ProtoReflect.Descriptor instead. +func (*Discounts) Descriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{1} +} + +func (x *Discounts) GetDiscounts() []*Discount { + if x != nil { + return x.Discounts + } + return nil +} + +type Discount struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` + Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` + Layer uint32 `protobuf:"varint,3,opt,name=Layer,proto3" json:"Layer,omitempty"` + Description string `protobuf:"bytes,4,opt,name=Description,proto3" json:"Description,omitempty"` + Condition *DiscountCondition `protobuf:"bytes,5,opt,name=Condition,proto3" json:"Condition,omitempty"` + Target *DiscountCalculationTarget `protobuf:"bytes,6,opt,name=Target,proto3" json:"Target,omitempty"` + Audit *Audit `protobuf:"bytes,7,opt,name=Audit,proto3" json:"Audit,omitempty"` + Deprecated bool `protobuf:"varint,8,opt,name=Deprecated,proto3" json:"Deprecated,omitempty"` +} + +func (x *Discount) Reset() { + *x = Discount{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_discount_model_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Discount) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Discount) ProtoMessage() {} + +func (x *Discount) ProtoReflect() protoreflect.Message { + mi := &file_discount_discount_model_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Discount.ProtoReflect.Descriptor instead. +func (*Discount) Descriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{2} +} + +func (x *Discount) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *Discount) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Discount) GetLayer() uint32 { + if x != nil { + return x.Layer + } + return 0 +} + +func (x *Discount) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Discount) GetCondition() *DiscountCondition { + if x != nil { + return x.Condition + } + return nil +} + +func (x *Discount) GetTarget() *DiscountCalculationTarget { + if x != nil { + return x.Target + } + return nil +} + +func (x *Discount) GetAudit() *Audit { + if x != nil { + return x.Audit + } + return nil +} + +func (x *Discount) GetDeprecated() bool { + if x != nil { + return x.Deprecated + } + return false +} + +type DiscountCalculationTarget struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Products []*ProductTarget `protobuf:"bytes,1,rep,name=Products,proto3" json:"Products,omitempty"` + Factor float64 `protobuf:"fixed64,2,opt,name=Factor,proto3" json:"Factor,omitempty"` + TargetScope *TargetScope `protobuf:"varint,3,opt,name=TargetScope,proto3,enum=discount.TargetScope,oneof" json:"TargetScope,omitempty"` + TargetGroup *string `protobuf:"bytes,4,opt,name=TargetGroup,proto3,oneof" json:"TargetGroup,omitempty"` + Overhelm *bool `protobuf:"varint,5,opt,name=Overhelm,proto3,oneof" json:"Overhelm,omitempty"` +} + +func (x *DiscountCalculationTarget) Reset() { + *x = DiscountCalculationTarget{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_discount_model_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DiscountCalculationTarget) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DiscountCalculationTarget) ProtoMessage() {} + +func (x *DiscountCalculationTarget) ProtoReflect() protoreflect.Message { + mi := &file_discount_discount_model_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DiscountCalculationTarget.ProtoReflect.Descriptor instead. +func (*DiscountCalculationTarget) Descriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{3} +} + +func (x *DiscountCalculationTarget) GetProducts() []*ProductTarget { + if x != nil { + return x.Products + } + return nil +} + +func (x *DiscountCalculationTarget) GetFactor() float64 { + if x != nil { + return x.Factor + } + return 0 +} + +func (x *DiscountCalculationTarget) GetTargetScope() TargetScope { + if x != nil && x.TargetScope != nil { + return *x.TargetScope + } + return TargetScope_Sum +} + +func (x *DiscountCalculationTarget) GetTargetGroup() string { + if x != nil && x.TargetGroup != nil { + return *x.TargetGroup + } + return "" +} + +func (x *DiscountCalculationTarget) GetOverhelm() bool { + if x != nil && x.Overhelm != nil { + return *x.Overhelm + } + return false +} + +type DiscountCondition struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Period *PeriodCondition `protobuf:"bytes,1,opt,name=Period,proto3,oneof" json:"Period,omitempty"` + User *string `protobuf:"bytes,2,opt,name=User,proto3,oneof" json:"User,omitempty"` + UserType *string `protobuf:"bytes,3,opt,name=UserType,proto3,oneof" json:"UserType,omitempty"` + Coupon *string `protobuf:"bytes,4,opt,name=Coupon,proto3,oneof" json:"Coupon,omitempty"` + PurchasesAmount *uint64 `protobuf:"varint,5,opt,name=PurchasesAmount,proto3,oneof" json:"PurchasesAmount,omitempty"` + CartPurchasesAmount *uint64 `protobuf:"varint,6,opt,name=CartPurchasesAmount,proto3,oneof" json:"CartPurchasesAmount,omitempty"` + Product *string `protobuf:"bytes,7,opt,name=Product,proto3,oneof" json:"Product,omitempty"` + Term *uint64 `protobuf:"varint,8,opt,name=Term,proto3,oneof" json:"Term,omitempty"` + Usage *uint64 `protobuf:"varint,9,opt,name=Usage,proto3,oneof" json:"Usage,omitempty"` + PriceFrom *uint64 `protobuf:"varint,10,opt,name=PriceFrom,proto3,oneof" json:"PriceFrom,omitempty"` + Group *string `protobuf:"bytes,11,opt,name=Group,proto3,oneof" json:"Group,omitempty"` +} + +func (x *DiscountCondition) Reset() { + *x = DiscountCondition{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_discount_model_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DiscountCondition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DiscountCondition) ProtoMessage() {} + +func (x *DiscountCondition) ProtoReflect() protoreflect.Message { + mi := &file_discount_discount_model_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DiscountCondition.ProtoReflect.Descriptor instead. +func (*DiscountCondition) Descriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{4} +} + +func (x *DiscountCondition) GetPeriod() *PeriodCondition { + if x != nil { + return x.Period + } + return nil +} + +func (x *DiscountCondition) GetUser() string { + if x != nil && x.User != nil { + return *x.User + } + return "" +} + +func (x *DiscountCondition) GetUserType() string { + if x != nil && x.UserType != nil { + return *x.UserType + } + return "" +} + +func (x *DiscountCondition) GetCoupon() string { + if x != nil && x.Coupon != nil { + return *x.Coupon + } + return "" +} + +func (x *DiscountCondition) GetPurchasesAmount() uint64 { + if x != nil && x.PurchasesAmount != nil { + return *x.PurchasesAmount + } + return 0 +} + +func (x *DiscountCondition) GetCartPurchasesAmount() uint64 { + if x != nil && x.CartPurchasesAmount != nil { + return *x.CartPurchasesAmount + } + return 0 +} + +func (x *DiscountCondition) GetProduct() string { + if x != nil && x.Product != nil { + return *x.Product + } + return "" +} + +func (x *DiscountCondition) GetTerm() uint64 { + if x != nil && x.Term != nil { + return *x.Term + } + return 0 +} + +func (x *DiscountCondition) GetUsage() uint64 { + if x != nil && x.Usage != nil { + return *x.Usage + } + return 0 +} + +func (x *DiscountCondition) GetPriceFrom() uint64 { + if x != nil && x.PriceFrom != nil { + return *x.PriceFrom + } + return 0 +} + +func (x *DiscountCondition) GetGroup() string { + if x != nil && x.Group != nil { + return *x.Group + } + return "" +} + +type ProductTarget struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` + Factor float64 `protobuf:"fixed64,2,opt,name=Factor,proto3" json:"Factor,omitempty"` + Overhelm *bool `protobuf:"varint,3,opt,name=Overhelm,proto3,oneof" json:"Overhelm,omitempty"` +} + +func (x *ProductTarget) Reset() { + *x = ProductTarget{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_discount_model_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProductTarget) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProductTarget) ProtoMessage() {} + +func (x *ProductTarget) ProtoReflect() protoreflect.Message { + mi := &file_discount_discount_model_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProductTarget.ProtoReflect.Descriptor instead. +func (*ProductTarget) Descriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{5} +} + +func (x *ProductTarget) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *ProductTarget) GetFactor() float64 { + if x != nil { + return x.Factor + } + return 0 +} + +func (x *ProductTarget) GetOverhelm() bool { + if x != nil && x.Overhelm != nil { + return *x.Overhelm + } + return false +} + +type PeriodCondition struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + From *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=From,proto3" json:"From,omitempty"` + To *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=To,proto3" json:"To,omitempty"` +} + +func (x *PeriodCondition) Reset() { + *x = PeriodCondition{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_discount_model_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PeriodCondition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PeriodCondition) ProtoMessage() {} + +func (x *PeriodCondition) ProtoReflect() protoreflect.Message { + mi := &file_discount_discount_model_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PeriodCondition.ProtoReflect.Descriptor instead. +func (*PeriodCondition) Descriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{6} +} + +func (x *PeriodCondition) GetFrom() *timestamppb.Timestamp { + if x != nil { + return x.From + } + return nil +} + +func (x *PeriodCondition) GetTo() *timestamppb.Timestamp { + if x != nil { + return x.To + } + return nil +} + +type UserInformation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` + Type string `protobuf:"bytes,2,opt,name=Type,proto3" json:"Type,omitempty"` + PurchasesAmount uint64 `protobuf:"varint,3,opt,name=PurchasesAmount,proto3" json:"PurchasesAmount,omitempty"` + CartPurchasesAmount uint64 `protobuf:"varint,4,opt,name=CartPurchasesAmount,proto3" json:"CartPurchasesAmount,omitempty"` +} + +func (x *UserInformation) Reset() { + *x = UserInformation{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_discount_model_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UserInformation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UserInformation) ProtoMessage() {} + +func (x *UserInformation) ProtoReflect() protoreflect.Message { + mi := &file_discount_discount_model_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UserInformation.ProtoReflect.Descriptor instead. +func (*UserInformation) Descriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{7} +} + +func (x *UserInformation) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *UserInformation) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *UserInformation) GetPurchasesAmount() uint64 { + if x != nil { + return x.PurchasesAmount + } + return 0 +} + +func (x *UserInformation) GetCartPurchasesAmount() uint64 { + if x != nil { + return x.CartPurchasesAmount + } + return 0 +} + +type ProductInformation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` + Price uint64 `protobuf:"varint,2,opt,name=Price,proto3" json:"Price,omitempty"` + Term *uint64 `protobuf:"varint,3,opt,name=Term,proto3,oneof" json:"Term,omitempty"` + Usage *uint64 `protobuf:"varint,4,opt,name=Usage,proto3,oneof" json:"Usage,omitempty"` + Group *string `protobuf:"bytes,5,opt,name=Group,proto3,oneof" json:"Group,omitempty"` +} + +func (x *ProductInformation) Reset() { + *x = ProductInformation{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_discount_model_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProductInformation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProductInformation) ProtoMessage() {} + +func (x *ProductInformation) ProtoReflect() protoreflect.Message { + mi := &file_discount_discount_model_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProductInformation.ProtoReflect.Descriptor instead. +func (*ProductInformation) Descriptor() ([]byte, []int) { + return file_discount_discount_model_proto_rawDescGZIP(), []int{8} +} + +func (x *ProductInformation) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +func (x *ProductInformation) GetPrice() uint64 { + if x != nil { + return x.Price + } + return 0 +} + +func (x *ProductInformation) GetTerm() uint64 { + if x != nil && x.Term != nil { + return *x.Term + } + return 0 +} + +func (x *ProductInformation) GetUsage() uint64 { + if x != nil && x.Usage != nil { + return *x.Usage + } + return 0 +} + +func (x *ProductInformation) GetGroup() string { + if x != nil && x.Group != nil { + return *x.Group + } + return "" +} + +var File_discount_discount_model_proto protoreflect.FileDescriptor + +var file_discount_discount_model_proto_rawDesc = []byte{ + 0x0a, 0x1d, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x08, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1a, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, + 0x61, 0x75, 0x64, 0x69, 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0xef, 0x02, 0x0a, 0x10, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x17, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, + 0x19, 0x0a, 0x05, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x01, + 0x52, 0x05, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0b, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x02, 0x52, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, + 0x01, 0x12, 0x3e, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, + 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x48, 0x03, 0x52, 0x09, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, + 0x01, 0x12, 0x40, 0x0a, 0x06, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x23, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x04, 0x52, 0x06, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x88, 0x01, 0x01, 0x12, 0x23, 0x0a, 0x0a, 0x44, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, + 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x48, 0x05, 0x52, 0x0a, 0x44, 0x65, 0x70, 0x72, 0x65, + 0x63, 0x61, 0x74, 0x65, 0x64, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x4e, 0x61, 0x6d, + 0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, + 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, + 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x54, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x44, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, + 0x74, 0x65, 0x64, 0x22, 0x3d, 0x0a, 0x09, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, + 0x12, 0x30, 0x0a, 0x09, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, + 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x09, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x73, 0x22, 0xa5, 0x02, 0x0a, 0x08, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, + 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x05, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x09, 0x43, + 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, + 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x43, 0x6f, 0x6e, + 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x06, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x61, 0x6c, 0x63, 0x75, 0x6c, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x06, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x12, 0x25, 0x0a, 0x05, 0x41, 0x75, 0x64, 0x69, 0x74, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x75, + 0x64, 0x69, 0x74, 0x52, 0x05, 0x41, 0x75, 0x64, 0x69, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x44, 0x65, + 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, + 0x44, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x22, 0x9b, 0x02, 0x0a, 0x19, 0x44, + 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x61, 0x6c, 0x63, 0x75, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x33, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x64, + 0x75, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x69, 0x73, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, 0x16, 0x0a, + 0x06, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x46, + 0x61, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x3c, 0x0a, 0x0b, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, + 0x63, 0x6f, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x64, 0x69, 0x73, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x63, 0x6f, 0x70, + 0x65, 0x48, 0x00, 0x52, 0x0b, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, + 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0b, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x0b, 0x54, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, 0x08, 0x4f, 0x76, + 0x65, 0x72, 0x68, 0x65, 0x6c, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x48, 0x02, 0x52, 0x08, + 0x4f, 0x76, 0x65, 0x72, 0x68, 0x65, 0x6c, 0x6d, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x42, 0x0b, 0x0a, 0x09, 0x5f, + 0x4f, 0x76, 0x65, 0x72, 0x68, 0x65, 0x6c, 0x6d, 0x22, 0xa8, 0x04, 0x0a, 0x11, 0x44, 0x69, 0x73, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x36, + 0x0a, 0x06, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, + 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x06, 0x50, 0x65, 0x72, + 0x69, 0x6f, 0x64, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x04, 0x55, 0x73, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, + 0x1f, 0x0a, 0x08, 0x55, 0x73, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x48, 0x02, 0x52, 0x08, 0x55, 0x73, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x88, 0x01, 0x01, + 0x12, 0x1b, 0x0a, 0x06, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x03, 0x52, 0x06, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, + 0x0f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x48, 0x04, 0x52, 0x0f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, + 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x12, 0x35, 0x0a, 0x13, + 0x43, 0x61, 0x72, 0x74, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x48, 0x05, 0x52, 0x13, 0x43, 0x61, 0x72, + 0x74, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x06, 0x52, 0x07, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x88, + 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x54, 0x65, 0x72, 0x6d, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, + 0x48, 0x07, 0x52, 0x04, 0x54, 0x65, 0x72, 0x6d, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x55, + 0x73, 0x61, 0x67, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x48, 0x08, 0x52, 0x05, 0x55, 0x73, + 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x09, 0x50, 0x72, 0x69, 0x63, 0x65, 0x46, + 0x72, 0x6f, 0x6d, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x48, 0x09, 0x52, 0x09, 0x50, 0x72, 0x69, + 0x63, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x48, 0x0a, 0x52, 0x05, 0x47, 0x72, 0x6f, 0x75, + 0x70, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x42, + 0x07, 0x0a, 0x05, 0x5f, 0x55, 0x73, 0x65, 0x72, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x55, 0x73, 0x65, + 0x72, 0x54, 0x79, 0x70, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, + 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x16, 0x0a, 0x14, 0x5f, 0x43, 0x61, 0x72, 0x74, 0x50, 0x75, 0x72, + 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x0a, 0x0a, 0x08, + 0x5f, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x54, 0x65, 0x72, + 0x6d, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x55, 0x73, 0x61, 0x67, 0x65, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, + 0x50, 0x72, 0x69, 0x63, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x47, 0x72, + 0x6f, 0x75, 0x70, 0x22, 0x65, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x54, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x01, 0x52, 0x06, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x08, + 0x4f, 0x76, 0x65, 0x72, 0x68, 0x65, 0x6c, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, + 0x52, 0x08, 0x4f, 0x76, 0x65, 0x72, 0x68, 0x65, 0x6c, 0x6d, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, + 0x09, 0x5f, 0x4f, 0x76, 0x65, 0x72, 0x68, 0x65, 0x6c, 0x6d, 0x22, 0x6d, 0x0a, 0x0f, 0x50, 0x65, + 0x72, 0x69, 0x6f, 0x64, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, + 0x04, 0x46, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x46, 0x72, 0x6f, 0x6d, 0x12, 0x2a, 0x0a, + 0x02, 0x54, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x02, 0x54, 0x6f, 0x22, 0x91, 0x01, 0x0a, 0x0f, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, + 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x12, 0x0a, + 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x50, 0x75, 0x72, 0x63, + 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x13, 0x43, + 0x61, 0x72, 0x74, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x13, 0x43, 0x61, 0x72, 0x74, 0x50, 0x75, + 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x73, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xa6, 0x01, + 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x17, 0x0a, 0x04, 0x54, 0x65, + 0x72, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x04, 0x54, 0x65, 0x72, 0x6d, + 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x04, 0x48, 0x01, 0x52, 0x05, 0x55, 0x73, 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 0x12, 0x19, + 0x0a, 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, + 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x54, 0x65, + 0x72, 0x6d, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x55, 0x73, 0x61, 0x67, 0x65, 0x42, 0x08, 0x0a, 0x06, + 0x5f, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x2a, 0x2b, 0x0a, 0x0b, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x53, 0x75, 0x6d, 0x10, 0x00, 0x12, 0x09, + 0x0a, 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x45, 0x61, 0x63, + 0x68, 0x10, 0x02, 0x42, 0x0c, 0x5a, 0x0a, 0x2e, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_discount_discount_model_proto_rawDescOnce sync.Once + file_discount_discount_model_proto_rawDescData = file_discount_discount_model_proto_rawDesc +) + +func file_discount_discount_model_proto_rawDescGZIP() []byte { + file_discount_discount_model_proto_rawDescOnce.Do(func() { + file_discount_discount_model_proto_rawDescData = protoimpl.X.CompressGZIP(file_discount_discount_model_proto_rawDescData) + }) + return file_discount_discount_model_proto_rawDescData +} + +var file_discount_discount_model_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_discount_discount_model_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_discount_discount_model_proto_goTypes = []interface{}{ + (TargetScope)(0), // 0: discount.TargetScope + (*DiscountOptional)(nil), // 1: discount.DiscountOptional + (*Discounts)(nil), // 2: discount.Discounts + (*Discount)(nil), // 3: discount.Discount + (*DiscountCalculationTarget)(nil), // 4: discount.DiscountCalculationTarget + (*DiscountCondition)(nil), // 5: discount.DiscountCondition + (*ProductTarget)(nil), // 6: discount.ProductTarget + (*PeriodCondition)(nil), // 7: discount.PeriodCondition + (*UserInformation)(nil), // 8: discount.UserInformation + (*ProductInformation)(nil), // 9: discount.ProductInformation + (*Audit)(nil), // 10: discount.Audit + (*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp +} +var file_discount_discount_model_proto_depIdxs = []int32{ + 5, // 0: discount.DiscountOptional.Condition:type_name -> discount.DiscountCondition + 4, // 1: discount.DiscountOptional.Target:type_name -> discount.DiscountCalculationTarget + 3, // 2: discount.Discounts.Discounts:type_name -> discount.Discount + 5, // 3: discount.Discount.Condition:type_name -> discount.DiscountCondition + 4, // 4: discount.Discount.Target:type_name -> discount.DiscountCalculationTarget + 10, // 5: discount.Discount.Audit:type_name -> discount.Audit + 6, // 6: discount.DiscountCalculationTarget.Products:type_name -> discount.ProductTarget + 0, // 7: discount.DiscountCalculationTarget.TargetScope:type_name -> discount.TargetScope + 7, // 8: discount.DiscountCondition.Period:type_name -> discount.PeriodCondition + 11, // 9: discount.PeriodCondition.From:type_name -> google.protobuf.Timestamp + 11, // 10: discount.PeriodCondition.To:type_name -> google.protobuf.Timestamp + 11, // [11:11] is the sub-list for method output_type + 11, // [11:11] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name +} + +func init() { file_discount_discount_model_proto_init() } +func file_discount_discount_model_proto_init() { + if File_discount_discount_model_proto != nil { + return + } + file_discount_audit_model_proto_init() + if !protoimpl.UnsafeEnabled { + file_discount_discount_model_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DiscountOptional); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_discount_discount_model_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Discounts); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_discount_discount_model_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Discount); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_discount_discount_model_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DiscountCalculationTarget); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_discount_discount_model_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DiscountCondition); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_discount_discount_model_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ProductTarget); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_discount_discount_model_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PeriodCondition); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_discount_discount_model_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UserInformation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_discount_discount_model_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ProductInformation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_discount_discount_model_proto_msgTypes[0].OneofWrappers = []interface{}{} + file_discount_discount_model_proto_msgTypes[3].OneofWrappers = []interface{}{} + file_discount_discount_model_proto_msgTypes[4].OneofWrappers = []interface{}{} + file_discount_discount_model_proto_msgTypes[5].OneofWrappers = []interface{}{} + file_discount_discount_model_proto_msgTypes[8].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_discount_discount_model_proto_rawDesc, + NumEnums: 1, + NumMessages: 9, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_discount_discount_model_proto_goTypes, + DependencyIndexes: file_discount_discount_model_proto_depIdxs, + EnumInfos: file_discount_discount_model_proto_enumTypes, + MessageInfos: file_discount_discount_model_proto_msgTypes, + }.Build() + File_discount_discount_model_proto = out.File + file_discount_discount_model_proto_rawDesc = nil + file_discount_discount_model_proto_goTypes = nil + file_discount_discount_model_proto_depIdxs = nil +} diff --git a/internal/proto/discount/service.pb.go b/internal/proto/discount/service.pb.go new file mode 100644 index 0000000..84ea7d9 --- /dev/null +++ b/internal/proto/discount/service.pb.go @@ -0,0 +1,524 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc (unknown) +// source: discount/service.proto + +package discount + +import ( + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type GetDiscountByIDRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` +} + +func (x *GetDiscountByIDRequest) Reset() { + *x = GetDiscountByIDRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetDiscountByIDRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDiscountByIDRequest) ProtoMessage() {} + +func (x *GetDiscountByIDRequest) ProtoReflect() protoreflect.Message { + mi := &file_discount_service_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetDiscountByIDRequest.ProtoReflect.Descriptor instead. +func (*GetDiscountByIDRequest) Descriptor() ([]byte, []int) { + return file_discount_service_proto_rawDescGZIP(), []int{0} +} + +func (x *GetDiscountByIDRequest) GetID() string { + if x != nil { + return x.ID + } + return "" +} + +type ApplyDiscountRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserInformation *UserInformation `protobuf:"bytes,1,opt,name=UserInformation,proto3" json:"UserInformation,omitempty"` + Products []*ProductInformation `protobuf:"bytes,2,rep,name=Products,proto3" json:"Products,omitempty"` + Date *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=Date,proto3" json:"Date,omitempty"` + Coupon *string `protobuf:"bytes,4,opt,name=Coupon,proto3,oneof" json:"Coupon,omitempty"` +} + +func (x *ApplyDiscountRequest) Reset() { + *x = ApplyDiscountRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ApplyDiscountRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApplyDiscountRequest) ProtoMessage() {} + +func (x *ApplyDiscountRequest) ProtoReflect() protoreflect.Message { + mi := &file_discount_service_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApplyDiscountRequest.ProtoReflect.Descriptor instead. +func (*ApplyDiscountRequest) Descriptor() ([]byte, []int) { + return file_discount_service_proto_rawDescGZIP(), []int{1} +} + +func (x *ApplyDiscountRequest) GetUserInformation() *UserInformation { + if x != nil { + return x.UserInformation + } + return nil +} + +func (x *ApplyDiscountRequest) GetProducts() []*ProductInformation { + if x != nil { + return x.Products + } + return nil +} + +func (x *ApplyDiscountRequest) GetDate() *timestamppb.Timestamp { + if x != nil { + return x.Date + } + return nil +} + +func (x *ApplyDiscountRequest) GetCoupon() string { + if x != nil && x.Coupon != nil { + return *x.Coupon + } + return "" +} + +type ApplyDiscountResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Price uint64 `protobuf:"varint,1,opt,name=Price,proto3" json:"Price,omitempty"` + AppliedDiscounts []*Discount `protobuf:"bytes,2,rep,name=AppliedDiscounts,proto3" json:"AppliedDiscounts,omitempty"` +} + +func (x *ApplyDiscountResponse) Reset() { + *x = ApplyDiscountResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ApplyDiscountResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ApplyDiscountResponse) ProtoMessage() {} + +func (x *ApplyDiscountResponse) ProtoReflect() protoreflect.Message { + mi := &file_discount_service_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ApplyDiscountResponse.ProtoReflect.Descriptor instead. +func (*ApplyDiscountResponse) Descriptor() ([]byte, []int) { + return file_discount_service_proto_rawDescGZIP(), []int{2} +} + +func (x *ApplyDiscountResponse) GetPrice() uint64 { + if x != nil { + return x.Price + } + return 0 +} + +func (x *ApplyDiscountResponse) GetAppliedDiscounts() []*Discount { + if x != nil { + return x.AppliedDiscounts + } + return nil +} + +type CreateDiscountRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"` + Layer uint32 `protobuf:"varint,2,opt,name=Layer,proto3" json:"Layer,omitempty"` + Description string `protobuf:"bytes,3,opt,name=Description,proto3" json:"Description,omitempty"` + Condition *DiscountCondition `protobuf:"bytes,4,opt,name=Condition,proto3" json:"Condition,omitempty"` + Target *DiscountCalculationTarget `protobuf:"bytes,5,opt,name=Target,proto3" json:"Target,omitempty"` +} + +func (x *CreateDiscountRequest) Reset() { + *x = CreateDiscountRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_discount_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateDiscountRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateDiscountRequest) ProtoMessage() {} + +func (x *CreateDiscountRequest) ProtoReflect() protoreflect.Message { + mi := &file_discount_service_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateDiscountRequest.ProtoReflect.Descriptor instead. +func (*CreateDiscountRequest) Descriptor() ([]byte, []int) { + return file_discount_service_proto_rawDescGZIP(), []int{3} +} + +func (x *CreateDiscountRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateDiscountRequest) GetLayer() uint32 { + if x != nil { + return x.Layer + } + return 0 +} + +func (x *CreateDiscountRequest) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *CreateDiscountRequest) GetCondition() *DiscountCondition { + if x != nil { + return x.Condition + } + return nil +} + +func (x *CreateDiscountRequest) GetTarget() *DiscountCalculationTarget { + if x != nil { + return x.Target + } + return nil +} + +var File_discount_service_proto protoreflect.FileDescriptor + +var file_discount_service_proto_rawDesc = []byte{ + 0x0a, 0x16, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, + 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1d, + 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x28, 0x0a, + 0x16, 0x47, 0x65, 0x74, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x44, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x22, 0xed, 0x01, 0x0a, 0x14, 0x41, 0x70, 0x70, 0x6c, + 0x79, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x43, 0x0a, 0x0f, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x64, 0x69, 0x73, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x12, + 0x2e, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x44, 0x61, 0x74, 0x65, 0x12, + 0x1b, 0x0a, 0x06, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x00, 0x52, 0x06, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, + 0x5f, 0x43, 0x6f, 0x75, 0x70, 0x6f, 0x6e, 0x22, 0x6d, 0x0a, 0x15, 0x41, 0x70, 0x70, 0x6c, 0x79, + 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x05, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x3e, 0x0a, 0x10, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, + 0x64, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x10, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, 0x44, 0x69, 0x73, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0xdb, 0x01, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x05, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x09, + 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1b, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x43, 0x6f, + 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x06, 0x54, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x61, 0x6c, 0x63, 0x75, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x06, 0x54, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x32, 0x80, 0x07, 0x0a, 0x0f, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x52, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x41, + 0x6c, 0x6c, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x1a, 0x13, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, + 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x12, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, + 0x12, 0x0a, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x66, 0x0a, 0x10, + 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, + 0x12, 0x20, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x44, + 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, + 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, + 0x13, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, + 0x7b, 0x49, 0x44, 0x7d, 0x12, 0x69, 0x0a, 0x12, 0x44, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, + 0x65, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x64, 0x69, 0x73, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x44, 0x69, 0x73, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x64, 0x69, 0x73, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, + 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x3a, 0x01, 0x2a, 0x22, 0x13, 0x2f, 0x64, 0x69, 0x73, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x64, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x65, 0x12, + 0x6d, 0x0a, 0x0e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x73, 0x12, 0x1e, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x70, 0x70, + 0x6c, 0x79, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1f, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x41, 0x70, 0x70, + 0x6c, 0x79, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01, 0x2a, 0x22, 0x0f, 0x2f, + 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x5f, + 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x79, 0x49, + 0x44, 0x12, 0x20, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x47, 0x65, 0x74, + 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, + 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x12, + 0x0e, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x7b, 0x49, 0x44, 0x7d, 0x12, + 0x5b, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x1f, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, + 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x14, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0e, 0x3a, 0x01, + 0x2a, 0x22, 0x09, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x5c, 0x0a, 0x0f, + 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, + 0x1a, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x1a, 0x12, 0x2e, 0x64, 0x69, + 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, + 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x3a, 0x01, 0x2a, 0x1a, 0x0e, 0x2f, 0x64, 0x69, 0x73, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2f, 0x7b, 0x49, 0x44, 0x7d, 0x12, 0x5b, 0x0a, 0x0e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x2e, 0x64, + 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x1a, 0x12, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x19, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x13, 0x3a, 0x01, 0x2a, 0x32, 0x0e, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x2f, 0x7b, 0x49, 0x44, 0x7d, 0x12, 0x5e, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x20, 0x2e, 0x64, 0x69, 0x73, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x69, + 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, + 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x2a, 0x0e, 0x2f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x2f, 0x7b, 0x49, 0x44, 0x7d, 0x42, 0x0c, 0x5a, 0x0a, 0x2e, 0x2f, 0x64, 0x69, 0x73, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_discount_service_proto_rawDescOnce sync.Once + file_discount_service_proto_rawDescData = file_discount_service_proto_rawDesc +) + +func file_discount_service_proto_rawDescGZIP() []byte { + file_discount_service_proto_rawDescOnce.Do(func() { + file_discount_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_discount_service_proto_rawDescData) + }) + return file_discount_service_proto_rawDescData +} + +var file_discount_service_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_discount_service_proto_goTypes = []interface{}{ + (*GetDiscountByIDRequest)(nil), // 0: discount.GetDiscountByIDRequest + (*ApplyDiscountRequest)(nil), // 1: discount.ApplyDiscountRequest + (*ApplyDiscountResponse)(nil), // 2: discount.ApplyDiscountResponse + (*CreateDiscountRequest)(nil), // 3: discount.CreateDiscountRequest + (*UserInformation)(nil), // 4: discount.UserInformation + (*ProductInformation)(nil), // 5: discount.ProductInformation + (*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp + (*Discount)(nil), // 7: discount.Discount + (*DiscountCondition)(nil), // 8: discount.DiscountCondition + (*DiscountCalculationTarget)(nil), // 9: discount.DiscountCalculationTarget + (*emptypb.Empty)(nil), // 10: google.protobuf.Empty + (*DiscountOptional)(nil), // 11: discount.DiscountOptional + (*Discounts)(nil), // 12: discount.Discounts +} +var file_discount_service_proto_depIdxs = []int32{ + 4, // 0: discount.ApplyDiscountRequest.UserInformation:type_name -> discount.UserInformation + 5, // 1: discount.ApplyDiscountRequest.Products:type_name -> discount.ProductInformation + 6, // 2: discount.ApplyDiscountRequest.Date:type_name -> google.protobuf.Timestamp + 7, // 3: discount.ApplyDiscountResponse.AppliedDiscounts:type_name -> discount.Discount + 8, // 4: discount.CreateDiscountRequest.Condition:type_name -> discount.DiscountCondition + 9, // 5: discount.CreateDiscountRequest.Target:type_name -> discount.DiscountCalculationTarget + 10, // 6: discount.DiscountService.GetAllDiscounts:input_type -> google.protobuf.Empty + 0, // 7: discount.DiscountService.GetUserDiscounts:input_type -> discount.GetDiscountByIDRequest + 1, // 8: discount.DiscountService.DetermineDiscounts:input_type -> discount.ApplyDiscountRequest + 1, // 9: discount.DiscountService.ApplyDiscounts:input_type -> discount.ApplyDiscountRequest + 0, // 10: discount.DiscountService.GetDiscountByID:input_type -> discount.GetDiscountByIDRequest + 3, // 11: discount.DiscountService.CreateDiscount:input_type -> discount.CreateDiscountRequest + 11, // 12: discount.DiscountService.ReplaceDiscount:input_type -> discount.DiscountOptional + 11, // 13: discount.DiscountService.UpdateDiscount:input_type -> discount.DiscountOptional + 0, // 14: discount.DiscountService.DeleteDiscount:input_type -> discount.GetDiscountByIDRequest + 12, // 15: discount.DiscountService.GetAllDiscounts:output_type -> discount.Discounts + 12, // 16: discount.DiscountService.GetUserDiscounts:output_type -> discount.Discounts + 12, // 17: discount.DiscountService.DetermineDiscounts:output_type -> discount.Discounts + 2, // 18: discount.DiscountService.ApplyDiscounts:output_type -> discount.ApplyDiscountResponse + 7, // 19: discount.DiscountService.GetDiscountByID:output_type -> discount.Discount + 7, // 20: discount.DiscountService.CreateDiscount:output_type -> discount.Discount + 7, // 21: discount.DiscountService.ReplaceDiscount:output_type -> discount.Discount + 7, // 22: discount.DiscountService.UpdateDiscount:output_type -> discount.Discount + 7, // 23: discount.DiscountService.DeleteDiscount:output_type -> discount.Discount + 15, // [15:24] is the sub-list for method output_type + 6, // [6:15] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_discount_service_proto_init() } +func file_discount_service_proto_init() { + if File_discount_service_proto != nil { + return + } + file_discount_discount_model_proto_init() + if !protoimpl.UnsafeEnabled { + file_discount_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetDiscountByIDRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_discount_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ApplyDiscountRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_discount_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ApplyDiscountResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_discount_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateDiscountRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_discount_service_proto_msgTypes[1].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_discount_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_discount_service_proto_goTypes, + DependencyIndexes: file_discount_service_proto_depIdxs, + MessageInfos: file_discount_service_proto_msgTypes, + }.Build() + File_discount_service_proto = out.File + file_discount_service_proto_rawDesc = nil + file_discount_service_proto_goTypes = nil + file_discount_service_proto_depIdxs = nil +} diff --git a/internal/proto/discount/service_grpc.pb.go b/internal/proto/discount/service_grpc.pb.go new file mode 100644 index 0000000..e9161fb --- /dev/null +++ b/internal/proto/discount/service_grpc.pb.go @@ -0,0 +1,404 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc (unknown) +// source: discount/service.proto + +package discount + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + DiscountService_GetAllDiscounts_FullMethodName = "/discount.DiscountService/GetAllDiscounts" + DiscountService_GetUserDiscounts_FullMethodName = "/discount.DiscountService/GetUserDiscounts" + DiscountService_DetermineDiscounts_FullMethodName = "/discount.DiscountService/DetermineDiscounts" + DiscountService_ApplyDiscounts_FullMethodName = "/discount.DiscountService/ApplyDiscounts" + DiscountService_GetDiscountByID_FullMethodName = "/discount.DiscountService/GetDiscountByID" + DiscountService_CreateDiscount_FullMethodName = "/discount.DiscountService/CreateDiscount" + DiscountService_ReplaceDiscount_FullMethodName = "/discount.DiscountService/ReplaceDiscount" + DiscountService_UpdateDiscount_FullMethodName = "/discount.DiscountService/UpdateDiscount" + DiscountService_DeleteDiscount_FullMethodName = "/discount.DiscountService/DeleteDiscount" +) + +// DiscountServiceClient is the client API for DiscountService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type DiscountServiceClient interface { + GetAllDiscounts(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Discounts, error) + GetUserDiscounts(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discounts, error) + DetermineDiscounts(ctx context.Context, in *ApplyDiscountRequest, opts ...grpc.CallOption) (*Discounts, error) + ApplyDiscounts(ctx context.Context, in *ApplyDiscountRequest, opts ...grpc.CallOption) (*ApplyDiscountResponse, error) + GetDiscountByID(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discount, error) + CreateDiscount(ctx context.Context, in *CreateDiscountRequest, opts ...grpc.CallOption) (*Discount, error) + ReplaceDiscount(ctx context.Context, in *DiscountOptional, opts ...grpc.CallOption) (*Discount, error) + UpdateDiscount(ctx context.Context, in *DiscountOptional, opts ...grpc.CallOption) (*Discount, error) + DeleteDiscount(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discount, error) +} + +type discountServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewDiscountServiceClient(cc grpc.ClientConnInterface) DiscountServiceClient { + return &discountServiceClient{cc} +} + +func (c *discountServiceClient) GetAllDiscounts(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Discounts, error) { + out := new(Discounts) + err := c.cc.Invoke(ctx, DiscountService_GetAllDiscounts_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *discountServiceClient) GetUserDiscounts(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discounts, error) { + out := new(Discounts) + err := c.cc.Invoke(ctx, DiscountService_GetUserDiscounts_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *discountServiceClient) DetermineDiscounts(ctx context.Context, in *ApplyDiscountRequest, opts ...grpc.CallOption) (*Discounts, error) { + out := new(Discounts) + err := c.cc.Invoke(ctx, DiscountService_DetermineDiscounts_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *discountServiceClient) ApplyDiscounts(ctx context.Context, in *ApplyDiscountRequest, opts ...grpc.CallOption) (*ApplyDiscountResponse, error) { + out := new(ApplyDiscountResponse) + err := c.cc.Invoke(ctx, DiscountService_ApplyDiscounts_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *discountServiceClient) GetDiscountByID(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discount, error) { + out := new(Discount) + err := c.cc.Invoke(ctx, DiscountService_GetDiscountByID_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *discountServiceClient) CreateDiscount(ctx context.Context, in *CreateDiscountRequest, opts ...grpc.CallOption) (*Discount, error) { + out := new(Discount) + err := c.cc.Invoke(ctx, DiscountService_CreateDiscount_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *discountServiceClient) ReplaceDiscount(ctx context.Context, in *DiscountOptional, opts ...grpc.CallOption) (*Discount, error) { + out := new(Discount) + err := c.cc.Invoke(ctx, DiscountService_ReplaceDiscount_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *discountServiceClient) UpdateDiscount(ctx context.Context, in *DiscountOptional, opts ...grpc.CallOption) (*Discount, error) { + out := new(Discount) + err := c.cc.Invoke(ctx, DiscountService_UpdateDiscount_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *discountServiceClient) DeleteDiscount(ctx context.Context, in *GetDiscountByIDRequest, opts ...grpc.CallOption) (*Discount, error) { + out := new(Discount) + err := c.cc.Invoke(ctx, DiscountService_DeleteDiscount_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// DiscountServiceServer is the server API for DiscountService service. +// All implementations should embed UnimplementedDiscountServiceServer +// for forward compatibility +type DiscountServiceServer interface { + GetAllDiscounts(context.Context, *emptypb.Empty) (*Discounts, error) + GetUserDiscounts(context.Context, *GetDiscountByIDRequest) (*Discounts, error) + DetermineDiscounts(context.Context, *ApplyDiscountRequest) (*Discounts, error) + ApplyDiscounts(context.Context, *ApplyDiscountRequest) (*ApplyDiscountResponse, error) + GetDiscountByID(context.Context, *GetDiscountByIDRequest) (*Discount, error) + CreateDiscount(context.Context, *CreateDiscountRequest) (*Discount, error) + ReplaceDiscount(context.Context, *DiscountOptional) (*Discount, error) + UpdateDiscount(context.Context, *DiscountOptional) (*Discount, error) + DeleteDiscount(context.Context, *GetDiscountByIDRequest) (*Discount, error) +} + +// UnimplementedDiscountServiceServer should be embedded to have forward compatible implementations. +type UnimplementedDiscountServiceServer struct { +} + +func (UnimplementedDiscountServiceServer) GetAllDiscounts(context.Context, *emptypb.Empty) (*Discounts, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetAllDiscounts not implemented") +} +func (UnimplementedDiscountServiceServer) GetUserDiscounts(context.Context, *GetDiscountByIDRequest) (*Discounts, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetUserDiscounts not implemented") +} +func (UnimplementedDiscountServiceServer) DetermineDiscounts(context.Context, *ApplyDiscountRequest) (*Discounts, error) { + return nil, status.Errorf(codes.Unimplemented, "method DetermineDiscounts not implemented") +} +func (UnimplementedDiscountServiceServer) ApplyDiscounts(context.Context, *ApplyDiscountRequest) (*ApplyDiscountResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ApplyDiscounts not implemented") +} +func (UnimplementedDiscountServiceServer) GetDiscountByID(context.Context, *GetDiscountByIDRequest) (*Discount, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetDiscountByID not implemented") +} +func (UnimplementedDiscountServiceServer) CreateDiscount(context.Context, *CreateDiscountRequest) (*Discount, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateDiscount not implemented") +} +func (UnimplementedDiscountServiceServer) ReplaceDiscount(context.Context, *DiscountOptional) (*Discount, error) { + return nil, status.Errorf(codes.Unimplemented, "method ReplaceDiscount not implemented") +} +func (UnimplementedDiscountServiceServer) UpdateDiscount(context.Context, *DiscountOptional) (*Discount, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateDiscount not implemented") +} +func (UnimplementedDiscountServiceServer) DeleteDiscount(context.Context, *GetDiscountByIDRequest) (*Discount, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteDiscount not implemented") +} + +// UnsafeDiscountServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to DiscountServiceServer will +// result in compilation errors. +type UnsafeDiscountServiceServer interface { + mustEmbedUnimplementedDiscountServiceServer() +} + +func RegisterDiscountServiceServer(s grpc.ServiceRegistrar, srv DiscountServiceServer) { + s.RegisterService(&DiscountService_ServiceDesc, srv) +} + +func _DiscountService_GetAllDiscounts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiscountServiceServer).GetAllDiscounts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: DiscountService_GetAllDiscounts_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiscountServiceServer).GetAllDiscounts(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _DiscountService_GetUserDiscounts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetDiscountByIDRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiscountServiceServer).GetUserDiscounts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: DiscountService_GetUserDiscounts_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiscountServiceServer).GetUserDiscounts(ctx, req.(*GetDiscountByIDRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _DiscountService_DetermineDiscounts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApplyDiscountRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiscountServiceServer).DetermineDiscounts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: DiscountService_DetermineDiscounts_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiscountServiceServer).DetermineDiscounts(ctx, req.(*ApplyDiscountRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _DiscountService_ApplyDiscounts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApplyDiscountRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiscountServiceServer).ApplyDiscounts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: DiscountService_ApplyDiscounts_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiscountServiceServer).ApplyDiscounts(ctx, req.(*ApplyDiscountRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _DiscountService_GetDiscountByID_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetDiscountByIDRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiscountServiceServer).GetDiscountByID(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: DiscountService_GetDiscountByID_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiscountServiceServer).GetDiscountByID(ctx, req.(*GetDiscountByIDRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _DiscountService_CreateDiscount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateDiscountRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiscountServiceServer).CreateDiscount(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: DiscountService_CreateDiscount_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiscountServiceServer).CreateDiscount(ctx, req.(*CreateDiscountRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _DiscountService_ReplaceDiscount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DiscountOptional) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiscountServiceServer).ReplaceDiscount(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: DiscountService_ReplaceDiscount_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiscountServiceServer).ReplaceDiscount(ctx, req.(*DiscountOptional)) + } + return interceptor(ctx, in, info, handler) +} + +func _DiscountService_UpdateDiscount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DiscountOptional) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiscountServiceServer).UpdateDiscount(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: DiscountService_UpdateDiscount_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiscountServiceServer).UpdateDiscount(ctx, req.(*DiscountOptional)) + } + return interceptor(ctx, in, info, handler) +} + +func _DiscountService_DeleteDiscount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetDiscountByIDRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiscountServiceServer).DeleteDiscount(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: DiscountService_DeleteDiscount_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiscountServiceServer).DeleteDiscount(ctx, req.(*GetDiscountByIDRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// DiscountService_ServiceDesc is the grpc.ServiceDesc for DiscountService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var DiscountService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "discount.DiscountService", + HandlerType: (*DiscountServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetAllDiscounts", + Handler: _DiscountService_GetAllDiscounts_Handler, + }, + { + MethodName: "GetUserDiscounts", + Handler: _DiscountService_GetUserDiscounts_Handler, + }, + { + MethodName: "DetermineDiscounts", + Handler: _DiscountService_DetermineDiscounts_Handler, + }, + { + MethodName: "ApplyDiscounts", + Handler: _DiscountService_ApplyDiscounts_Handler, + }, + { + MethodName: "GetDiscountByID", + Handler: _DiscountService_GetDiscountByID_Handler, + }, + { + MethodName: "CreateDiscount", + Handler: _DiscountService_CreateDiscount_Handler, + }, + { + MethodName: "ReplaceDiscount", + Handler: _DiscountService_ReplaceDiscount_Handler, + }, + { + MethodName: "UpdateDiscount", + Handler: _DiscountService_UpdateDiscount_Handler, + }, + { + MethodName: "DeleteDiscount", + Handler: _DiscountService_DeleteDiscount_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "discount/service.proto", +} diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index 5f2d9f6..354006c 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -186,14 +186,14 @@ func (r *PromoCodeRepository) GetPromoCodesList(ctx context.Context, req *models return promoCodes, count, nil } -func (r *PromoCodeRepository) ActivatePromo(ctx context.Context, req *models.ActivateReq) (string, error) { +func (r *PromoCodeRepository) ActivatePromo(ctx context.Context, req *models.ActivateReq) (*models.PromoCode, error) { session, err := r.mdb.Database().Client().StartSession() if err != nil { - return "", err + return nil, err } defer session.EndSession(ctx) - var greetings string + var promoCode models.PromoCode transactionErr := mongo.WithSession(ctx, session, func(sc mongo.SessionContext) error { var filter bson.M @@ -232,16 +232,16 @@ func (r *PromoCodeRepository) ActivatePromo(ctx context.Context, req *models.Act return err } - greetings = updatedPromoCode.Greetings + promoCode = updatedPromoCode return nil }) if transactionErr != nil { - return "", transactionErr + return nil, transactionErr } - return greetings, nil + return &promoCode, nil } func (r *PromoCodeRepository) DeletePromoCode(ctx context.Context, promoCodeID string) error { diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index cbdd179..c203fbe 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -1,6 +1,7 @@ package services import ( + "codeword/internal/kafka/tariff" "codeword/internal/models" "codeword/internal/utils/genID" "context" @@ -12,7 +13,7 @@ type PromoCodeRepository interface { CreatePromoCode(ctx context.Context, req *models.PromoCode) (*models.PromoCode, error) EditPromoCode(ctx context.Context, req *models.ReqEditPromoCode) (*models.PromoCode, error) GetPromoCodesList(ctx context.Context, req *models.GetPromoCodesListReq) ([]models.PromoCode, int64, error) - ActivatePromo(ctx context.Context, req *models.ActivateReq) (string, error) + ActivatePromo(ctx context.Context, req *models.ActivateReq) (*models.PromoCode, error) DeletePromoCode(ctx context.Context, promoCodeID string) error GetPromoCodeByID(ctx context.Context, promoCodeID primitive.ObjectID) (*models.PromoCode, error) AddFastLink(ctx context.Context, promoCodeID primitive.ObjectID, xid string) error @@ -21,17 +22,20 @@ type PromoCodeRepository interface { type PromoDeps struct { Logger *zap.Logger PromoCodeRepo PromoCodeRepository + Kafka *tariff.Producer } type PromoCodeService struct { logger *zap.Logger promoCodeRepo PromoCodeRepository + kafka *tariff.Producer } func NewPromoCodeService(deps PromoDeps) *PromoCodeService { return &PromoCodeService{ logger: deps.Logger, promoCodeRepo: deps.PromoCodeRepo, + kafka: deps.Kafka, } } @@ -66,13 +70,34 @@ func (s *PromoCodeService) GetPromoCodesList(ctx context.Context, req *models.Ge } func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.ActivateReq) (string, error) { - greetings, err := s.promoCodeRepo.ActivatePromo(ctx, req) + promoCode, err := s.promoCodeRepo.ActivatePromo(ctx, req) if err != nil { s.logger.Error("Failed to activate promocode", zap.Error(err)) return "", err } - return greetings, nil + var privileges []models.Privilege + privilege := models.Privilege{ + PrivilegeID: promoCode.Bonus.Privilege.PrivilegeID, + Amount: promoCode.Bonus.Privilege.Amount, + } + privileges = append(privileges, privilege) + + // создание fake tariff + fakeTariff := &models.Tariff{ + Name: promoCode.Codeword, // используем codeword как имя тарифа + Privileges: privileges, + Deleted: promoCode.Delete, + CreatedAt: promoCode.CreatedAt, + } + if err := s.kafka.Send(ctx, req.UserID, fakeTariff); err != nil { + s.logger.Error("Failed to send fake tariff to Kafka", zap.Error(err)) + return "", err + } + + // код для вызова gRPC service для создания скидки... + + return promoCode.Greetings, nil } func (s *PromoCodeService) DeletePromoCode(ctx context.Context, promoCodeID string) error { diff --git a/internal/utils/transfer/privilege.go b/internal/utils/transfer/privilege.go new file mode 100644 index 0000000..60bb9c5 --- /dev/null +++ b/internal/utils/transfer/privilege.go @@ -0,0 +1,31 @@ +package transfer + +import ( + "codeword/internal/models" + "codeword/internal/proto/broker" +) + +func PrivilegeModelToProto(privilege *models.Privilege) *broker.PrivilegeMessage { + if privilege == nil { + return &broker.PrivilegeMessage{} + } + + return &broker.PrivilegeMessage{ + PrivilegeID: privilege.PrivilegeID, + ServiceKey: privilege.ServiceKey, + Type: models.PrivilegeBrokerTypeMap[privilege.Type], + Value: privilege.Value, + Amount: privilege.Amount, + } +} + +func PrivilegeArrayModelToProto(privileges []models.Privilege) []*broker.PrivilegeMessage { + privilegesProto := make([]*broker.PrivilegeMessage, len(privileges)) + + for index, privilege := range privileges { + privilegeCopy := privilege + privilegesProto[index] = PrivilegeModelToProto(&privilegeCopy) + } + + return privilegesProto +} diff --git a/internal/utils/transfer/tariff.go b/internal/utils/transfer/tariff.go new file mode 100644 index 0000000..592c56e --- /dev/null +++ b/internal/utils/transfer/tariff.go @@ -0,0 +1,17 @@ +package transfer + +import ( + "codeword/internal/models" + "codeword/internal/proto/broker" +) + +func TariffModelToProtoMessage(userID string, tariffModel *models.Tariff) *broker.TariffMessage { + if tariffModel == nil { + return &broker.TariffMessage{} + } + + return &broker.TariffMessage{ + UserID: userID, + Privileges: PrivilegeArrayModelToProto(tariffModel.Privileges), + } +} From 24ba370ccdb5125f5cf07c04a18bad65ae6516e4 Mon Sep 17 00:00:00 2001 From: Pavel Date: Wed, 17 Jan 2024 21:50:50 +0300 Subject: [PATCH 22/66] init rpc discount client --- .env | 1 + internal/app/app.go | 13 +++++-- internal/initialize/config.go | 53 +++++++++++++------------- internal/initialize/grpc.go | 26 +++++++++++++ internal/models/bonus.go | 2 +- internal/services/promocode_service.go | 47 ++++++++++++++++++----- 6 files changed, 102 insertions(+), 40 deletions(-) create mode 100644 internal/initialize/grpc.go diff --git a/.env b/.env index cd26e2f..4a78021 100644 --- a/.env +++ b/.env @@ -37,6 +37,7 @@ SMTP_SENDER="noreply@mailing.pena.digital" # URL settings DEFAULT_REDIRECTION_URL = "def.url" AUTH_EXCHANGE_URL = "http://localhost:8000/auth/exchange" +DISCOUNT_ADDRESS = "http://CHANGEME:1234" # Kafka settings KAFKA_BROKERS="localhost:9092" diff --git a/internal/app/app.go b/internal/app/app.go index a5bb8db..3f1b003 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -51,6 +51,12 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { return err } + discountRpcClient, err := initialize.DiscountGRPCClient(cfg.DiscountServiceAddress) + if err != nil { + logger.Error("failed to connect to discount service", zap.Error(err)) + return err + } + brokers := initialize.NewBrokers(initialize.BrokersDeps{ Logger: logger, TariffClient: kafkaTariffClient, @@ -76,9 +82,10 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { }) promoService := services.NewPromoCodeService(services.PromoDeps{ - Logger: logger, - PromoCodeRepo: promoCodeRepo, - Kafka: brokers.TariffProducer, + Logger: logger, + PromoCodeRepo: promoCodeRepo, + Kafka: brokers.TariffProducer, + DiscountClient: discountRpcClient, }) recoveryController := recovery.NewRecoveryController(logger, recoveryService, cfg.DefaultRedirectionURL) diff --git a/internal/initialize/config.go b/internal/initialize/config.go index 002156c..087bfb2 100644 --- a/internal/initialize/config.go +++ b/internal/initialize/config.go @@ -7,32 +7,33 @@ import ( ) type Config struct { - AppName string `env:"APP_NAME" envDefault:"codeword"` - HTTPHost string `env:"HTTP_HOST" envDefault:"localhost"` - HTTPPort string `env:"HTTP_PORT" envDefault:"3000"` - MongoHost string `env:"MONGO_HOST" envDefault:"127.0.0.1"` - MongoPort string `env:"MONGO_PORT" envDefault:"27020"` - MongoUser string `env:"MONGO_USER" envDefault:"test"` - MongoPassword string `env:"MONGO_PASSWORD" envDefault:"test"` - MongoDatabase string `env:"MONGO_DB" envDefault:"admin"` - MongoAuth string `env:"MONGO_AUTH" envDefault:"admin"` - PublicCurveKey string `env:"PUBLIC_CURVE_KEY"` - PrivateCurveKey string `env:"PRIVATE_CURVE_KEY"` - SignSecret string `env:"SIGN_SECRET"` - RedisAddr string `env:"REDIS_ADDR" envDefault:"localhost:6379"` - RedisPassword string `env:"REDIS_PASS" envDefault:"admin"` - RedisDB int `env:"REDIS_DB" envDefault:"2"` - SmtpApiUrl string `env:"SMTP_API_URL"` - SmtpHost string `env:"SMTP_HOST"` - SmtpPort string `env:"SMTP_PORT"` - SmtpUsername string `env:"SMTP_UNAME"` - SmtpPassword string `env:"SMTP_PASS"` - SmtpApiKey string `env:"SMTP_API_KEY"` - SmtpSender string `env:"SMTP_SENDER"` - DefaultRedirectionURL string `env:"DEFAULT_REDIRECTION_URL"` - AuthURL string `env:"AUTH_EXCHANGE_URL"` - KafkaBrokers string `env:"KAFKA_BROKERS"` - KafkaTopic string `env:"KAFKA_TOPIC_TARIFF"` + AppName string `env:"APP_NAME" envDefault:"codeword"` + HTTPHost string `env:"HTTP_HOST" envDefault:"localhost"` + HTTPPort string `env:"HTTP_PORT" envDefault:"3000"` + MongoHost string `env:"MONGO_HOST" envDefault:"127.0.0.1"` + MongoPort string `env:"MONGO_PORT" envDefault:"27020"` + MongoUser string `env:"MONGO_USER" envDefault:"test"` + MongoPassword string `env:"MONGO_PASSWORD" envDefault:"test"` + MongoDatabase string `env:"MONGO_DB" envDefault:"admin"` + MongoAuth string `env:"MONGO_AUTH" envDefault:"admin"` + PublicCurveKey string `env:"PUBLIC_CURVE_KEY"` + PrivateCurveKey string `env:"PRIVATE_CURVE_KEY"` + SignSecret string `env:"SIGN_SECRET"` + RedisAddr string `env:"REDIS_ADDR" envDefault:"localhost:6379"` + RedisPassword string `env:"REDIS_PASS" envDefault:"admin"` + RedisDB int `env:"REDIS_DB" envDefault:"2"` + SmtpApiUrl string `env:"SMTP_API_URL"` + SmtpHost string `env:"SMTP_HOST"` + SmtpPort string `env:"SMTP_PORT"` + SmtpUsername string `env:"SMTP_UNAME"` + SmtpPassword string `env:"SMTP_PASS"` + SmtpApiKey string `env:"SMTP_API_KEY"` + SmtpSender string `env:"SMTP_SENDER"` + DefaultRedirectionURL string `env:"DEFAULT_REDIRECTION_URL"` + AuthURL string `env:"AUTH_EXCHANGE_URL"` + KafkaBrokers string `env:"KAFKA_BROKERS"` + KafkaTopic string `env:"KAFKA_TOPIC_TARIFF"` + DiscountServiceAddress string `env:"DISCOUNT_ADDRESS"` } func LoadConfig() (*Config, error) { diff --git a/internal/initialize/grpc.go b/internal/initialize/grpc.go new file mode 100644 index 0000000..5559bf3 --- /dev/null +++ b/internal/initialize/grpc.go @@ -0,0 +1,26 @@ +package initialize + +import ( + "codeword/internal/proto/discount" + "context" + "google.golang.org/grpc" + "time" +) + +func DiscountGRPCClient(address string) (discount.DiscountServiceClient, error) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + options := []grpc.DialOption{ + grpc.WithInsecure(), + grpc.WithBlock(), + } + + conn, err := grpc.DialContext(ctx, address, options...) + if err != nil { + return nil, err + } + + discountClient := discount.NewDiscountServiceClient(conn) + return discountClient, nil +} diff --git a/internal/models/bonus.go b/internal/models/bonus.go index cdba331..5612739 100644 --- a/internal/models/bonus.go +++ b/internal/models/bonus.go @@ -18,7 +18,7 @@ type PromoCode struct { Amount uint64 `json:"amount" bson:"amount"` // количество } `json:"privilege" bson:"privilege"` Discount struct { - Layer int `json:"layer" bson:"layer"` // 1|2 + Layer uint32 `json:"layer" bson:"layer"` // 1|2 Factor float64 `json:"factor" bson:"factor"` // процент скидки, вернее множитель, при котором достигается этот процент скидки Target string `json:"target" bson:"target"` // PrivilegeID или ServiceKey в зависимости от слоя Threshold int64 `json:"threshold" bson:"threshold"` // граничное значение, при пересечении которого применяется эта скидка diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index c203fbe..1cd8915 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -3,6 +3,7 @@ package services import ( "codeword/internal/kafka/tariff" "codeword/internal/models" + "codeword/internal/proto/discount" "codeword/internal/utils/genID" "context" "go.mongodb.org/mongo-driver/bson/primitive" @@ -20,22 +21,25 @@ type PromoCodeRepository interface { } type PromoDeps struct { - Logger *zap.Logger - PromoCodeRepo PromoCodeRepository - Kafka *tariff.Producer + Logger *zap.Logger + PromoCodeRepo PromoCodeRepository + Kafka *tariff.Producer + DiscountClient discount.DiscountServiceClient } type PromoCodeService struct { - logger *zap.Logger - promoCodeRepo PromoCodeRepository - kafka *tariff.Producer + logger *zap.Logger + promoCodeRepo PromoCodeRepository + kafka *tariff.Producer + discountClient discount.DiscountServiceClient } func NewPromoCodeService(deps PromoDeps) *PromoCodeService { return &PromoCodeService{ - logger: deps.Logger, - promoCodeRepo: deps.PromoCodeRepo, - kafka: deps.Kafka, + logger: deps.Logger, + promoCodeRepo: deps.PromoCodeRepo, + kafka: deps.Kafka, + discountClient: deps.DiscountClient, } } @@ -69,6 +73,10 @@ func (s *PromoCodeService) GetPromoCodesList(ctx context.Context, req *models.Ge return promoCodes, count, nil } +// todo одумать еще реализацию этого дела, надо уточнить как разделяется ответственность в бонусе между привилегией и скидкой +// разделяется ли она или они всегда вместе, если разделяются то что-то из этого может быть пустым либо все заполеннное, +//соответсвенно надо сделать соответствующие проверки до записи в кафку и до отправки в дискаунт сервис + func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.ActivateReq) (string, error) { promoCode, err := s.promoCodeRepo.ActivatePromo(ctx, req) if err != nil { @@ -95,7 +103,26 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa return "", err } - // код для вызова gRPC service для создания скидки... + disOverHelm := true + discountRequest := &discount.CreateDiscountRequest{ + Name: promoCode.Codeword, + Layer: promoCode.Bonus.Discount.Layer, + Description: "", + Condition: &discount.DiscountCondition{ + Coupon: &promoCode.Codeword, + User: &req.UserID, + }, + Target: &discount.DiscountCalculationTarget{ + Factor: promoCode.Bonus.Discount.Factor, + Overhelm: &disOverHelm, + }, + } + + _, err = s.discountClient.CreateDiscount(ctx, discountRequest) + if err != nil { + s.logger.Error("Failed to create discount", zap.Error(err)) + return "", err + } return promoCode.Greetings, nil } From e7e249a2ba56a31911d6e254189675dba9da8817 Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 18 Jan 2024 15:40:15 +0300 Subject: [PATCH 23/66] add rec url to env --- .env | 1 + internal/adapters/client/mail.go | 25 +++++++++---------- .../recovery/recovery_controller.go | 1 - internal/initialize/clients.go | 21 ++++++++-------- internal/initialize/config.go | 1 + 5 files changed, 24 insertions(+), 25 deletions(-) diff --git a/.env b/.env index 4a78021..469a8ab 100644 --- a/.env +++ b/.env @@ -38,6 +38,7 @@ SMTP_SENDER="noreply@mailing.pena.digital" DEFAULT_REDIRECTION_URL = "def.url" AUTH_EXCHANGE_URL = "http://localhost:8000/auth/exchange" DISCOUNT_ADDRESS = "http://CHANGEME:1234" +RECOVERY_URL = "http://127.0.0.1:8080/recover/" # Kafka settings KAFKA_BROKERS="localhost:9092" diff --git a/internal/adapters/client/mail.go b/internal/adapters/client/mail.go index 1522cf8..849d74a 100644 --- a/internal/adapters/client/mail.go +++ b/internal/adapters/client/mail.go @@ -9,17 +9,16 @@ import ( ) type RecoveryEmailSenderDeps struct { - SmtpApiUrl string - SmtpHost string - SmtpPort string - SmtpSender string - Username string - Password string - ApiKey string - FiberClient *fiber.Client - Logger *zap.Logger - CodewordHost string - CodewordPort string + SmtpApiUrl string + SmtpHost string + SmtpPort string + SmtpSender string + Username string + Password string + ApiKey string + FiberClient *fiber.Client + Logger *zap.Logger + RecoveryUrl string } type RecoveryEmailSender struct { @@ -40,7 +39,7 @@ func (r *RecoveryEmailSender) SendRecoveryEmail(email string, signature string) fmt.Println(email, signature) - message := fmt.Sprintf("http://"+r.deps.CodewordHost+":"+r.deps.CodewordPort+"/recover/%s", signature) + message := r.deps.RecoveryUrl + signature form := new(bytes.Buffer) writer := multipart.NewWriter(form) @@ -48,7 +47,7 @@ func (r *RecoveryEmailSender) SendRecoveryEmail(email string, signature string) fields := map[string]string{ "from": r.deps.SmtpSender, - "to": "pashamullin202@gmail.com", + "to": email, "subject": "Восстановление доступа", "html": message, } diff --git a/internal/controller/recovery/recovery_controller.go b/internal/controller/recovery/recovery_controller.go index b55b2b7..f3e9afe 100644 --- a/internal/controller/recovery/recovery_controller.go +++ b/internal/controller/recovery/recovery_controller.go @@ -58,7 +58,6 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { user, err := r.service.FindUserByEmail(c.Context(), req.Email) if err != nil || user == nil { r.logger.Error("Failed to find user by email", zap.Error(err)) - return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"}) } diff --git a/internal/initialize/clients.go b/internal/initialize/clients.go index e7851b8..e32051b 100644 --- a/internal/initialize/clients.go +++ b/internal/initialize/clients.go @@ -8,17 +8,16 @@ import ( func RecoveryEmailSender(cfg Config, logger *zap.Logger) *client.RecoveryEmailSender { return client.NewRecoveryEmailSender(client.RecoveryEmailSenderDeps{ - SmtpApiUrl: cfg.SmtpApiUrl, - SmtpHost: cfg.SmtpHost, - SmtpPort: cfg.SmtpPort, - SmtpSender: cfg.SmtpSender, - Username: cfg.SmtpUsername, - Password: cfg.SmtpPassword, - ApiKey: cfg.SmtpApiKey, - FiberClient: &fiber.Client{}, - Logger: logger, - CodewordHost: cfg.HTTPHost, - CodewordPort: cfg.HTTPPort, + SmtpApiUrl: cfg.SmtpApiUrl, + SmtpHost: cfg.SmtpHost, + SmtpPort: cfg.SmtpPort, + SmtpSender: cfg.SmtpSender, + Username: cfg.SmtpUsername, + Password: cfg.SmtpPassword, + ApiKey: cfg.SmtpApiKey, + FiberClient: &fiber.Client{}, + Logger: logger, + RecoveryUrl: cfg.RecoveryUrl, }) } diff --git a/internal/initialize/config.go b/internal/initialize/config.go index 087bfb2..c0c0621 100644 --- a/internal/initialize/config.go +++ b/internal/initialize/config.go @@ -34,6 +34,7 @@ type Config struct { KafkaBrokers string `env:"KAFKA_BROKERS"` KafkaTopic string `env:"KAFKA_TOPIC_TARIFF"` DiscountServiceAddress string `env:"DISCOUNT_ADDRESS"` + RecoveryUrl string `env:"RECOVERY_URL"` } func LoadConfig() (*Config, error) { From 5b083eaee9d9d231449c1add4a3debe9b5955319 Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 18 Jan 2024 20:49:02 +0300 Subject: [PATCH 24/66] add ping to redis --- internal/repository/codeword_repository.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/repository/codeword_repository.go b/internal/repository/codeword_repository.go index 9d3bb40..d2a9fec 100644 --- a/internal/repository/codeword_repository.go +++ b/internal/repository/codeword_repository.go @@ -93,5 +93,8 @@ func (r *CodewordRepository) Ping(ctx context.Context) error { if err := r.mdb.Database().Client().Ping(ctx, readpref.Primary()); err != nil { return err } + if err := r.rdb.Ping(ctx).Err(); err != nil { + return err + } return nil } From 68906556b898a6a66ede9a3b9811a8c2b70c2c3e Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 19 Jan 2024 15:10:54 +0300 Subject: [PATCH 25/66] add some test for codeword repo --- .../recovery/recovery_controller.go | 2 +- internal/repository/user_repository.go | 2 +- tests/repository_test/repository_test.go | 58 +++++++++++++++++-- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/internal/controller/recovery/recovery_controller.go b/internal/controller/recovery/recovery_controller.go index f3e9afe..adfb670 100644 --- a/internal/controller/recovery/recovery_controller.go +++ b/internal/controller/recovery/recovery_controller.go @@ -67,7 +67,7 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } - signUrl := req.RedirectionURL + base64.URLEncoding.EncodeToString(key) + signUrl := req.RedirectionURL sign := base64.URLEncoding.EncodeToString(key) id, err := r.service.StoreRecoveryRecord(c.Context(), models.StoreRecDeps{UserID: user.ID.Hex(), Email: user.Email, Key: sign, Url: signUrl}) diff --git a/internal/repository/user_repository.go b/internal/repository/user_repository.go index efdc1b3..97a62ae 100644 --- a/internal/repository/user_repository.go +++ b/internal/repository/user_repository.go @@ -26,7 +26,7 @@ func NewUserRepository(deps Deps) *UserRepository { func (r *UserRepository) FindByEmail(ctx context.Context, email string) (*models.User, error) { var user models.User - err := r.mdb.FindOne(ctx, bson.M{"email": email}).Decode(&user) + err := r.mdb.FindOne(ctx, bson.M{"login": email}).Decode(&user) if err != nil { if err == mongo.ErrNoDocuments { return nil, nil diff --git a/tests/repository_test/repository_test.go b/tests/repository_test/repository_test.go index 6b69033..382ffef 100644 --- a/tests/repository_test/repository_test.go +++ b/tests/repository_test/repository_test.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "log" @@ -21,6 +22,7 @@ import ( const mongoURI = "mongodb://test:test@127.0.0.1:27020/?authMechanism=SCRAM-SHA-256&authSource=admin&directConnection=true" +// codeword unit tests func TestFindByEmail(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() @@ -45,14 +47,15 @@ func TestFindByEmail(t *testing.T) { userRepo := repository.NewUserRepository(repository.Deps{Rdb: nil, Mdb: db.Collection("users")}) t.Run("FindByEmail - existing user", func(t *testing.T) { - user, err := userRepo.FindByEmail(ctx, "email@mail.ru") + user, err := userRepo.FindByEmail(ctx, "admin") assert.NoError(t, err) assert.NotNil(t, user) - assert.Equal(t, "email@mail.ru", user.Email) + fmt.Println(user.Email) + assert.Equal(t, "admin", user.Login) }) t.Run("FindByEmail - non-existing user", func(t *testing.T) { - user, err := userRepo.FindByEmail(ctx, "nonexisting@example.com") + user, err := userRepo.FindByEmail(ctx, "neadmin") assert.NoError(t, err) assert.Nil(t, user) }) @@ -60,7 +63,8 @@ func TestFindByEmail(t *testing.T) { } func TestStoreRecoveryRecord(t *testing.T) { - ctx := context.Background() + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI)) require.NoError(t, err) @@ -93,3 +97,49 @@ func TestStoreRecoveryRecord(t *testing.T) { _ = database.Drop(ctx) } + +func TestGetRecoveryRecord(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI)) + require.NoError(t, err) + + defer func() { + _ = mongoClient.Disconnect(ctx) + }() + + database := mongoClient.Database("admin") + codeword := database.Collection("codeword") + _ = codeword.Drop(ctx) + + userRepo := repository.NewCodewordRepository(repository.Deps{Rdb: nil, Mdb: codeword}) + + ID := primitive.NewObjectID() + userID := "6597babdd1ba7e2dbd32d7e3" + email := "test@mail.ru" + key := "test_recovery_key" + + record := models.RestoreRequest{ + ID: ID, + UserID: userID, + Email: email, + Sign: key, + SignUrl: "def.url", + SignID: key + userID, + CreatedAt: time.Now(), + } + + _, err = codeword.InsertOne(ctx, record) + assert.NoError(t, err) + + result, err := userRepo.GetRecoveryRecord(ctx, key+userID) + assert.NoError(t, err) + assert.Equal(t, userID, result.UserID) + assert.Equal(t, email, result.Email) + assert.Equal(t, key, result.Sign) + + _ = database.Drop(ctx) +} + +// promocode unit tests From 02a9518b2c2e75d736f47b9021c3d326238b3995 Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 19 Jan 2024 16:46:08 +0300 Subject: [PATCH 26/66] add some promo tests --- tests/repository_test/repository_test.go | 379 ++++++++++++++++++++++- 1 file changed, 376 insertions(+), 3 deletions(-) diff --git a/tests/repository_test/repository_test.go b/tests/repository_test/repository_test.go index 382ffef..724a137 100644 --- a/tests/repository_test/repository_test.go +++ b/tests/repository_test/repository_test.go @@ -11,6 +11,7 @@ import ( "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "log" + "strconv" "testing" "time" @@ -18,8 +19,6 @@ import ( "github.com/stretchr/testify/assert" ) -// todo add another tests - const mongoURI = "mongodb://test:test@127.0.0.1:27020/?authMechanism=SCRAM-SHA-256&authSource=admin&directConnection=true" // codeword unit tests @@ -142,4 +141,378 @@ func TestGetRecoveryRecord(t *testing.T) { _ = database.Drop(ctx) } -// promocode unit tests +// promoCode unit tests + +func TestInitPromoCodeIndexes(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI)) + require.NoError(t, err) + + defer func() { + _ = mongoClient.Disconnect(ctx) + }() + + database := mongoClient.Database("admin") + promoCode := database.Collection("promoCode") + err = repository.InitPromoCodeIndexes(ctx, promoCode) + assert.NoError(t, err) +} + +func TestCreatePromoCode(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI)) + require.NoError(t, err) + defer func() { + _ = mongoClient.Disconnect(ctx) + }() + + database := mongoClient.Database("admin") + promoCode := database.Collection("promoCode") + _ = promoCode.Drop(ctx) + + userRepo := repository.NewPromoCodeRepository(promoCode) + + err = repository.InitPromoCodeIndexes(ctx, promoCode) + assert.NoError(t, err) + + t.Run("CreatePromoCode - success", func(t *testing.T) { + req := &models.PromoCode{ + Codeword: "test_codeword", + Description: faker.String(), + Greetings: faker.String(), + DueTo: 1737280065, + ActivationCount: 100, + } + createdPromoCode, err := userRepo.CreatePromoCode(ctx, req) + assert.NoError(t, err) + assert.NotNil(t, createdPromoCode) + assert.Equal(t, "test_codeword", createdPromoCode.Codeword) + }) + + t.Run("CreatePromoCode - duplicate codeword", func(t *testing.T) { + req := &models.PromoCode{ + Codeword: "test_codeword", + Description: faker.String(), + Greetings: faker.String(), + DueTo: 1737280065, + ActivationCount: 100, + } + _, err := userRepo.CreatePromoCode(ctx, req) + assert.Error(t, err) + }) +} + +func TestEditPromoCode(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI)) + require.NoError(t, err) + defer func() { + _ = mongoClient.Disconnect(ctx) + }() + + database := mongoClient.Database("admin") + promoCode := database.Collection("promoCode") + _ = promoCode.Drop(ctx) + + userRepo := repository.NewPromoCodeRepository(promoCode) + + err = repository.InitPromoCodeIndexes(ctx, promoCode) + assert.NoError(t, err) + + req := &models.PromoCode{ + Codeword: "test_codeword", + Description: faker.String(), + Greetings: faker.String(), + DueTo: 1737280065, + ActivationCount: 100, + } + createdPromoCode, err := userRepo.CreatePromoCode(ctx, req) + require.NoError(t, err) + + newDescription := "New Description" + + t.Run("EditPromoCode - success", func(t *testing.T) { + editReq := &models.ReqEditPromoCode{ + ID: createdPromoCode.ID.Hex(), + Description: &newDescription, + } + editedPromoCode, err := userRepo.EditPromoCode(ctx, editReq) + assert.NoError(t, err) + assert.NotNil(t, editedPromoCode) + assert.Equal(t, "New Description", editedPromoCode.Description) + }) + + t.Run("EditPromoCode - promo code not found", func(t *testing.T) { + nonExistingID := primitive.NewObjectID().Hex() + editReq := &models.ReqEditPromoCode{ + ID: nonExistingID, + } + _, err := userRepo.EditPromoCode(ctx, editReq) + assert.Error(t, err) + }) +} + +func TestGetPromoCodeByID(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI)) + require.NoError(t, err) + defer func() { + _ = mongoClient.Disconnect(ctx) + }() + + database := mongoClient.Database("admin") + promoCode := database.Collection("promoCode") + _ = promoCode.Drop(ctx) + + userRepo := repository.NewPromoCodeRepository(promoCode) + + err = repository.InitPromoCodeIndexes(ctx, promoCode) + assert.NoError(t, err) + + req := &models.PromoCode{ + Codeword: "test_codeword", + Description: faker.String(), + Greetings: faker.String(), + DueTo: 1737280065, + ActivationCount: 100, + } + createdPromoCode, err := userRepo.CreatePromoCode(ctx, req) + require.NoError(t, err) + + t.Run("GetPromoCodeByID - success", func(t *testing.T) { + result, err := userRepo.GetPromoCodeByID(ctx, createdPromoCode.ID) + assert.NoError(t, err) + assert.NotNil(t, result) + assert.Equal(t, createdPromoCode.Codeword, result.Codeword) + }) + + t.Run("GetPromoCodeByID - promo code not found", func(t *testing.T) { + nonExistingID := primitive.NewObjectID() + _, err := userRepo.GetPromoCodeByID(ctx, nonExistingID) + assert.Error(t, err) + }) +} + +func TestGetPromoCodesList(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI)) + require.NoError(t, err) + defer func() { + _ = mongoClient.Disconnect(ctx) + }() + + database := mongoClient.Database("admin") + promoCode := database.Collection("promoCode") + _ = promoCode.Drop(ctx) + + userRepo := repository.NewPromoCodeRepository(promoCode) + + err = repository.InitPromoCodeIndexes(ctx, promoCode) + assert.NoError(t, err) + for i := 0; i < 1111; i++ { + + req := &models.PromoCode{ + Codeword: "test" + faker.String() + strconv.Itoa(i), + Description: faker.String(), + Greetings: faker.String(), + DueTo: 1737280065, + ActivationCount: 100, + Delete: faker.Bool(), + Outdated: faker.Bool(), + OffLimit: faker.Bool(), + } + createdPromoCode, err := userRepo.CreatePromoCode(ctx, req) + assert.NoError(t, err) + assert.NotNil(t, createdPromoCode) + } + t.Run("GetPromoCodesList - true", func(t *testing.T) { + filter := models.GetPromoCodesListReqFilter{ + Text: "test", + Active: true, + } + req := &models.GetPromoCodesListReq{ + Page: 0, + Limit: 10, + Filter: filter, + } + promoCodes, count, err := userRepo.GetPromoCodesList(ctx, req) + assert.NoError(t, err) + assert.NotNil(t, promoCodes) + assert.True(t, count >= 0) + }) + + t.Run("GetPromoCodesList - false", func(t *testing.T) { + filter := models.GetPromoCodesListReqFilter{ + Text: "test", + Active: false, + } + req := &models.GetPromoCodesListReq{ + Page: 0, + Limit: 10, + Filter: filter, + } + promoCodes, count, err := userRepo.GetPromoCodesList(ctx, req) + assert.NoError(t, err) + assert.NotNil(t, promoCodes) + assert.True(t, count >= 0) + }) +} + +func TestActivatePromo(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI)) + require.NoError(t, err) + defer func() { + _ = mongoClient.Disconnect(ctx) + }() + + database := mongoClient.Database("admin") + promoCode := database.Collection("promoCode") + _ = promoCode.Drop(ctx) + + userRepo := repository.NewPromoCodeRepository(promoCode) + + err = repository.InitPromoCodeIndexes(ctx, promoCode) + assert.NoError(t, err) + + req := &models.PromoCode{ + Codeword: "test_codeword", + Description: faker.String(), + Greetings: faker.String(), + DueTo: 1737280065, + ActivationCount: 100, + } + createdPromoCode, err := userRepo.CreatePromoCode(ctx, req) + assert.NoError(t, err) + assert.NotNil(t, createdPromoCode) + + xid := "test_xid" + err = userRepo.AddFastLink(ctx, createdPromoCode.ID, xid) + assert.NoError(t, err) + + t.Run("ActivatePromo Codeword - success", func(t *testing.T) { + req := &models.ActivateReq{ + UserID: "6597babdd1ba7e2dbd32d7e3", + Codeword: "test_codeword", + } + activatedPromoCode, err := userRepo.ActivatePromo(ctx, req) + assert.NoError(t, err) + assert.NotNil(t, activatedPromoCode) + }) + + t.Run("ActivatePromo FastLink - success", func(t *testing.T) { + req := &models.ActivateReq{ + UserID: "6597babdd1ba7e2dbd32d7e3", + FastLink: "test_xid", + } + activatedPromoCode, err := userRepo.ActivatePromo(ctx, req) + assert.NoError(t, err) + assert.NotNil(t, activatedPromoCode) + }) + + t.Run("ActivatePromo - promo code not found", func(t *testing.T) { + req := &models.ActivateReq{ + UserID: "6597babdd1ba7e2dbd32d7e3", + Codeword: "non_existing_codeword", + } + _, err := userRepo.ActivatePromo(ctx, req) + assert.Error(t, err) + }) +} + +func TestDeletePromoCode(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI)) + require.NoError(t, err) + defer func() { + _ = mongoClient.Disconnect(ctx) + }() + + database := mongoClient.Database("admin") + promoCode := database.Collection("promoCode") + _ = promoCode.Drop(ctx) + + userRepo := repository.NewPromoCodeRepository(promoCode) + + err = repository.InitPromoCodeIndexes(ctx, promoCode) + assert.NoError(t, err) + + req := &models.PromoCode{ + Codeword: "test_codeword", + Description: faker.String(), + Greetings: faker.String(), + DueTo: 1737280065, + ActivationCount: 100, + } + createdPromoCode, err := userRepo.CreatePromoCode(ctx, req) + assert.NoError(t, err) + assert.NotNil(t, createdPromoCode) + + t.Run("DeletePromoCode - success", func(t *testing.T) { + err := userRepo.DeletePromoCode(ctx, createdPromoCode.ID.Hex()) + assert.NoError(t, err) + }) + + t.Run("DeletePromoCode - promo code not found", func(t *testing.T) { + nonExistingID := primitive.NewObjectID().Hex() + err := userRepo.DeletePromoCode(ctx, nonExistingID) + assert.Error(t, err) + }) +} + +func TestAddFastLink(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI)) + require.NoError(t, err) + defer func() { + _ = mongoClient.Disconnect(ctx) + }() + + database := mongoClient.Database("admin") + promoCode := database.Collection("promoCode") + _ = promoCode.Drop(ctx) + + userRepo := repository.NewPromoCodeRepository(promoCode) + + err = repository.InitPromoCodeIndexes(ctx, promoCode) + assert.NoError(t, err) + + req := &models.PromoCode{ + Codeword: "test_codeword", + Description: faker.String(), + Greetings: faker.String(), + DueTo: 1737280065, + ActivationCount: 100, + } + createdPromoCode, err := userRepo.CreatePromoCode(ctx, req) + assert.NoError(t, err) + assert.NotNil(t, createdPromoCode) + + t.Run("AddFastLink - success", func(t *testing.T) { + xid := "test_xid" + err := userRepo.AddFastLink(ctx, createdPromoCode.ID, xid) + assert.NoError(t, err) + }) + + t.Run("AddFastLink - promo code not found", func(t *testing.T) { + nonExistingID := primitive.NewObjectID() + xid := "test_xid" + err := userRepo.AddFastLink(ctx, nonExistingID, xid) + assert.Error(t, err) + }) +} From ba4d3d2b01f9030bde949fc7b0bb4d56db5e7e1d Mon Sep 17 00:00:00 2001 From: Pavel Date: Mon, 22 Jan 2024 18:09:36 +0300 Subject: [PATCH 27/66] add some e2e test --- go.mod | 2 ++ go.sum | 9 +++++- tests/e2e/recover_test.go | 64 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 tests/e2e/recover_test.go diff --git a/go.mod b/go.mod index 042e172..aee4146 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/golang/snappy v0.0.1 // indirect github.com/google/uuid v1.4.0 // indirect github.com/klauspost/compress v1.16.7 // indirect + github.com/kr/pretty v0.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect @@ -50,5 +51,6 @@ require ( golang.org/x/text v0.14.0 // indirect google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 731f00a..0f083ca 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,12 @@ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -144,8 +150,9 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/tests/e2e/recover_test.go b/tests/e2e/recover_test.go new file mode 100644 index 0000000..996136c --- /dev/null +++ b/tests/e2e/recover_test.go @@ -0,0 +1,64 @@ +package e2e + +import ( + "codeword/internal/models" + "encoding/json" + "testing" + + "github.com/gofiber/fiber/v2" + "github.com/stretchr/testify/assert" +) + +func TestRecoveryHandler(t *testing.T) { + client := fiber.AcquireClient() + + t.Run("HandleRecoveryRequest", func(t *testing.T) { + reqBody := models.RecoveryRequest{ + Email: "test@example.com", + RedirectionURL: "http://redirect.com", + } + reqJSON, _ := json.Marshal(reqBody) + + req := client.Post("/recover").Set("Content-Type", "application/json").Body(reqJSON) + + statusCode, resBody, errs := req.Bytes() + assert.NoError(t, errs[0]) + + assert.Equal(t, fiber.StatusOK, statusCode) + + var responseMap map[string]interface{} + err := json.Unmarshal(resBody, &responseMap) + assert.NoError(t, err) + + assert.Equal(t, "Recovery email sent successfully", responseMap["message"]) + }) + + t.Run("HandleRecoveryRequest_MissingEmail", func(t *testing.T) { + reqBody := models.RecoveryRequest{ + RedirectionURL: "http://redirect.com", + } + reqJSON, _ := json.Marshal(reqBody) + + req := client.Post("/recover").Set("Content-Type", "application/json").Body(reqJSON) + + statusCode, _, errs := req.Bytes() + assert.NoError(t, errs[0]) + + assert.Equal(t, fiber.StatusBadRequest, statusCode) + }) + + t.Run("HandleRecoveryRequest_UserNotFound", func(t *testing.T) { + reqBody := models.RecoveryRequest{ + Email: "nonexistent@example.com", + RedirectionURL: "http://redirect.com", + } + reqJSON, _ := json.Marshal(reqBody) + + req := client.Post("/recover").Set("Content-Type", "application/json").Body(reqJSON) + + statusCode, _, errs := req.Bytes() + assert.NoError(t, errs[0]) + + assert.Equal(t, fiber.StatusNotFound, statusCode) + }) +} From 2508b0fc5e54137e31d168be8fe16637af6b602b Mon Sep 17 00:00:00 2001 From: Pavel Date: Mon, 22 Jan 2024 22:36:19 +0300 Subject: [PATCH 28/66] add some one --- .../recovery/recovery_controller.go | 15 +++++- tests/e2e/recover_test.go | 47 +++++++++++++++---- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/internal/controller/recovery/recovery_controller.go b/internal/controller/recovery/recovery_controller.go index adfb670..1957941 100644 --- a/internal/controller/recovery/recovery_controller.go +++ b/internal/controller/recovery/recovery_controller.go @@ -47,6 +47,10 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Bad Request"}) } + if req.Email == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "email is required"}) + } + referralURL := c.Get("Referrer") if req.RedirectionURL == "" && referralURL != "" { @@ -113,5 +117,14 @@ func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } - return c.Status(fiber.StatusOK).JSON(tokens) + c.Cookie(&fiber.Cookie{ + Name: "refreshToken", + Value: tokens["refreshToken"], + Domain: ".pena.digital", + Expires: time.Now().Add(30 * 24 * time.Hour), + Secure: true, + HTTPOnly: true, + }) + + return c.Redirect(record.SignUrl + "?auth=" + tokens["accessToken"]) } diff --git a/tests/e2e/recover_test.go b/tests/e2e/recover_test.go index 996136c..aadeff7 100644 --- a/tests/e2e/recover_test.go +++ b/tests/e2e/recover_test.go @@ -3,34 +3,55 @@ package e2e import ( "codeword/internal/models" "encoding/json" - "testing" - + "fmt" "github.com/gofiber/fiber/v2" "github.com/stretchr/testify/assert" + "testing" ) +const BaseUrl = "http://localhost:8080" + +// post handler func TestRecoveryHandler(t *testing.T) { client := fiber.AcquireClient() t.Run("HandleRecoveryRequest", func(t *testing.T) { reqBody := models.RecoveryRequest{ - Email: "test@example.com", + Email: "admin", RedirectionURL: "http://redirect.com", } reqJSON, _ := json.Marshal(reqBody) - req := client.Post("/recover").Set("Content-Type", "application/json").Body(reqJSON) + req := client.Post(BaseUrl+"/recover").Set("Content-Type", "application/json").Body(reqJSON) statusCode, resBody, errs := req.Bytes() - assert.NoError(t, errs[0]) + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } assert.Equal(t, fiber.StatusOK, statusCode) var responseMap map[string]interface{} err := json.Unmarshal(resBody, &responseMap) assert.NoError(t, err) + fmt.Println(responseMap) + }) - assert.Equal(t, "Recovery email sent successfully", responseMap["message"]) + t.Run("HandleRecoveryRequest", func(t *testing.T) { + reqBody := models.RecoveryRequest{ + Email: "admin", + RedirectionURL: "http://redirect.com", + } + reqJSON, _ := json.Marshal(reqBody) + + req := client.Post(BaseUrl+"/recover").Set("Content-Type", "application/json").Body(reqJSON) + + statusCode, _, errs := req.Bytes() + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } + + assert.Equal(t, fiber.StatusAlreadyReported, statusCode) }) t.Run("HandleRecoveryRequest_MissingEmail", func(t *testing.T) { @@ -39,10 +60,12 @@ func TestRecoveryHandler(t *testing.T) { } reqJSON, _ := json.Marshal(reqBody) - req := client.Post("/recover").Set("Content-Type", "application/json").Body(reqJSON) + req := client.Post(BaseUrl+"/recover").Set("Content-Type", "application/json").Body(reqJSON) statusCode, _, errs := req.Bytes() - assert.NoError(t, errs[0]) + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } assert.Equal(t, fiber.StatusBadRequest, statusCode) }) @@ -54,11 +77,15 @@ func TestRecoveryHandler(t *testing.T) { } reqJSON, _ := json.Marshal(reqBody) - req := client.Post("/recover").Set("Content-Type", "application/json").Body(reqJSON) + req := client.Post(BaseUrl+"/recover").Set("Content-Type", "application/json").Body(reqJSON) statusCode, _, errs := req.Bytes() - assert.NoError(t, errs[0]) + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } assert.Equal(t, fiber.StatusNotFound, statusCode) }) } + +// get handler From c2ad4cd3a2c08dc975b7cb4d6dea4c5ea7c5a369 Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 25 Jan 2024 19:20:41 +0300 Subject: [PATCH 29/66] add some recover tests --- tests/e2e/promo_test.go | 1 + tests/e2e/recover_test.go | 31 ++++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 tests/e2e/promo_test.go diff --git a/tests/e2e/promo_test.go b/tests/e2e/promo_test.go new file mode 100644 index 0000000..df8caf7 --- /dev/null +++ b/tests/e2e/promo_test.go @@ -0,0 +1 @@ +package e2e diff --git a/tests/e2e/recover_test.go b/tests/e2e/recover_test.go index aadeff7..1d23f39 100644 --- a/tests/e2e/recover_test.go +++ b/tests/e2e/recover_test.go @@ -7,9 +7,13 @@ import ( "github.com/gofiber/fiber/v2" "github.com/stretchr/testify/assert" "testing" + "time" ) -const BaseUrl = "http://localhost:8080" +const ( + BaseUrl = "http://localhost:8080" + ValidSign = "GSiyv5zBITGshqnvYLHKtXE3e4yZjKGvruOVFWuUuj9Nvaps28-Zt6RDq9n47eaNUlay1-nUVld61I3xoAAgCA==65b286c2f13095d96792079d" +) // post handler func TestRecoveryHandler(t *testing.T) { @@ -89,3 +93,28 @@ func TestRecoveryHandler(t *testing.T) { } // get handler +func TestRecoveryLinkHandler(t *testing.T) { + client := fiber.AcquireClient() + + t.Run("HandleRecoveryLink_ValidSign", func(t *testing.T) { + req := client.Get(BaseUrl + "/recover/" + ValidSign) + statusCode, _, errs := req.Bytes() + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } + + assert.Equal(t, fiber.StatusOK, statusCode) + + fmt.Println("Recovery link handled successfully") + }) + time.Sleep(15 * time.Minute) + t.Run("HandleRecoveryLink_ExpiredSign", func(t *testing.T) { + req := client.Get(BaseUrl + "/recover/" + ValidSign) + statusCode, _, errs := req.Bytes() + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } + assert.Equal(t, fiber.StatusNotAcceptable, statusCode) + fmt.Println("Recovery link with expired sign handled correctly") + }) +} From 539f88a89d8b2af3ea87b631a86d096cabb526c5 Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 25 Jan 2024 20:18:00 +0300 Subject: [PATCH 30/66] add postfix if fastlink not nil --- internal/services/promocode_service.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index 1cd8915..c438345 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -6,6 +6,7 @@ import ( "codeword/internal/proto/discount" "codeword/internal/utils/genID" "context" + "fmt" "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" ) @@ -84,6 +85,12 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa return "", err } + var postfix string + + if req.FastLink != "" { + postfix = fmt.Sprintf(":(%s)", req.FastLink) + } + var privileges []models.Privilege privilege := models.Privilege{ PrivilegeID: promoCode.Bonus.Privilege.PrivilegeID, @@ -91,9 +98,8 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa } privileges = append(privileges, privilege) - // создание fake tariff fakeTariff := &models.Tariff{ - Name: promoCode.Codeword, // используем codeword как имя тарифа + Name: promoCode.Codeword + postfix, Privileges: privileges, Deleted: promoCode.Delete, CreatedAt: promoCode.CreatedAt, @@ -105,7 +111,7 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa disOverHelm := true discountRequest := &discount.CreateDiscountRequest{ - Name: promoCode.Codeword, + Name: promoCode.Codeword + postfix, Layer: promoCode.Bonus.Discount.Layer, Description: "", Condition: &discount.DiscountCondition{ From 87b255a3566d3ba9120de67c65b6a2b8a19e6e6d Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 26 Jan 2024 14:47:44 +0300 Subject: [PATCH 31/66] add test create promo --- .../promocode/promocode_controller.go | 4 + tests/e2e/promo_test.go | 184 ++++++++++++++++++ 2 files changed, 188 insertions(+) diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index e1eb1b1..3876843 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -27,6 +27,10 @@ func (p *PromoCodeController) CreatePromoCode(c *fiber.Ctx) error { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) } + if req.Codeword == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "codeword ID is required"}) + } + createdPromoCode, err := p.promoCodeService.CreatePromoCode(c.Context(), &req) if err != nil { p.logger.Error("Failed to create promocode", zap.Error(err)) diff --git a/tests/e2e/promo_test.go b/tests/e2e/promo_test.go index df8caf7..0b68f4b 100644 --- a/tests/e2e/promo_test.go +++ b/tests/e2e/promo_test.go @@ -1 +1,185 @@ package e2e + +import ( + "codeword/internal/models" + "encoding/json" + "fmt" + "github.com/gofiber/fiber/v2" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestPromoCodeControllerE2E(t *testing.T) { + client := fiber.AcquireClient() + + t.Run("CreatePromoCode-success", func(t *testing.T) { + jsonString := `{ + "codeword": "example", + "description": "Example description", + "greetings": "Example greetings", + "dueTo": 1734429225, + "activationCount": 100, + "bonus": { + "privilege": { + "privilegeID": "examplePrivilegeID", + "amount": 50 + }, + "discount": { + "layer": 1, + "factor": 0.2, + "target": "exampleTarget", + "threshold": 500 + } + }, + "outdated": false, + "offLimit": false, + "delete": false + }` + + var reqBody models.PromoCode + err := json.Unmarshal([]byte(jsonString), &reqBody) + assert.NoError(t, err) + + reqJSON, _ := json.Marshal(reqBody) + + req := client.Post(BaseUrl+"/promocode/create").Set("Content-Type", "application/json").Body(reqJSON) + + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } + + assert.Equal(t, fiber.StatusCreated, statusCode) + + var response models.PromoCode + err = json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fmt.Println(response) + }) + t.Run("CreatePromoCode-duplicate", func(t *testing.T) { + jsonString := `{ + "codeword": "example", + "description": "Example description", + "greetings": "Example greetings", + "dueTo": 1734429225, + "activationCount": 100, + "bonus": { + "privilege": { + "privilegeID": "examplePrivilegeID", + "amount": 50 + }, + "discount": { + "layer": 1, + "factor": 0.2, + "target": "exampleTarget", + "threshold": 500 + } + }, + "outdated": false, + "offLimit": false, + "delete": false + }` + + var reqBody models.PromoCode + err := json.Unmarshal([]byte(jsonString), &reqBody) + assert.NoError(t, err) + + reqJSON, _ := json.Marshal(reqBody) + + req := client.Post(BaseUrl+"/promocode/create").Set("Content-Type", "application/json").Body(reqJSON) + + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.Error(t, errs[0]) + } + + assert.Equal(t, fiber.StatusBadRequest, statusCode) + + var response map[string]interface{} + err = json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fmt.Println(response["error"]) + }) + t.Run("CreatePromoCode-invalid request payload", func(t *testing.T) { + jsonString := `{ + "example": "example", + "description": "Example description", + "greetings": "Example greetings", + "dueTo": 1734429225, + "activationCount": 100, + "bonus": { + "privilege": { + "privilegeID": "examplePrivilegeID", + "amount": 50 + }, + "discount": { + "layer": 1, + "factor": 0.2, + "target": "exampleTarget", + "threshold": 500 + } + }, + "outdated": false, + "offLimit": false, + "delete": false + }` + + req := client.Post(BaseUrl+"/promocode/create").Set("Content-Type", "application/json").Body([]byte(jsonString)) + + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.Error(t, errs[0]) + } + + assert.Equal(t, fiber.StatusBadRequest, statusCode) + + var response map[string]interface{} + err := json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fmt.Println(response["error"]) + }) + t.Run("CreatePromoCode-nil codeword", func(t *testing.T) { + jsonString := `{ + "description": "Example description", + "greetings": "Example greetings", + "dueTo": 1734429225, + "activationCount": 100, + "bonus": { + "privilege": { + "privilegeID": "examplePrivilegeID", + "amount": 50 + }, + "discount": { + "layer": 1, + "factor": 0.2, + "target": "exampleTarget", + "threshold": 500 + } + }, + "outdated": false, + "offLimit": false, + "delete": false + }` + + var reqBody models.PromoCode + err := json.Unmarshal([]byte(jsonString), &reqBody) + assert.NoError(t, err) + + reqJSON, _ := json.Marshal(reqBody) + + req := client.Post(BaseUrl+"/promocode/create").Set("Content-Type", "application/json").Body(reqJSON) + + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.Error(t, errs[0]) + } + + assert.Equal(t, fiber.StatusBadRequest, statusCode) + + var response map[string]interface{} + err = json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fmt.Println(response["error"]) + }) + +} From a24f601f1ff83f24f629aab2f3a3aef8a9ed33c2 Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 26 Jan 2024 14:50:47 +0300 Subject: [PATCH 32/66] add test create promo --- internal/controller/promocode/promocode_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index 3876843..ec478ae 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -28,7 +28,7 @@ func (p *PromoCodeController) CreatePromoCode(c *fiber.Ctx) error { } if req.Codeword == "" { - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "codeword ID is required"}) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "codeword is required"}) } createdPromoCode, err := p.promoCodeService.CreatePromoCode(c.Context(), &req) From 82c3cf2679eb68b15c372eb87e52aa369d0d3a02 Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 26 Jan 2024 15:24:53 +0300 Subject: [PATCH 33/66] add test edit promo --- tests/e2e/promo_test.go | 121 +++++++++++++++++++++++++++++++++++++- tests/e2e/recover_test.go | 53 ++++++++--------- 2 files changed, 145 insertions(+), 29 deletions(-) diff --git a/tests/e2e/promo_test.go b/tests/e2e/promo_test.go index 0b68f4b..c8a3243 100644 --- a/tests/e2e/promo_test.go +++ b/tests/e2e/promo_test.go @@ -6,10 +6,14 @@ import ( "fmt" "github.com/gofiber/fiber/v2" "github.com/stretchr/testify/assert" + "go.mongodb.org/mongo-driver/bson/primitive" "testing" ) -func TestPromoCodeControllerE2E(t *testing.T) { +var promoID string + +// CreatePromoCode +func TestCreatePromoCode(t *testing.T) { client := fiber.AcquireClient() t.Run("CreatePromoCode-success", func(t *testing.T) { @@ -54,6 +58,7 @@ func TestPromoCodeControllerE2E(t *testing.T) { var response models.PromoCode err = json.Unmarshal(resBody, &response) assert.NoError(t, err) + promoID = response.ID.Hex() fmt.Println(response) }) t.Run("CreatePromoCode-duplicate", func(t *testing.T) { @@ -181,5 +186,117 @@ func TestPromoCodeControllerE2E(t *testing.T) { assert.NoError(t, err) fmt.Println(response["error"]) }) - +} + +// EditPromoCode +func TestEditPromoCode(t *testing.T) { + client := fiber.AcquireClient() + + t.Run("EditPromoCode-success", func(t *testing.T) { + reqBody := models.ReqEditPromoCode{ + ID: promoID, + Description: toString("Updated description"), + Greetings: toString("Updated greetings"), + DueTo: toInt64(1734429225), + ActivationCount: toInt64(150), + Delete: toBool(false), + } + + reqJSON, _ := json.Marshal(reqBody) + req := client.Put(BaseUrl+"/promocode/edit").Set("Content-Type", "application/json").Body(reqJSON) + + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } + assert.Equal(t, fiber.StatusOK, statusCode) + + var response models.PromoCode + err := json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fmt.Println(response) + }) + + t.Run("EditPromoCode-success one column", func(t *testing.T) { + reqBody := models.ReqEditPromoCode{ + ID: promoID, + Greetings: toString("Updated greetings one"), + } + + reqJSON, _ := json.Marshal(reqBody) + req := client.Put(BaseUrl+"/promocode/edit").Set("Content-Type", "application/json").Body(reqJSON) + + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } + assert.Equal(t, fiber.StatusOK, statusCode) + + var response models.PromoCode + err := json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fmt.Println(response) + }) + + t.Run("EditPromoCode-promocod not found", func(t *testing.T) { + reqBody := models.ReqEditPromoCode{ + ID: primitive.NewObjectID().Hex(), + Description: toString("Updated description"), + Greetings: toString("Updated greetings"), + DueTo: toInt64(1734429225), + ActivationCount: toInt64(150), + Delete: toBool(false), + } + + reqJSON, _ := json.Marshal(reqBody) + req := client.Put(BaseUrl+"/promocode/edit").Set("Content-Type", "application/json").Body(reqJSON) + + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } + assert.Equal(t, fiber.StatusNotFound, statusCode) + + var response map[string]interface{} + err := json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fmt.Println(response["error"]) + }) + + t.Run("EditPromoCode-invalid request payload", func(t *testing.T) { + reqBody := map[string]interface{}{ + "invalid_field": "example", + "description": "Updated description", + "greetings": "Updated greetings", + "dueTo": 1734429225, + "activationCount": 150, + "delete": false, + } + + reqJSON, _ := json.Marshal(reqBody) + req := client.Put(BaseUrl+"/promocode/edit").Set("Content-Type", "application/json").Body(reqJSON) + + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } + assert.Equal(t, fiber.StatusBadRequest, statusCode) + + var response map[string]interface{} + err := json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fmt.Println(response["error"]) + }) +} + +func toString(s string) *string { + return &s +} + +func toInt64(i int64) *int64 { + return &i +} + +func toBool(b bool) *bool { + return &b } diff --git a/tests/e2e/recover_test.go b/tests/e2e/recover_test.go index 1d23f39..489cafd 100644 --- a/tests/e2e/recover_test.go +++ b/tests/e2e/recover_test.go @@ -7,7 +7,6 @@ import ( "github.com/gofiber/fiber/v2" "github.com/stretchr/testify/assert" "testing" - "time" ) const ( @@ -21,7 +20,7 @@ func TestRecoveryHandler(t *testing.T) { t.Run("HandleRecoveryRequest", func(t *testing.T) { reqBody := models.RecoveryRequest{ - Email: "admin", + Email: "adminSOLO", RedirectionURL: "http://redirect.com", } reqJSON, _ := json.Marshal(reqBody) @@ -93,28 +92,28 @@ func TestRecoveryHandler(t *testing.T) { } // get handler -func TestRecoveryLinkHandler(t *testing.T) { - client := fiber.AcquireClient() - - t.Run("HandleRecoveryLink_ValidSign", func(t *testing.T) { - req := client.Get(BaseUrl + "/recover/" + ValidSign) - statusCode, _, errs := req.Bytes() - if len(errs) != 0 { - assert.NoError(t, errs[0]) - } - - assert.Equal(t, fiber.StatusOK, statusCode) - - fmt.Println("Recovery link handled successfully") - }) - time.Sleep(15 * time.Minute) - t.Run("HandleRecoveryLink_ExpiredSign", func(t *testing.T) { - req := client.Get(BaseUrl + "/recover/" + ValidSign) - statusCode, _, errs := req.Bytes() - if len(errs) != 0 { - assert.NoError(t, errs[0]) - } - assert.Equal(t, fiber.StatusNotAcceptable, statusCode) - fmt.Println("Recovery link with expired sign handled correctly") - }) -} +//func TestRecoveryLinkHandler(t *testing.T) { +// client := fiber.AcquireClient() +// +// t.Run("HandleRecoveryLink_ValidSign", func(t *testing.T) { +// req := client.Get(BaseUrl + "/recover/" + ValidSign) +// statusCode, _, errs := req.Bytes() +// if len(errs) != 0 { +// assert.NoError(t, errs[0]) +// } +// +// assert.Equal(t, fiber.StatusOK, statusCode) +// +// fmt.Println("Recovery link handled successfully") +// }) +// time.Sleep(15 * time.Minute) +// t.Run("HandleRecoveryLink_ExpiredSign", func(t *testing.T) { +// req := client.Get(BaseUrl + "/recover/" + ValidSign) +// statusCode, _, errs := req.Bytes() +// if len(errs) != 0 { +// assert.NoError(t, errs[0]) +// } +// assert.Equal(t, fiber.StatusNotAcceptable, statusCode) +// fmt.Println("Recovery link with expired sign handled correctly") +// }) +//} From bfc332fd7760aeb0ed3cd9e53a621c2eca23fe2e Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 26 Jan 2024 18:37:07 +0300 Subject: [PATCH 34/66] add another tests --- .../promocode/promocode_controller.go | 4 + tests/e2e/promo_test.go | 298 +++++++++++++++++- tests/e2e/recover_test.go | 61 ++-- 3 files changed, 318 insertions(+), 45 deletions(-) diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index ec478ae..a83c9a1 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -148,6 +148,10 @@ func (p *PromoCodeController) CreateFastLink(c *fiber.Ctx) error { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) } + if req.PromoCodeID == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "PromoCode ID is required"}) + } + fastLink, err := p.promoCodeService.CreateFastLink(c.Context(), req.PromoCodeID) if err != nil { p.logger.Error("Failed to create fastlink", zap.Error(err)) diff --git a/tests/e2e/promo_test.go b/tests/e2e/promo_test.go index c8a3243..df1f2a0 100644 --- a/tests/e2e/promo_test.go +++ b/tests/e2e/promo_test.go @@ -5,19 +5,23 @@ import ( "encoding/json" "fmt" "github.com/gofiber/fiber/v2" + "github.com/pioz/faker" "github.com/stretchr/testify/assert" "go.mongodb.org/mongo-driver/bson/primitive" + "strconv" "testing" ) var promoID string +var fastLink string // CreatePromoCode func TestCreatePromoCode(t *testing.T) { client := fiber.AcquireClient() t.Run("CreatePromoCode-success", func(t *testing.T) { - jsonString := `{ + for i := 0; i < 10; i++ { + jsonString := `{ "codeword": "example", "description": "Example description", "greetings": "Example greetings", @@ -40,26 +44,31 @@ func TestCreatePromoCode(t *testing.T) { "delete": false }` - var reqBody models.PromoCode - err := json.Unmarshal([]byte(jsonString), &reqBody) - assert.NoError(t, err) + var reqBody models.PromoCode + err := json.Unmarshal([]byte(jsonString), &reqBody) + assert.NoError(t, err) + if i != 0 { + reqBody.Codeword = reqBody.Codeword + faker.String() + strconv.Itoa(i) + } - reqJSON, _ := json.Marshal(reqBody) + reqJSON, _ := json.Marshal(reqBody) - req := client.Post(BaseUrl+"/promocode/create").Set("Content-Type", "application/json").Body(reqJSON) + req := client.Post(BaseUrl+"/promocode/create").Set("Content-Type", "application/json").Body(reqJSON) - statusCode, resBody, errs := req.Bytes() - if len(errs) != 0 { - assert.NoError(t, errs[0]) + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } + + assert.Equal(t, fiber.StatusCreated, statusCode) + + var response models.PromoCode + err = json.Unmarshal(resBody, &response) + assert.NoError(t, err) + promoID = response.ID.Hex() + fmt.Println(response) } - assert.Equal(t, fiber.StatusCreated, statusCode) - - var response models.PromoCode - err = json.Unmarshal(resBody, &response) - assert.NoError(t, err) - promoID = response.ID.Hex() - fmt.Println(response) }) t.Run("CreatePromoCode-duplicate", func(t *testing.T) { jsonString := `{ @@ -300,3 +309,260 @@ func toInt64(i int64) *int64 { func toBool(b bool) *bool { return &b } + +// CreateFastLink +func TestCreateFastLink(t *testing.T) { + client := fiber.AcquireClient() + + t.Run("CreateFastLink-success", func(t *testing.T) { + reqBody := map[string]string{"id": promoID} + + reqJSON, _ := json.Marshal(reqBody) + + req := client.Post(BaseUrl+"/promocode/fastlink").Set("Content-Type", "application/json").Body(reqJSON) + + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } + + assert.Equal(t, fiber.StatusCreated, statusCode) + + var response map[string]string + err := json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fastLink = response["fastlink"] + fmt.Println(response["fastlink"]) + }) + + t.Run("CreateFastLink-missing promoCodeID", func(t *testing.T) { + req := client.Post(BaseUrl+"/promocode/fastlink").Set("Content-Type", "application/json").Body([]byte(`{}`)) + + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.Error(t, errs[0]) + } + + assert.Equal(t, fiber.StatusBadRequest, statusCode) + + var response map[string]interface{} + err := json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fmt.Println(response["error"]) + }) + + t.Run("CreateFastLink-promocode not found", func(t *testing.T) { + reqBody := map[string]string{"id": primitive.NewObjectID().Hex()} + + reqJSON, _ := json.Marshal(reqBody) + + req := client.Post(BaseUrl+"/promocode/fastlink").Set("Content-Type", "application/json").Body(reqJSON) + + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.Error(t, errs[0]) + } + + assert.Equal(t, fiber.StatusNotFound, statusCode) + + var response map[string]interface{} + err := json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fmt.Println(response["error"]) + }) +} + +// GetPromoCodesList +func TestGetPromoCodesList(t *testing.T) { + client := fiber.AcquireClient() + + t.Run("GetPromoCodesList-success", func(t *testing.T) { + reqBody := models.GetPromoCodesListReq{ + Page: 0, + Limit: 10, + Filter: models.GetPromoCodesListReqFilter{ + Text: "example", + Active: true, + }, + } + + reqJSON, _ := json.Marshal(reqBody) + req := client.Post(BaseUrl+"/promocode/getList").Set("Content-Type", "application/json").Body(reqJSON) + + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } + assert.Equal(t, fiber.StatusOK, statusCode) + + var response models.GetPromoCodesListResp + err := json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fmt.Println(response) + }) + + t.Run("GetPromoCodesList-invalid request payload", func(t *testing.T) { + req := client.Post(BaseUrl+"/promocode/getList").Set("Content-Type", "application/json").Body([]byte("invalid json")) + + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } + assert.Equal(t, fiber.StatusBadRequest, statusCode) + + var response map[string]interface{} + err := json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fmt.Println(response["error"]) + }) +} + +// ActivatePromoCode +func TestActivatePromoCode(t *testing.T) { + client := fiber.AcquireClient() + + t.Run("ActivatePromoCode-success codeword", func(t *testing.T) { + reqBody := models.ActivateReq{ + UserID: ExampleUserID, + Codeword: "example", + } + + reqJSON, _ := json.Marshal(reqBody) + + req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Body(reqJSON) + + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } + + assert.Equal(t, fiber.StatusOK, statusCode) + + var response models.ActivateResp + err := json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fmt.Println(response) + }) + + t.Run("ActivatePromoCode-success fastLink", func(t *testing.T) { + reqBody := models.ActivateReq{ + UserID: ExampleUserID, + FastLink: fastLink, + } + + reqJSON, _ := json.Marshal(reqBody) + + req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Body(reqJSON) + + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } + + assert.Equal(t, fiber.StatusOK, statusCode) + + var response models.ActivateResp + err := json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fmt.Println(response) + }) + + t.Run("ActivatePromoCode-missing userid", func(t *testing.T) { + reqBody := models.ActivateReq{ + Codeword: "example", + } + reqJSON, _ := json.Marshal(reqBody) + + req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Body(reqJSON) + + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.Error(t, errs[0]) + } + + assert.Equal(t, fiber.StatusBadRequest, statusCode) + + var response map[string]interface{} + err := json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fmt.Println(response["error"]) + }) + + t.Run("ActivatePromoCode-missing codeword and fastlink", func(t *testing.T) { + reqBody := models.ActivateReq{ + UserID: ExampleUserID, + } + + reqJSON, _ := json.Marshal(reqBody) + + req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Body(reqJSON) + + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.Error(t, errs[0]) + } + + assert.Equal(t, fiber.StatusBadRequest, statusCode) + + var response map[string]interface{} + err := json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fmt.Println(response["error"]) + }) + + t.Run("ActivatePromoCode-promocode not found", func(t *testing.T) { + reqBody := models.ActivateReq{ + UserID: ExampleUserID, + Codeword: "none", + } + + reqJSON, _ := json.Marshal(reqBody) + + req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Body(reqJSON) + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.Error(t, errs[0]) + } + + assert.Equal(t, fiber.StatusNotFound, statusCode) + + var response map[string]interface{} + err := json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fmt.Println(response["error"]) + }) +} + +// DeletePromoCode +func TestDeletePromoCode(t *testing.T) { + client := fiber.AcquireClient() + + t.Run("DeletePromoCode-success", func(t *testing.T) { + + req := client.Delete(BaseUrl + "/promocode/" + promoID) + + statusCode, _, errs := req.Bytes() + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } + + assert.Equal(t, fiber.StatusOK, statusCode) + }) + + t.Run("DeletePromoCode-promocode not found", func(t *testing.T) { + + req := client.Delete(BaseUrl + "/promocode/" + primitive.NewObjectID().Hex()) + + statusCode, resBody, errs := req.Bytes() + if len(errs) != 0 { + assert.Error(t, errs[0]) + } + + assert.Equal(t, fiber.StatusNotFound, statusCode) + + var response map[string]interface{} + err := json.Unmarshal(resBody, &response) + assert.NoError(t, err) + fmt.Println(response["error"]) + }) +} diff --git a/tests/e2e/recover_test.go b/tests/e2e/recover_test.go index 489cafd..0aab88f 100644 --- a/tests/e2e/recover_test.go +++ b/tests/e2e/recover_test.go @@ -7,11 +7,14 @@ import ( "github.com/gofiber/fiber/v2" "github.com/stretchr/testify/assert" "testing" + "time" ) +// todo добавить другие константы такие как exampleUserID const ( - BaseUrl = "http://localhost:8080" - ValidSign = "GSiyv5zBITGshqnvYLHKtXE3e4yZjKGvruOVFWuUuj9Nvaps28-Zt6RDq9n47eaNUlay1-nUVld61I3xoAAgCA==65b286c2f13095d96792079d" + BaseUrl = "http://localhost:8080" + ValidSign = "GSiyv5zBITGshqnvYLHKtXE3e4yZjKGvruOVFWuUuj9Nvaps28-Zt6RDq9n47eaNUlay1-nUVld61I3xoAAgCA==65b286c2f13095d96792079d" + ExampleUserID = "6597babdd1ba7e2dbd32d7e3" ) // post handler @@ -40,9 +43,9 @@ func TestRecoveryHandler(t *testing.T) { fmt.Println(responseMap) }) - t.Run("HandleRecoveryRequest", func(t *testing.T) { + t.Run("HandleRecoveryRequest-AlreadyReported", func(t *testing.T) { reqBody := models.RecoveryRequest{ - Email: "admin", + Email: "adminSOLO", RedirectionURL: "http://redirect.com", } reqJSON, _ := json.Marshal(reqBody) @@ -92,28 +95,28 @@ func TestRecoveryHandler(t *testing.T) { } // get handler -//func TestRecoveryLinkHandler(t *testing.T) { -// client := fiber.AcquireClient() -// -// t.Run("HandleRecoveryLink_ValidSign", func(t *testing.T) { -// req := client.Get(BaseUrl + "/recover/" + ValidSign) -// statusCode, _, errs := req.Bytes() -// if len(errs) != 0 { -// assert.NoError(t, errs[0]) -// } -// -// assert.Equal(t, fiber.StatusOK, statusCode) -// -// fmt.Println("Recovery link handled successfully") -// }) -// time.Sleep(15 * time.Minute) -// t.Run("HandleRecoveryLink_ExpiredSign", func(t *testing.T) { -// req := client.Get(BaseUrl + "/recover/" + ValidSign) -// statusCode, _, errs := req.Bytes() -// if len(errs) != 0 { -// assert.NoError(t, errs[0]) -// } -// assert.Equal(t, fiber.StatusNotAcceptable, statusCode) -// fmt.Println("Recovery link with expired sign handled correctly") -// }) -//} +func TestRecoveryLinkHandler(t *testing.T) { + client := fiber.AcquireClient() + + t.Run("HandleRecoveryLink_ValidSign", func(t *testing.T) { + req := client.Get(BaseUrl + "/recover/" + ValidSign) + statusCode, _, errs := req.Bytes() + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } + + assert.Equal(t, fiber.StatusOK, statusCode) + + fmt.Println("Recovery link handled successfully") + }) + time.Sleep(15 * time.Minute) + t.Run("HandleRecoveryLink_ExpiredSign", func(t *testing.T) { + req := client.Get(BaseUrl + "/recover/" + ValidSign) + statusCode, _, errs := req.Bytes() + if len(errs) != 0 { + assert.NoError(t, errs[0]) + } + assert.Equal(t, fiber.StatusNotAcceptable, statusCode) + fmt.Println("Recovery link with expired sign handled correctly") + }) +} From c282b8151ba2eda1a16c1762427c27d6cb61f8e8 Mon Sep 17 00:00:00 2001 From: Pavel Date: Sat, 27 Jan 2024 15:51:32 +0300 Subject: [PATCH 35/66] 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() From adf42e8a22e59d4fa4cd99e6832e88fb35aebee8 Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 2 Feb 2024 15:34:03 +0300 Subject: [PATCH 36/66] replace mongo pkg add common --- go.mod | 3 +- go.sum | 10 +++--- internal/initialize/mongo.go | 14 ++++----- pkg/mongo/config.go | 22 -------------- pkg/mongo/connection.go | 59 ------------------------------------ pkg/mongo/errors.go | 7 ----- 6 files changed, 13 insertions(+), 102 deletions(-) delete mode 100644 pkg/mongo/config.go delete mode 100644 pkg/mongo/connection.go delete mode 100644 pkg/mongo/errors.go diff --git a/go.mod b/go.mod index aee4146..98a0a6c 100644 --- a/go.mod +++ b/go.mod @@ -9,13 +9,14 @@ require ( github.com/joho/godotenv v1.5.1 github.com/pioz/faker v1.7.3 github.com/rs/xid v1.5.0 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.8.4 github.com/twmb/franz-go v1.15.4 go.mongodb.org/mongo-driver v1.13.1 go.uber.org/zap v1.26.0 google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac google.golang.org/grpc v1.60.1 google.golang.org/protobuf v1.32.0 + penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240202120244-c4ef330cfe5d ) require ( diff --git a/go.sum b/go.sum index 0f083ca..6f4b4c4 100644 --- a/go.sum +++ b/go.sum @@ -63,13 +63,9 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/twmb/franz-go v1.15.4 h1:qBCkHaiutetnrXjAUWA99D9FEcZVMt2AYwkH3vWEQTw= github.com/twmb/franz-go v1.15.4/go.mod h1:rC18hqNmfo8TMc1kz7CQmHL74PLNF8KVvhflxiiJZCU= github.com/twmb/franz-go/pkg/kmsg v1.7.0 h1:a457IbvezYfA5UkiBvyV3zj0Is3y1i8EJgqjJYoij2E= @@ -160,3 +156,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240202120244-c4ef330cfe5d h1:gbaDt35HMDqOK84WYmDIlXMI7rstUcRqNttaT6Kx1do= +penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240202120244-c4ef330cfe5d/go.mod h1:lTmpjry+8evVkXWbEC+WMOELcFkRD1lFMc7J09mOndM= diff --git a/internal/initialize/mongo.go b/internal/initialize/mongo.go index fe98615..c947b4a 100644 --- a/internal/initialize/mongo.go +++ b/internal/initialize/mongo.go @@ -2,21 +2,21 @@ package initialize import ( "codeword/internal/repository" - mdb "codeword/pkg/mongo" "context" "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" + mdb "penahub.gitlab.yandexcloud.net/backend/penahub_common/mongo" "time" ) func MongoDB(ctx context.Context, cfg Config) (*mongo.Database, error) { dbConfig := &mdb.Configuration{ - MongoHost: cfg.MongoHost, - MongoPort: cfg.MongoPort, - MongoUser: cfg.MongoUser, - MongoPassword: cfg.MongoPassword, - MongoDatabase: cfg.MongoDatabase, - MongoAuth: cfg.MongoAuth, + Host: cfg.MongoHost, + Port: cfg.MongoPort, + User: cfg.MongoUser, + Password: cfg.MongoPassword, + DatabaseName: cfg.MongoDatabase, + Auth: cfg.MongoAuth, } newCtx, cancel := context.WithTimeout(ctx, 10*time.Second) diff --git a/pkg/mongo/config.go b/pkg/mongo/config.go deleted file mode 100644 index dc809b4..0000000 --- a/pkg/mongo/config.go +++ /dev/null @@ -1,22 +0,0 @@ -package mongo - -import ( - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -type Configuration struct { - MongoHost string `env:"MONGO_HOST" envDefault:"127.0.0.1"` - MongoPort string `env:"MONGO_PORT" envDefault:"27020"` - MongoUser string `env:"MONGO_USER" envDefault:"test"` - MongoPassword string `env:"MONGO_PASSWORD" envDefault:"test"` - MongoDatabase string `env:"MONGO_DB" envDefault:"admin"` - MongoAuth string `env:"MONGO_AUTH" envDefault:"admin"` -} - -type RequestSettings struct { - Driver *mongo.Collection - Options *options.FindOptions - Filter primitive.M -} diff --git a/pkg/mongo/connection.go b/pkg/mongo/connection.go deleted file mode 100644 index 726b277..0000000 --- a/pkg/mongo/connection.go +++ /dev/null @@ -1,59 +0,0 @@ -package mongo - -import ( - "context" - "fmt" - "log" - "net" - "net/url" - "time" - - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -type ConnectDeps struct { - Configuration *Configuration - Timeout time.Duration -} - -func Connect(ctx context.Context, deps *ConnectDeps) (*mongo.Database, error) { - if deps == nil { - return nil, ErrEmptyArgs - } - - mongoURI := &url.URL{ - Scheme: "mongodb", - Host: net.JoinHostPort(deps.Configuration.MongoHost, deps.Configuration.MongoPort), - } - - connectionOptions := options.Client(). - ApplyURI(mongoURI.String()). - SetAuth(options.Credential{ - AuthMechanism: "SCRAM-SHA-256", - AuthSource: deps.Configuration.MongoAuth, - Username: deps.Configuration.MongoUser, - Password: deps.Configuration.MongoPassword, - }) - - ticker := time.NewTicker(1 * time.Second) - timeoutExceeded := time.After(deps.Timeout) - - defer ticker.Stop() - - for { - select { - case <-ticker.C: - connection, err := mongo.Connect(ctx, connectionOptions) - if err == nil { - return connection.Database(deps.Configuration.MongoDatabase), nil - } - - log.Printf("failed to connect to db <%s>: %s", mongoURI.String(), err.Error()) - case <-timeoutExceeded: - return nil, fmt.Errorf("db connection <%s> failed after %d timeout", mongoURI.String(), deps.Timeout) - default: - time.Sleep(1 * time.Second) - } - } -} diff --git a/pkg/mongo/errors.go b/pkg/mongo/errors.go deleted file mode 100644 index 2e592f2..0000000 --- a/pkg/mongo/errors.go +++ /dev/null @@ -1,7 +0,0 @@ -package mongo - -import "errors" - -var ( - ErrEmptyArgs = errors.New("arguments are empty") -) From 9adc2f64659bd52e774ae5aecd06410d601bcce5 Mon Sep 17 00:00:00 2001 From: Pavel Date: Wed, 21 Feb 2024 17:51:29 +0300 Subject: [PATCH 37/66] update openapi --- docs/openapi.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index ba2a82d..244e4dc 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -379,12 +379,6 @@ components: threshold: type: integer description: Информация о бонусах - outdated: - type: boolean - offLimit: - type: boolean - delete: - type: boolean PromoCodeResponse: type: object From 4fa3e14d1d6edbc071851e4ec77c73c503fb9986 Mon Sep 17 00:00:00 2001 From: Pavel Date: Wed, 21 Feb 2024 17:57:41 +0300 Subject: [PATCH 38/66] cleaning openapi --- docs/openapi.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index f8ce1bf..344b00a 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -168,12 +168,6 @@ components: threshold: type: integer description: Информация о бонусах - outdated: - type: boolean - offLimit: - type: boolean - delete: - type: boolean PromoCodeResponse: type: object From 84e83b66dc7c45834d891a9e171d7038e86090be Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 22 Feb 2024 12:18:24 +0300 Subject: [PATCH 39/66] changes after review --- docs/openapi.yaml | 26 +++++++++++- .../promocode/promocode_controller.go | 5 +-- internal/models/bonus.go | 16 ++++++- internal/repository/promocode_repository.go | 42 +++++++++++++++---- 4 files changed, 77 insertions(+), 12 deletions(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 3a14988..249c2fe 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -324,6 +324,31 @@ components: delete: type: boolean description: Флаг удаления промокода + bonus: + type: object + properties: + privilege: + type: object + properties: + privilegeID: + type: string + description: Идентификатор привилегии, которую необходимо предоставить + amount: + type: integer + format: uint64 + description: Размер привилегии + discount: + type: object + properties: + layer: + type: integer + factor: + type: number + target: + type: string + threshold: + type: integer + description: Информация о бонусах required: - id GetPromoCodesListReq: @@ -331,7 +356,6 @@ components: required: - page - limit - - filter properties: page: type: integer diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index 783d4ea..dfb23fb 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -77,9 +77,8 @@ func (p *PromoCodeController) GetList(c *fiber.Ctx) error { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } - resp := models.GetPromoCodesListResp{ + return c.Status(fiber.StatusOK).JSON(models.GetPromoCodesListResp{ Count: count, Items: promoCodes, - } - return c.Status(fiber.StatusOK).JSON(resp) + }) } diff --git a/internal/models/bonus.go b/internal/models/bonus.go index 7a4dc9e..f066fa1 100644 --- a/internal/models/bonus.go +++ b/internal/models/bonus.go @@ -31,7 +31,7 @@ type PromoCode struct { } type ReqEditPromoCode struct { - ID string `json:"id" bson:"_id"` //айдишник промокода, который обновляем + ID string `json:"id" bson:"_id"` // айдишник промокода, который обновляем Description *string `json:"description,omitempty" bson:"description"` // описание, необходимое менеджеру в админке Greetings *string `json:"greetings,omitempty" bson:"greetings"` // текст, выдаваемый пользователю в ответ на активацию промокода @@ -39,6 +39,20 @@ type ReqEditPromoCode struct { ActivationCount *int64 `json:"activationCount,omitempty" bson:"activationCount"` // предел количества активаций промокода Delete *bool `json:"delete,omitempty" bson:"delete"` + + Bonus *struct { + Privilege *struct { + PrivilegeID string `json:"privilegeID,omitempty" bson:"privilegeID"` + Amount uint64 `json:"amount,omitempty" bson:"amount"` + } `json:"privilege,omitempty" bson:"privilege"` + + Discount *struct { + Layer int `json:"layer,omitempty" bson:"layer"` + Factor float64 `json:"factor,omitempty" bson:"factor"` + Target string `json:"target,omitempty" bson:"target"` + Threshold int64 `json:"threshold,omitempty" bson:"threshold"` + } `json:"discount,omitempty" bson:"discount"` + } `json:"bonus,omitempty" bson:"bonus"` } type GetPromoCodesListReqFilter struct { diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index 83944ef..2206d48 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -99,21 +99,49 @@ func (r *PromoCodeRepository) EditPromoCode(ctx context.Context, req *models.Req updateFields["delete"] = *req.Delete } + if req.Bonus != nil { + if req.Bonus.Privilege != nil { + if req.Bonus.Privilege.PrivilegeID != "" { + updateFields["bonus.privilege.privilegeID"] = req.Bonus.Privilege.PrivilegeID + } + if req.Bonus.Privilege.Amount != 0 { + updateFields["bonus.privilege.amount"] = req.Bonus.Privilege.Amount + } + } + if req.Bonus.Discount != nil { + if req.Bonus.Discount.Layer != 0 { + updateFields["bonus.discount.layer"] = req.Bonus.Discount.Layer + } + if req.Bonus.Discount.Factor != 0.0 { + updateFields["bonus.discount.factor"] = req.Bonus.Discount.Factor + } + if req.Bonus.Discount.Target != "" { + updateFields["bonus.discount.target"] = req.Bonus.Discount.Target + } + if req.Bonus.Discount.Threshold != 0 { + updateFields["bonus.discount.threshold"] = req.Bonus.Discount.Threshold + } + } + } + if len(updateFields) == 0 { return r.GetPromoCodeByID(ctx, promoCodeID) } update := bson.M{"$set": updateFields} - result, err := r.mdb.UpdateOne(ctx, bson.M{"_id": promoCodeID}, update) + options := options.FindOneAndUpdate().SetReturnDocument(options.After) + result := r.mdb.FindOneAndUpdate(ctx, bson.M{"_id": promoCodeID}, update, options) + if result.Err() != nil { + return nil, result.Err() + } + + var updatedPromoCode models.PromoCode + err = result.Decode(&updatedPromoCode) if err != nil { return nil, err } - if result.MatchedCount == 0 { - return nil, ErrPromoCodeNotFound - } - - return r.GetPromoCodeByID(ctx, promoCodeID) + return &updatedPromoCode, nil } func (r *PromoCodeRepository) GetPromoCodeByID(ctx context.Context, promoCodeID primitive.ObjectID) (*models.PromoCode, error) { @@ -164,7 +192,7 @@ func (r *PromoCodeRepository) GetPromoCodesList(ctx context.Context, req *models } defer cursor.Close(ctx) - var promoCodes = make([]models.PromoCode, 0) + var promoCodes = make([]models.PromoCode, 0, 10) for cursor.Next(ctx) { var p models.PromoCode if err := cursor.Decode(&p); err != nil { From a6f1bdfb683f95aab0d17080d8ca5854f8653e64 Mon Sep 17 00:00:00 2001 From: Pavel Date: Sun, 25 Feb 2024 15:00:05 +0300 Subject: [PATCH 40/66] change init routes in server --- .env | 2 +- internal/app/app.go | 56 +++++++++---------- internal/controller/promocode/route.go | 18 ++++++ .../recovery/recovery_controller.go | 4 ++ internal/controller/recovery/route.go | 14 +++++ internal/server/http/http_server.go | 54 +++++++++--------- 6 files changed, 90 insertions(+), 58 deletions(-) create mode 100644 internal/controller/promocode/route.go create mode 100644 internal/controller/recovery/route.go diff --git a/.env b/.env index 469a8ab..6e2c851 100644 --- a/.env +++ b/.env @@ -5,7 +5,7 @@ HTTP_PORT="8080" # MongoDB settings MONGO_HOST="127.0.0.1" -MONGO_PORT="27020" +MONGO_PORT="27021" MONGO_USER="test" MONGO_PASSWORD="test" MONGO_DB="admin" diff --git a/internal/app/app.go b/internal/app/app.go index 4dc2cb7..decb0dd 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -12,7 +12,6 @@ import ( "codeword/pkg/closer" "context" "errors" - "github.com/twmb/franz-go/pkg/kgo" "go.uber.org/zap" "time" ) @@ -42,26 +41,26 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { return err } - kafkaTariffClient, err := kgo.NewClient( - kgo.SeedBrokers(cfg.KafkaBrokers), - kgo.ConsumeResetOffset(kgo.NewOffset().AtStart()), - kgo.DefaultProduceTopic(cfg.KafkaTopic), - ) - if err != nil { - return err - } + //kafkaTariffClient, err := kgo.NewClient( + // kgo.SeedBrokers(cfg.KafkaBrokers), + // kgo.ConsumeResetOffset(kgo.NewOffset().AtStart()), + // kgo.DefaultProduceTopic(cfg.KafkaTopic), + //) + //if err != nil { + // return err + //} - discountRpcClient, err := initialize.DiscountGRPCClient(cfg.DiscountServiceAddress) - if err != nil { - logger.Error("failed to connect to discount service", zap.Error(err)) - return err - } - - brokers := initialize.NewBrokers(initialize.BrokersDeps{ - Logger: logger, - TariffClient: kafkaTariffClient, - Topic: cfg.KafkaTopic, - }) + //discountRpcClient, err := initialize.DiscountGRPCClient(cfg.DiscountServiceAddress) + //if err != nil { + // logger.Error("failed to connect to discount service", zap.Error(err)) + // return err + //} + // + //brokers := initialize.NewBrokers(initialize.BrokersDeps{ + // Logger: logger, + // TariffClient: kafkaTariffClient, + // Topic: cfg.KafkaTopic, + //}) rdb, err := initialize.Redis(ctx, cfg) encrypt := initialize.Encrypt(cfg) @@ -83,11 +82,11 @@ 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, + Logger: logger, + PromoCodeRepo: promoCodeRepo, + StatsRepo: statsRepo, + //Kafka: brokers.TariffProducer, + //DiscountClient: discountRpcClient, }) recoveryController := recovery.NewRecoveryController(logger, recoveryService, cfg.DefaultRedirectionURL) @@ -109,9 +108,8 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { go purgeWC.Start(ctx) server := httpserver.NewServer(httpserver.ServerConfig{ - Logger: logger, - RecoveryController: recoveryController, - PromoCodeController: promoCodeController, + Logger: logger, + Controllers: []httpserver.Controller{recoveryController, promoCodeController}, }) go func() { @@ -121,6 +119,8 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { } }() + server.ListRoutes() + shutdownGroup.Add(closer.CloserFunc(server.Shutdown)) shutdownGroup.Add(closer.CloserFunc(mdb.Client().Disconnect)) shutdownGroup.Add(closer.CloserFunc(recoveryWC.Stop)) diff --git a/internal/controller/promocode/route.go b/internal/controller/promocode/route.go new file mode 100644 index 0000000..e867821 --- /dev/null +++ b/internal/controller/promocode/route.go @@ -0,0 +1,18 @@ +package promocode + +import "github.com/gofiber/fiber/v2" + +func (p *PromoCodeController) Register(router fiber.Router) { + router.Post("/create", p.CreatePromoCode) + router.Put("/edit", p.EditPromoCode) + router.Post("/getList", p.GetList) + router.Post("/activate", p.Activate) + router.Delete("/:promocodeID", p.Delete) + router.Post("/fastlink", p.CreateFastLink) + router.Get("/stats", p.GetAllStats) + +} + +func (p *PromoCodeController) Name() string { + return "promocode" +} diff --git a/internal/controller/recovery/recovery_controller.go b/internal/controller/recovery/recovery_controller.go index 1957941..b120227 100644 --- a/internal/controller/recovery/recovery_controller.go +++ b/internal/controller/recovery/recovery_controller.go @@ -26,6 +26,10 @@ func NewRecoveryController(logger *zap.Logger, service *services.RecoveryService } } +func (r *RecoveryController) HandleLiveness(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) +} + func (r *RecoveryController) HandlePingDB(c *fiber.Ctx) error { startTime := time.Now() if err := r.service.Ping(c.Context()); err != nil { diff --git a/internal/controller/recovery/route.go b/internal/controller/recovery/route.go new file mode 100644 index 0000000..c2d6431 --- /dev/null +++ b/internal/controller/recovery/route.go @@ -0,0 +1,14 @@ +package recovery + +import "github.com/gofiber/fiber/v2" + +func (r *RecoveryController) Register(router fiber.Router) { + router.Get("/liveness", r.HandleLiveness) + router.Get("/readiness", r.HandlePingDB) + router.Post("/recover", r.HandleRecoveryRequest) + router.Get("/recover/:sign", r.HandleRecoveryLink) +} + +func (r *RecoveryController) Name() string { + return "" +} diff --git a/internal/server/http/http_server.go b/internal/server/http/http_server.go index 6800a91..4bb0aed 100644 --- a/internal/server/http/http_server.go +++ b/internal/server/http/http_server.go @@ -1,34 +1,30 @@ package http import ( - "codeword/internal/controller/promocode" - "codeword/internal/controller/recovery" "context" + "fmt" "github.com/gofiber/fiber/v2" "go.uber.org/zap" ) type ServerConfig struct { - Logger *zap.Logger - RecoveryController *recovery.RecoveryController - PromoCodeController *promocode.PromoCodeController + Logger *zap.Logger + Controllers []Controller } type Server struct { - Logger *zap.Logger - RecoveryController *recovery.RecoveryController - PromoCodeController *promocode.PromoCodeController - app *fiber.App + Logger *zap.Logger + Controllers []Controller + app *fiber.App } func NewServer(config ServerConfig) *Server { app := fiber.New() s := &Server{ - Logger: config.Logger, - RecoveryController: config.RecoveryController, - PromoCodeController: config.PromoCodeController, - app: app, + Logger: config.Logger, + Controllers: config.Controllers, + app: app, } s.registerRoutes() @@ -49,22 +45,22 @@ func (s *Server) Shutdown(ctx context.Context) error { } func (s *Server) registerRoutes() { - s.app.Get("/liveness", s.handleLiveness) - s.app.Get("/readiness", s.RecoveryController.HandlePingDB) - - s.app.Post("/recover", s.RecoveryController.HandleRecoveryRequest) - s.app.Get("/recover/:sign", s.RecoveryController.HandleRecoveryLink) - - s.app.Post("/promocode/create", s.PromoCodeController.CreatePromoCode) - s.app.Put("/promocode/edit", s.PromoCodeController.EditPromoCode) - s.app.Post("/promocode/getList", s.PromoCodeController.GetList) - 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 + for _, c := range s.Controllers { + router := s.app.Group(c.Name()) + c.Register(router) + } } -func (s *Server) handleLiveness(c *fiber.Ctx) error { - return c.SendStatus(fiber.StatusOK) +type Controller interface { + Register(router fiber.Router) + Name() string +} + +func (s *Server) ListRoutes() { + fmt.Println("Registered routes:") + for _, stack := range s.app.Stack() { + for _, route := range stack { + fmt.Printf("%s %s\n", route.Method, route.Path) + } + } } From d125d4f13e957443e2b5b0319d9027c80bb09781 Mon Sep 17 00:00:00 2001 From: Pavel Date: Sun, 25 Feb 2024 15:02:15 +0300 Subject: [PATCH 41/66] replace // from app --- internal/app/app.go | 49 +++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index decb0dd..4e59040 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -12,6 +12,7 @@ import ( "codeword/pkg/closer" "context" "errors" + "github.com/twmb/franz-go/pkg/kgo" "go.uber.org/zap" "time" ) @@ -41,26 +42,26 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { return err } - //kafkaTariffClient, err := kgo.NewClient( - // kgo.SeedBrokers(cfg.KafkaBrokers), - // kgo.ConsumeResetOffset(kgo.NewOffset().AtStart()), - // kgo.DefaultProduceTopic(cfg.KafkaTopic), - //) - //if err != nil { - // return err - //} + kafkaTariffClient, err := kgo.NewClient( + kgo.SeedBrokers(cfg.KafkaBrokers), + kgo.ConsumeResetOffset(kgo.NewOffset().AtStart()), + kgo.DefaultProduceTopic(cfg.KafkaTopic), + ) + if err != nil { + return err + } - //discountRpcClient, err := initialize.DiscountGRPCClient(cfg.DiscountServiceAddress) - //if err != nil { - // logger.Error("failed to connect to discount service", zap.Error(err)) - // return err - //} - // - //brokers := initialize.NewBrokers(initialize.BrokersDeps{ - // Logger: logger, - // TariffClient: kafkaTariffClient, - // Topic: cfg.KafkaTopic, - //}) + discountRpcClient, err := initialize.DiscountGRPCClient(cfg.DiscountServiceAddress) + if err != nil { + logger.Error("failed to connect to discount service", zap.Error(err)) + return err + } + + brokers := initialize.NewBrokers(initialize.BrokersDeps{ + Logger: logger, + TariffClient: kafkaTariffClient, + Topic: cfg.KafkaTopic, + }) rdb, err := initialize.Redis(ctx, cfg) encrypt := initialize.Encrypt(cfg) @@ -82,11 +83,11 @@ 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, + Logger: logger, + PromoCodeRepo: promoCodeRepo, + StatsRepo: statsRepo, + Kafka: brokers.TariffProducer, + DiscountClient: discountRpcClient, }) recoveryController := recovery.NewRecoveryController(logger, recoveryService, cfg.DefaultRedirectionURL) From b9fb8015e3f68df4bed361588336bacab84c56c6 Mon Sep 17 00:00:00 2001 From: Pavel Date: Sun, 3 Mar 2024 10:34:19 +0300 Subject: [PATCH 42/66] fix return err from redis init --- internal/app/app.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/app/app.go b/internal/app/app.go index 4e59040..695dc12 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -64,6 +64,11 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { }) rdb, err := initialize.Redis(ctx, cfg) + if err != nil { + logger.Error("failed to connect to redis db", zap.Error(err)) + return err + } + encrypt := initialize.Encrypt(cfg) promoCodeRepo := repository.NewPromoCodeRepository(mdb.Collection("promoCodes")) From aa8c326b2530aca3ddbb307bce3cee0c0f2f32a4 Mon Sep 17 00:00:00 2001 From: Pavel Date: Sun, 3 Mar 2024 13:13:13 +0300 Subject: [PATCH 43/66] fix add auth midlleware --- .env | 22 ++++- deployment/local/docker-compose.yaml | 5 +- go.mod | 1 + go.sum | 2 + internal/app/app.go | 6 +- .../promocode/promocode_controller.go | 36 +++++--- internal/initialize/config.go | 4 + internal/models/auth.go | 3 + internal/models/bonus.go | 1 - internal/services/promocode_service.go | 8 +- tests/e2e/promo_test.go | 30 +++---- tests/helpers/jwt.go | 38 ++++++++ utils/authenticator.go | 46 ++++++++++ utils/jwt.go | 89 +++++++++++++++++++ 14 files changed, 255 insertions(+), 36 deletions(-) create mode 100644 tests/helpers/jwt.go create mode 100644 utils/authenticator.go create mode 100644 utils/jwt.go diff --git a/.env b/.env index 6e2c851..ccc52be 100644 --- a/.env +++ b/.env @@ -5,7 +5,7 @@ HTTP_PORT="8080" # MongoDB settings MONGO_HOST="127.0.0.1" -MONGO_PORT="27021" +MONGO_PORT="27020" MONGO_USER="test" MONGO_PASSWORD="test" MONGO_DB="admin" @@ -21,6 +21,26 @@ PUBLIC_CURVE_KEY="-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAEbnIvjIMle4rqVol6K PRIVATE_CURVE_KEY="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIKn0BKwF3vZvODgWAnUIwQhd8de5oZhY48gc23EWfrfs\n-----END PRIVATE KEY-----" +JWT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- + MIICWwIBAAKBgHgnvr7O2tiApjJfid1orFnIGm6980fZp+Lpbjo+NC/0whMFga2B + iw5b1G2Q/B2u0tpO1Fs/E8z7Lv1nYfr5jx2S8x6BdA4TS2kB9Kf0wn0+7wSlyikH + oKhbtzwXHZl17GsyEi6wHnsqNBSauyIWhpha8i+Y+3GyaOY536H47qyXAgMBAAEC + gYAOphnVPXbk6lpYzdkLC1Xn5EOEuNfOLLURLxBnPWozZo26r/Mtahu/9mYhrYlv + PP8r6mxta3VIil8iOdZyOLa/4d1LPd+UehgEXIJEiYXLtn7RS5eUnoPuQxssfs1k + OWjdN8p6SzppleegFTvGRX4KM3cDLfSphOk8JuBCrpSSYQJBAOdqizTSrdKMTuVe + c7Jk1JOJkyFuFs+N5zeryyeFGH7IpRdWy0rkWMxIUAi8Ap1vYVBPHv4tDOo3sy5X + VLc/knkCQQCE62pg+0TmsrhO/2Pgog6MLBkzlzXYMRp/01HbmznwYF+ejfPnzLkz + hnUlxRUNK3lhXM/7H6oAjvqF2R72u/OPAkEAterkmdbQfEZ+MwNoEiH/lie9OLdx + SSI1VGdBYcTYN7qFRW6eizYstBJYkDU0HQ0Uw+we4hMKJwk4W0KdvxxDiQJAeqlB + V1QqBneBbK10PzVuFV8QtrJhJyxRVwrtbKq38iMNuqUnI4+ijXEUpJFWVvv6nKXo + 7McQvEk12dU/JNTX8wJAOlAtSNjp9tVwpMpC0w2St1eKc1L2SknjeohA5ldoBz8sGeZsPhTU3eHSD1neAZXLKN5K68z3zFBr20ubY9nyLw== + -----END RSA PRIVATE KEY-----" + +JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHgnvr7O2tiApjJfid1orFnIGm6980fZp+Lpbjo+NC/0whMFga2Biw5b1G2Q/B2u0tpO1Fs/E8z7Lv1nYfr5jx2S8x6BdA4TS2kB9Kf0wn0+7wSlyikHoKhbtzwXHZl17GsyEi6wHnsqNBSauyIWhpha8i+Y+3GyaOY536H47qyXAgMBAAE=\n-----END PUBLIC KEY-----" + +JWT_ISSUER="pena-auth-service" + +JWT_AUDIENCE="pena" # SIGN_SECRET="group" SIGN_SECRET="secret" diff --git a/deployment/local/docker-compose.yaml b/deployment/local/docker-compose.yaml index 249e549..d77ec4a 100644 --- a/deployment/local/docker-compose.yaml +++ b/deployment/local/docker-compose.yaml @@ -6,8 +6,9 @@ services: ports: - "27020:27017" environment: - - MONGO_INITDB_ROOT_USERNAME=${MONGO_USER} - - MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD} + - MONGO_INITDB_ROOT_USERNAME=test + - MONGO_INITDB_ROOT_PASSWORD=test + - MONGO_INITDB_AUTH_MECHANISM=SCRAM-SHA-1 volumes: - mongo_data:/data/db diff --git a/go.mod b/go.mod index 98a0a6c..fc04dda 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/caarlos0/env/v8 v8.0.0 github.com/go-redis/redis/v8 v8.11.5 github.com/gofiber/fiber/v2 v2.51.0 + github.com/golang-jwt/jwt/v5 v5.2.0 github.com/joho/godotenv v1.5.1 github.com/pioz/faker v1.7.3 github.com/rs/xid v1.5.0 diff --git a/go.sum b/go.sum index 6f4b4c4..523b7ea 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/gofiber/fiber/v2 v2.51.0 h1:JNACcZy5e2tGApWB2QrRpenTWn0fq0hkFm6k0C86gKQ= github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U= +github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= +github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= diff --git a/internal/app/app.go b/internal/app/app.go index 695dc12..119d910 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -10,6 +10,7 @@ import ( "codeword/internal/worker/purge_worker" "codeword/internal/worker/recovery_worker" "codeword/pkg/closer" + "codeword/utils" "context" "errors" "github.com/twmb/franz-go/pkg/kgo" @@ -95,8 +96,11 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { DiscountClient: discountRpcClient, }) + jwtUtil := utils.NewJWT(&cfg) + authMiddleware := utils.NewAuthenticator(jwtUtil) + recoveryController := recovery.NewRecoveryController(logger, recoveryService, cfg.DefaultRedirectionURL) - promoCodeController := promocode.NewPromoCodeController(logger, promoService) + promoCodeController := promocode.NewPromoCodeController(promocode.Deps{Logger: logger, PromoCodeService: promoService, AuthMiddleware: authMiddleware}) recoveryWC := recovery_worker.NewRecoveryWC(recovery_worker.Deps{ Logger: logger, diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index ad799a3..a83c468 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -9,15 +9,23 @@ import ( "go.uber.org/zap" ) +type Deps struct { + Logger *zap.Logger + PromoCodeService *services.PromoCodeService + AuthMiddleware func(*fiber.Ctx) error +} + type PromoCodeController struct { logger *zap.Logger promoCodeService *services.PromoCodeService + authMiddleware func(*fiber.Ctx) error } -func NewPromoCodeController(logger *zap.Logger, promoCodeService *services.PromoCodeService) *PromoCodeController { +func NewPromoCodeController(deps Deps) *PromoCodeController { return &PromoCodeController{ - logger: logger, - promoCodeService: promoCodeService, + logger: deps.Logger, + promoCodeService: deps.PromoCodeService, + authMiddleware: deps.AuthMiddleware, } } @@ -89,20 +97,27 @@ func (p *PromoCodeController) GetList(c *fiber.Ctx) error { } func (p *PromoCodeController) Activate(c *fiber.Ctx) error { + err := p.authMiddleware(c) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err}) + } + + userID := c.Locals(models.AuthJWTDecodedUserIDKey).(string) + + if userID == "" { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "failed to get jwt payload"}) + } + var req models.ActivateReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) } - if req.UserID == "" { - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "userid is required"}) - } - if req.Codeword == "" && req.FastLink == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "codeword or fastlink is required"}) } - greetings, err := p.promoCodeService.ActivatePromo(c.Context(), &req) + greetings, err := p.promoCodeService.ActivatePromo(c.Context(), &req, userID) if err != nil { p.logger.Error("Failed to activate promocode", zap.Error(err)) @@ -113,10 +128,7 @@ func (p *PromoCodeController) Activate(c *fiber.Ctx) error { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } - resp := models.ActivateResp{ - Greetings: greetings, - } - return c.Status(fiber.StatusOK).JSON(resp) + return c.Status(fiber.StatusOK).JSON(models.ActivateResp{Greetings: greetings}) } func (p *PromoCodeController) Delete(c *fiber.Ctx) error { diff --git a/internal/initialize/config.go b/internal/initialize/config.go index c0c0621..23c78b1 100644 --- a/internal/initialize/config.go +++ b/internal/initialize/config.go @@ -35,6 +35,10 @@ type Config struct { KafkaTopic string `env:"KAFKA_TOPIC_TARIFF"` DiscountServiceAddress string `env:"DISCOUNT_ADDRESS"` RecoveryUrl string `env:"RECOVERY_URL"` + PrivateKey string `env:"JWT_PRIVATE_KEY"` + PublicKey string `env:"JWT_PUBLIC_KEY,required"` + Issuer string `env:"JWT_ISSUER,required"` + Audience string `env:"JWT_AUDIENCE,required"` } func LoadConfig() (*Config, error) { diff --git a/internal/models/auth.go b/internal/models/auth.go index a159a74..204def2 100644 --- a/internal/models/auth.go +++ b/internal/models/auth.go @@ -9,3 +9,6 @@ type RefreshResponse struct { AccessToken string `json:"accessToken"` RefreshToken string `json:"refreshToken"` } + +const AuthJWTDecodedUserIDKey = "userID" +const AuthJWTDecodedAccessTokenKey = "access-token" diff --git a/internal/models/bonus.go b/internal/models/bonus.go index 31bfc8d..7ac63cd 100644 --- a/internal/models/bonus.go +++ b/internal/models/bonus.go @@ -59,7 +59,6 @@ type GetPromoCodesListResp struct { } type ActivateReq struct { - UserID string `json:"userID"` // для кого активировать нужно для кафки Codeword string `json:"codeword"` FastLink string `json:"fastLink"` } diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index cca3a5e..ddcdd58 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -86,14 +86,14 @@ func (s *PromoCodeService) GetPromoCodesList(ctx context.Context, req *models.Ge // разделяется ли она или они всегда вместе, если разделяются то что-то из этого может быть пустым либо все заполеннное, // соответсвенно надо сделать соответствующие проверки до записи в кафку и до отправки в дискаунт сервис -func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.ActivateReq) (string, error) { +func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.ActivateReq, userID string) (string, error) { promoCode, err := s.promoCodeRepo.ActivatePromo(ctx, req) if err != nil { s.logger.Error("Failed to activate promocode", zap.Error(err)) return "", err } - err = s.statsRepo.UpdateStatistics(ctx, promoCode.ID.Hex(), req.UserID) + err = s.statsRepo.UpdateStatistics(ctx, promoCode.ID.Hex(), userID) if err != nil { s.logger.Error("Failed add in stats", zap.Error(err)) return "", err @@ -118,7 +118,7 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa Deleted: promoCode.Delete, CreatedAt: promoCode.CreatedAt, } - if err := s.kafka.Send(ctx, req.UserID, fakeTariff); err != nil { + if err := s.kafka.Send(ctx, userID, fakeTariff); err != nil { s.logger.Error("Failed to send fake tariff to Kafka", zap.Error(err)) return "", err } @@ -130,7 +130,7 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa Description: "", Condition: &discount.DiscountCondition{ Coupon: &promoCode.Codeword, - User: &req.UserID, + User: &userID, }, Target: &discount.DiscountCalculationTarget{ Factor: promoCode.Bonus.Discount.Factor, diff --git a/tests/e2e/promo_test.go b/tests/e2e/promo_test.go index c85b866..8de7845 100644 --- a/tests/e2e/promo_test.go +++ b/tests/e2e/promo_test.go @@ -2,6 +2,7 @@ package e2e import ( "codeword/internal/models" + "codeword/tests/helpers" "encoding/json" "fmt" "github.com/gofiber/fiber/v2" @@ -420,24 +421,30 @@ func TestGetPromoCodesList(t *testing.T) { // ActivatePromoCode func TestActivatePromoCode(t *testing.T) { client := fiber.AcquireClient() - + jwtUtil := helpers.InitializeJWT() + token, tokenErr := jwtUtil.Create(ExampleUserID) + fmt.Println(token) + if isNoError := assert.NoError(t, tokenErr); !isNoError { + return + } t.Run("ActivatePromoCode-success codeword", func(t *testing.T) { reqBody := models.ActivateReq{ - UserID: ExampleUserID, Codeword: "example", } reqJSON, _ := json.Marshal(reqBody) - req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Body(reqJSON) + req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Set("Authorization", "Bearer "+token).Body(reqJSON) statusCode, resBody, errs := req.Bytes() if len(errs) != 0 { assert.NoError(t, errs[0]) } - assert.Equal(t, fiber.StatusOK, statusCode) + fmt.Println(string(resBody)) + assert.Equal(t, fiber.StatusOK, statusCode) + fmt.Println(statusCode) var response models.ActivateResp err := json.Unmarshal(resBody, &response) assert.NoError(t, err) @@ -446,13 +453,12 @@ func TestActivatePromoCode(t *testing.T) { t.Run("ActivatePromoCode-success fastLink", func(t *testing.T) { reqBody := models.ActivateReq{ - UserID: ExampleUserID, FastLink: fastLink, } reqJSON, _ := json.Marshal(reqBody) - req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Body(reqJSON) + req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Set("Authorization", "Bearer "+token).Body(reqJSON) statusCode, resBody, errs := req.Bytes() if len(errs) != 0 { @@ -473,7 +479,7 @@ func TestActivatePromoCode(t *testing.T) { } reqJSON, _ := json.Marshal(reqBody) - req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Body(reqJSON) + req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Set("Authorization", "Bearer "+token).Body(reqJSON) statusCode, resBody, errs := req.Bytes() if len(errs) != 0 { @@ -489,13 +495,8 @@ func TestActivatePromoCode(t *testing.T) { }) t.Run("ActivatePromoCode-missing codeword and fastlink", func(t *testing.T) { - reqBody := models.ActivateReq{ - UserID: ExampleUserID, - } - reqJSON, _ := json.Marshal(reqBody) - - req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Body(reqJSON) + req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Set("Authorization", "Bearer "+token).Body(nil) statusCode, resBody, errs := req.Bytes() if len(errs) != 0 { @@ -512,13 +513,12 @@ func TestActivatePromoCode(t *testing.T) { t.Run("ActivatePromoCode-promocode not found", func(t *testing.T) { reqBody := models.ActivateReq{ - UserID: ExampleUserID, Codeword: "none", } reqJSON, _ := json.Marshal(reqBody) - req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Body(reqJSON) + req := client.Post(BaseUrl+"/promocode/activate").Set("Content-Type", "application/json").Set("Authorization", "Bearer "+token).Body(reqJSON) statusCode, resBody, errs := req.Bytes() if len(errs) != 0 { assert.Error(t, errs[0]) diff --git a/tests/helpers/jwt.go b/tests/helpers/jwt.go new file mode 100644 index 0000000..a0134ab --- /dev/null +++ b/tests/helpers/jwt.go @@ -0,0 +1,38 @@ +package helpers + +import ( + "codeword/internal/initialize" + "codeword/utils" + "strings" +) + +func InitializeJWT() *utils.JWT { + publicKey := strings.Replace(`-----BEGIN PUBLIC KEY----- + MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHgnvr7O2tiApjJfid1orFnIGm69 + 80fZp+Lpbjo+NC/0whMFga2Biw5b1G2Q/B2u0tpO1Fs/E8z7Lv1nYfr5jx2S8x6B + dA4TS2kB9Kf0wn0+7wSlyikHoKhbtzwXHZl17GsyEi6wHnsqNBSauyIWhpha8i+Y + +3GyaOY536H47qyXAgMBAAE= + -----END PUBLIC KEY-----`, "\t", "", -1) + + privateKey := strings.Replace(`-----BEGIN RSA PRIVATE KEY----- + MIICWwIBAAKBgHgnvr7O2tiApjJfid1orFnIGm6980fZp+Lpbjo+NC/0whMFga2B + iw5b1G2Q/B2u0tpO1Fs/E8z7Lv1nYfr5jx2S8x6BdA4TS2kB9Kf0wn0+7wSlyikH + oKhbtzwXHZl17GsyEi6wHnsqNBSauyIWhpha8i+Y+3GyaOY536H47qyXAgMBAAEC + gYAOphnVPXbk6lpYzdkLC1Xn5EOEuNfOLLURLxBnPWozZo26r/Mtahu/9mYhrYlv + PP8r6mxta3VIil8iOdZyOLa/4d1LPd+UehgEXIJEiYXLtn7RS5eUnoPuQxssfs1k + OWjdN8p6SzppleegFTvGRX4KM3cDLfSphOk8JuBCrpSSYQJBAOdqizTSrdKMTuVe + c7Jk1JOJkyFuFs+N5zeryyeFGH7IpRdWy0rkWMxIUAi8Ap1vYVBPHv4tDOo3sy5X + VLc/knkCQQCE62pg+0TmsrhO/2Pgog6MLBkzlzXYMRp/01HbmznwYF+ejfPnzLkz + hnUlxRUNK3lhXM/7H6oAjvqF2R72u/OPAkEAterkmdbQfEZ+MwNoEiH/lie9OLdx + SSI1VGdBYcTYN7qFRW6eizYstBJYkDU0HQ0Uw+we4hMKJwk4W0KdvxxDiQJAeqlB + V1QqBneBbK10PzVuFV8QtrJhJyxRVwrtbKq38iMNuqUnI4+ijXEUpJFWVvv6nKXo + 7McQvEk12dU/JNTX8wJAOlAtSNjp9tVwpMpC0w2St1eKc1L2SknjeohA5ldoBz8sGeZsPhTU3eHSD1neAZXLKN5K68z3zFBr20ubY9nyLw== + -----END RSA PRIVATE KEY-----`, "\t", "", -1) + + return utils.NewJWT(&initialize.Config{ + PrivateKey: privateKey, + PublicKey: publicKey, + Audience: "pena", + Issuer: "pena-auth-service", + }) +} diff --git a/utils/authenticator.go b/utils/authenticator.go new file mode 100644 index 0000000..193fcd4 --- /dev/null +++ b/utils/authenticator.go @@ -0,0 +1,46 @@ +package utils + +import ( + "codeword/internal/models" + "fmt" + "strings" + + "github.com/gofiber/fiber/v2" +) + +const ( + prefix = "Bearer " +) + +func NewAuthenticator(jwtUtil *JWT) func(*fiber.Ctx) error { + return func(c *fiber.Ctx) error { + if jwtUtil == nil { + return fmt.Errorf("jwt util is nil") + } + + if err := authenticate(c, jwtUtil); err != nil { + return fmt.Errorf("authentication error:%d", err) + } + + return nil + } +} + +func authenticate(c *fiber.Ctx, jwtUtil *JWT) error { + authHeader := c.Get("Authorization") + if authHeader == "" || !strings.HasPrefix(authHeader, prefix) { + return fmt.Errorf("failed to parse jws from request header: %s", authHeader) + } + + jws := strings.TrimPrefix(authHeader, prefix) + + userID, validateErr := jwtUtil.Validate(jws) + if validateErr != nil { + return validateErr + } + + c.Locals(models.AuthJWTDecodedUserIDKey, userID) + c.Locals(models.AuthJWTDecodedAccessTokenKey, jws) + + return nil +} diff --git a/utils/jwt.go b/utils/jwt.go new file mode 100644 index 0000000..4584dee --- /dev/null +++ b/utils/jwt.go @@ -0,0 +1,89 @@ +package utils + +import ( + "codeword/internal/initialize" + "errors" + "fmt" + "github.com/golang-jwt/jwt/v5" + "time" +) + +type JWT struct { + privateKey []byte + publicKey []byte + algorithm *jwt.SigningMethodRSA + expiresIn time.Duration + issuer string + audience string +} + +func NewJWT(configuration *initialize.Config) *JWT { + return &JWT{ + privateKey: []byte(configuration.PrivateKey), + publicKey: []byte(configuration.PublicKey), + issuer: configuration.Issuer, + audience: configuration.Audience, + algorithm: jwt.SigningMethodRS256, + expiresIn: 15 * time.Minute, + } +} + +func (j *JWT) Create(id string) (string, error) { + privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(j.privateKey) + if err != nil { + return "", fmt.Errorf("failed to parse private key on of : %w", err) + } + + now := time.Now().UTC() + + claims := jwt.MapClaims{ + "id": id, + "exp": now.Add(j.expiresIn).Unix(), + "aud": j.audience, + "iss": j.issuer, + } + + token, err := jwt.NewWithClaims(j.algorithm, claims).SignedString(privateKey) + if err != nil { + return "", fmt.Errorf("failed to sing on of : %w", err) + } + + return token, nil +} + +func (j *JWT) Validate(tokenString string) (string, error) { + key, err := jwt.ParseRSAPublicKeyFromPEM(j.publicKey) + if err != nil { + return "", fmt.Errorf("failed to parse rsa public key on of : %w", err) + } + + parseCallback := func(token *jwt.Token) (any, error) { + if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { + return nil, fmt.Errorf("unexpected signing method: %s", token.Header["alg"]) + } + + return key, nil + } + + token, err := jwt.Parse( + tokenString, + parseCallback, + jwt.WithAudience(j.audience), + jwt.WithIssuer(j.issuer), + ) + if err != nil { + return "", fmt.Errorf("failed to parse jwt token on of : %w", err) + } + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok || !token.Valid { + return "", errors.New("token is invalid on of ") + } + + data, ok := claims["id"].(string) + if !ok { + return "", errors.New("data is empty or not a string on of ") + } + + return data, nil +} From 13110b9e4d4a2fb72f608ac68ac9aff988adc635 Mon Sep 17 00:00:00 2001 From: Pavel Date: Sun, 3 Mar 2024 13:35:38 +0300 Subject: [PATCH 44/66] fix add ping to kafka --- internal/app/app.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/app/app.go b/internal/app/app.go index 119d910..9e082d9 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -52,6 +52,11 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { return err } + err = kafkaTariffClient.Ping(ctx) + if err != nil { + return err + } + discountRpcClient, err := initialize.DiscountGRPCClient(cfg.DiscountServiceAddress) if err != nil { logger.Error("failed to connect to discount service", zap.Error(err)) From cbd82e8779794bea4e525e97b270560ab1fdd663 Mon Sep 17 00:00:00 2001 From: Pavel Date: Sun, 3 Mar 2024 13:49:46 +0300 Subject: [PATCH 45/66] comented grpc withblock --- internal/initialize/grpc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/initialize/grpc.go b/internal/initialize/grpc.go index 5559bf3..692ca21 100644 --- a/internal/initialize/grpc.go +++ b/internal/initialize/grpc.go @@ -13,7 +13,7 @@ func DiscountGRPCClient(address string) (discount.DiscountServiceClient, error) options := []grpc.DialOption{ grpc.WithInsecure(), - grpc.WithBlock(), + //grpc.WithBlock(), } conn, err := grpc.DialContext(ctx, address, options...) From ca0e4f77081dc2d01e8df4d842d7f7d089089d1d Mon Sep 17 00:00:00 2001 From: Pavel Date: Sun, 3 Mar 2024 17:18:18 +0300 Subject: [PATCH 46/66] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D1=80=D0=B0=D0=B7=D0=B3=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B2=20=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=B1=D0=BA=D0=B0=D1=85,=20=D1=83=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=82=D1=80=D0=B0=D0=BD=D0=B7=D0=B0=D0=BA=D1=86?= =?UTF-8?q?=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../promocode/promocode_controller.go | 14 ++- internal/repository/errors.go | 11 ++- internal/repository/promocode_repository.go | 85 ++++++++----------- internal/repository/promocode_stats.go | 15 +++- internal/services/promocode_service.go | 27 ++++++ tests/e2e/promo_test.go | 1 - 6 files changed, 94 insertions(+), 59 deletions(-) diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index a83c468..b8192ee 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -103,7 +103,6 @@ func (p *PromoCodeController) Activate(c *fiber.Ctx) error { } userID := c.Locals(models.AuthJWTDecodedUserIDKey).(string) - if userID == "" { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "failed to get jwt payload"}) } @@ -121,11 +120,18 @@ func (p *PromoCodeController) Activate(c *fiber.Ctx) error { if err != nil { p.logger.Error("Failed to activate promocode", zap.Error(err)) - if errors.Is(err, repository.ErrPromoCodeNotFound) { + switch { + case errors.Is(err, repository.ErrPromoCodeNotFound): return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "PromoCode not found"}) + case errors.Is(err, repository.ErrPromoCodeAlreadyActivated): + return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "PromoCode already activated"}) + case errors.Is(err, repository.ErrPromoCodeExpired): + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) + case errors.Is(err, repository.ErrPromoCodeExhausted): + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "PromoCode exhausted"}) + default: + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } - - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } return c.Status(fiber.StatusOK).JSON(models.ActivateResp{Greetings: greetings}) diff --git a/internal/repository/errors.go b/internal/repository/errors.go index d61a2da..c941611 100644 --- a/internal/repository/errors.go +++ b/internal/repository/errors.go @@ -3,8 +3,11 @@ package repository import "errors" var ( - ErrPromoUserNotFound = errors.New("user not found") - ErrAlreadyReported = errors.New("already reported") - ErrDuplicateCodeword = errors.New("duplicate codeword") - ErrPromoCodeNotFound = errors.New("promo code not found") + ErrPromoUserNotFound = errors.New("user not found") + ErrAlreadyReported = errors.New("already reported") + ErrDuplicateCodeword = errors.New("duplicate codeword") + ErrPromoCodeNotFound = errors.New("promo code not found") + ErrPromoCodeExpired = errors.New("promo code is expired") + ErrPromoCodeExhausted = errors.New("promo code is exhausted") + ErrPromoCodeAlreadyActivated = errors.New("promo code is already activated") ) diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index 354006c..47826f5 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -187,63 +187,52 @@ func (r *PromoCodeRepository) GetPromoCodesList(ctx context.Context, req *models } func (r *PromoCodeRepository) ActivatePromo(ctx context.Context, req *models.ActivateReq) (*models.PromoCode, error) { - session, err := r.mdb.Database().Client().StartSession() - if err != nil { - return nil, err - } - defer session.EndSession(ctx) - var promoCode models.PromoCode - transactionErr := mongo.WithSession(ctx, session, func(sc mongo.SessionContext) error { - var filter bson.M - - if req.Codeword != "" { - filter = bson.M{ - "codeword": req.Codeword, - "delete": false, - "outdated": false, - "offLimit": false, - "activationCount": bson.M{"$gt": 0}, - "dueTo": bson.M{"$gt": time.Now().Unix()}, - } - } else if req.FastLink != "" { - filter = bson.M{ - "fastLinks": req.FastLink, - "delete": false, - "outdated": false, - "offLimit": false, - "activationCount": bson.M{"$gt": 0}, - "dueTo": bson.M{"$gt": time.Now().Unix()}, - } + var filter bson.M + if req.Codeword != "" { + filter = bson.M{ + "codeword": req.Codeword, } - - update := bson.M{ - "$inc": bson.M{"activationCount": -1}, + } else if req.FastLink != "" { + filter = bson.M{ + "fastLinks": req.FastLink, } - opts := options.FindOneAndUpdate().SetReturnDocument(options.After) - - var updatedPromoCode models.PromoCode - err := r.mdb.FindOneAndUpdate(sc, filter, update, opts).Decode(&updatedPromoCode) - if err != nil { - if err == mongo.ErrNoDocuments { - return ErrPromoCodeNotFound - } - return err - } - - promoCode = updatedPromoCode - - return nil - }) - - if transactionErr != nil { - return nil, transactionErr } + opts := options.FindOneAndUpdate().SetReturnDocument(options.After) + + err := r.mdb.FindOneAndUpdate(ctx, filter, bson.M{"$inc": bson.M{"activationCount": -1}}, opts).Decode(&promoCode) + if err != nil { + if err == mongo.ErrNoDocuments { + return nil, ErrPromoCodeNotFound + } + return nil, err + } + + if promoCode.ActivationCount <= 0 && promoCode.DueTo > time.Now().Unix() { + if !promoCode.OffLimit { + update := bson.M{"$set": bson.M{"offLimit": true}} + _, err := r.mdb.UpdateOne(ctx, filter, update) + if err != nil { + return nil, err + } + } + } return &promoCode, nil } +func (r *PromoCodeRepository) IncreaseActivationCount(ctx context.Context, promoCodeID primitive.ObjectID) error { + filter := bson.M{"_id": promoCodeID} + update := bson.M{"$inc": bson.M{"activationCount": 1}} + _, err := r.mdb.UpdateOne(ctx, filter, update) + if err != nil { + return err + } + + return nil +} + func (r *PromoCodeRepository) DeletePromoCode(ctx context.Context, promoCodeID string) error { id, err := primitive.ObjectIDFromHex(promoCodeID) if err != nil { diff --git a/internal/repository/promocode_stats.go b/internal/repository/promocode_stats.go index 1f0a215..fe61c2b 100644 --- a/internal/repository/promocode_stats.go +++ b/internal/repository/promocode_stats.go @@ -19,15 +19,26 @@ func NewStatsRepository(deps Deps) *StatsRepository { } func (r *StatsRepository) UpdateStatistics(ctx context.Context, key, userID string) error { + filter := bson.M{"_id": key, "usageCount." + userID: bson.M{"$exists": true}} + + count, err := r.mdb.CountDocuments(ctx, filter) + if err != nil { + return err + } + + if count > 0 { + return ErrPromoCodeAlreadyActivated + } + 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} + filter = bson.M{"_id": key} - _, err := r.mdb.UpdateOne(ctx, filter, update, opts) + _, err = r.mdb.UpdateOne(ctx, filter, update, opts) return err } diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index ddcdd58..a5670e7 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -4,11 +4,14 @@ import ( "codeword/internal/kafka/tariff" "codeword/internal/models" "codeword/internal/proto/discount" + "codeword/internal/repository" "codeword/internal/utils/genID" "context" + "errors" "fmt" "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" + "time" ) type PromoCodeRepository interface { @@ -19,6 +22,7 @@ type PromoCodeRepository interface { DeletePromoCode(ctx context.Context, promoCodeID string) error GetPromoCodeByID(ctx context.Context, promoCodeID primitive.ObjectID) (*models.PromoCode, error) AddFastLink(ctx context.Context, promoCodeID primitive.ObjectID, xid string) error + IncreaseActivationCount(ctx context.Context, promoCodeID primitive.ObjectID) error } type PromoStatsRepository interface { @@ -92,9 +96,32 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa s.logger.Error("Failed to activate promocode", zap.Error(err)) return "", err } + //todo такая реализация проверок кажется довольно массивной, думаю как то это стоит сделать параллельно обхаживая все условия + if promoCode.DueTo < time.Now().Unix() && promoCode.OffLimit { + err := s.promoCodeRepo.IncreaseActivationCount(ctx, promoCode.ID) + if err != nil { + return "", err + } + return "", fmt.Errorf("%w: expired on %s", repository.ErrPromoCodeExpired, time.Unix(promoCode.DueTo, 0).Format(time.RFC3339)) + } + + if promoCode.DueTo == 0 && promoCode.ActivationCount < 0 { + err := s.promoCodeRepo.IncreaseActivationCount(ctx, promoCode.ID) + if err != nil { + return "", err + } + return "", repository.ErrPromoCodeExhausted + } err = s.statsRepo.UpdateStatistics(ctx, promoCode.ID.Hex(), userID) if err != nil { + if errors.Is(err, repository.ErrPromoCodeAlreadyActivated) { + err := s.promoCodeRepo.IncreaseActivationCount(ctx, promoCode.ID) + if err != nil { + return "", err + } + return "", repository.ErrPromoCodeAlreadyActivated + } s.logger.Error("Failed add in stats", zap.Error(err)) return "", err } diff --git a/tests/e2e/promo_test.go b/tests/e2e/promo_test.go index 8de7845..b42461a 100644 --- a/tests/e2e/promo_test.go +++ b/tests/e2e/promo_test.go @@ -466,7 +466,6 @@ func TestActivatePromoCode(t *testing.T) { } assert.Equal(t, fiber.StatusOK, statusCode) - var response models.ActivateResp err := json.Unmarshal(resBody, &response) assert.NoError(t, err) From 0b17ab396f92f004da8d09d2aba7ab65302498d5 Mon Sep 17 00:00:00 2001 From: Pavel Date: Sun, 3 Mar 2024 21:24:28 +0300 Subject: [PATCH 47/66] change promo history --- docs/openapi.yaml | 21 +++++++- .../promocode/promocode_controller.go | 12 ++++- internal/controller/promocode/route.go | 2 +- internal/models/bonus.go | 11 ++-- internal/repository/promocode_stats.go | 54 ++++++++++--------- internal/services/promocode_service.go | 10 ++-- tests/e2e/promo_test.go | 18 +++++-- 7 files changed, 87 insertions(+), 41 deletions(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 244e4dc..0ee074a 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -313,8 +313,18 @@ paths: error: type: string /promocode/stats: - get: + post: summary: Получить статистику промокода + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + promoCodeID: + type: string + description: Идентификатор промокода responses: '200': description: Статистика промокода успешно получена @@ -322,6 +332,15 @@ paths: application/json: schema: $ref: '#/components/schemas/PromoCodeStats' + '400': + description: Неверный запрос + content: + application/json: + schema: + type: object + properties: + error: + type: string '500': description: Внутренняя ошибка сервера content: diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index b8192ee..7e8c642 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -184,8 +184,16 @@ 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()) +func (p *PromoCodeController) GetStats(c *fiber.Ctx) error { + var req struct { + PromoCodeID string `json:"id"` + } + + if err := c.BodyParser(&req); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) + } + + promoStats, err := p.promoCodeService.GetStats(c.Context(), req.PromoCodeID) 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"}) diff --git a/internal/controller/promocode/route.go b/internal/controller/promocode/route.go index e867821..af93df7 100644 --- a/internal/controller/promocode/route.go +++ b/internal/controller/promocode/route.go @@ -9,7 +9,7 @@ func (p *PromoCodeController) Register(router fiber.Router) { router.Post("/activate", p.Activate) router.Delete("/:promocodeID", p.Delete) router.Post("/fastlink", p.CreateFastLink) - router.Get("/stats", p.GetAllStats) + router.Get("/stats", p.GetStats) } diff --git a/internal/models/bonus.go b/internal/models/bonus.go index 7ac63cd..d14150c 100644 --- a/internal/models/bonus.go +++ b/internal/models/bonus.go @@ -68,7 +68,12 @@ type ActivateResp struct { } 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"` + ID string `bson:"_id,omitempty" json:"id,omitempty"` + UsageCount int `bson:"usageCount" json:"usageCount"` + UsageMap map[string][]Usage `bson:"usageMap" json:"usageMap"` +} + +type Usage struct { + Key string `bson:"key" json:"key"` + Time time.Time `bson:"time" json:"time"` } diff --git a/internal/repository/promocode_stats.go b/internal/repository/promocode_stats.go index fe61c2b..c439549 100644 --- a/internal/repository/promocode_stats.go +++ b/internal/repository/promocode_stats.go @@ -4,6 +4,7 @@ import ( "codeword/internal/models" "context" "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "time" @@ -18,53 +19,54 @@ func NewStatsRepository(deps Deps) *StatsRepository { return &StatsRepository{mdb: deps.Mdb} } -func (r *StatsRepository) UpdateStatistics(ctx context.Context, key, userID string) error { - filter := bson.M{"_id": key, "usageCount." + userID: bson.M{"$exists": true}} - +func (r *StatsRepository) UpdateStatistics(ctx context.Context, req *models.ActivateReq, promoCode *models.PromoCode, userID string) error { + filter := bson.M{"_id": promoCode.ID, "usageMap." + userID: bson.M{"$exists": true}} count, err := r.mdb.CountDocuments(ctx, filter) if err != nil { return err } - if count > 0 { + if count == 1 { return ErrPromoCodeAlreadyActivated } + var key string + if req.FastLink != "" { + key = req.FastLink + } else { + key = req.Codeword + } + + usage := models.Usage{ + Key: key, + Time: time.Now(), + } + update := bson.M{ - "$inc": bson.M{"usageCount." + userID: 1}, - "$push": bson.M{"usageHistory." + userID: time.Now()}, + "$inc": bson.M{"usageCount": 1}, + "$push": bson.M{ + "usageMap." + userID: usage, + }, } opts := options.Update().SetUpsert(true) - filter = bson.M{"_id": key} - - _, err = r.mdb.UpdateOne(ctx, filter, update, opts) + _, err = r.mdb.UpdateOne(ctx, bson.M{"_id": promoCode.ID}, 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) +func (r *StatsRepository) GetStatistics(ctx context.Context, promoCodeID string) (*models.PromoCodeStats, error) { + objID, err := primitive.ObjectIDFromHex(promoCodeID) 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) - } + filter := bson.M{"_id": objID} - if err := cursor.Err(); err != nil { + var promoCodeStats models.PromoCodeStats + err = r.mdb.FindOne(ctx, filter).Decode(&promoCodeStats) + if err != nil { return nil, err } - return promoCodeStatsList, nil + return &promoCodeStats, nil } diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index a5670e7..2150cd4 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -26,8 +26,8 @@ type PromoCodeRepository interface { } type PromoStatsRepository interface { - UpdateStatistics(ctx context.Context, key, userID string) error - GetAllStatistics(ctx context.Context) ([]models.PromoCodeStats, error) + UpdateStatistics(ctx context.Context, req *models.ActivateReq, promoCode *models.PromoCode, userID string) error + GetStatistics(ctx context.Context, promoCodeID string) (*models.PromoCodeStats, error) } type PromoDeps struct { @@ -113,7 +113,7 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa return "", repository.ErrPromoCodeExhausted } - err = s.statsRepo.UpdateStatistics(ctx, promoCode.ID.Hex(), userID) + err = s.statsRepo.UpdateStatistics(ctx, req, promoCode, userID) if err != nil { if errors.Is(err, repository.ErrPromoCodeAlreadyActivated) { err := s.promoCodeRepo.IncreaseActivationCount(ctx, promoCode.ID) @@ -201,8 +201,8 @@ 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) +func (s *PromoCodeService) GetStats(ctx context.Context, promoCodeID string) (*models.PromoCodeStats, error) { + promoStats, err := s.statsRepo.GetStatistics(ctx, promoCodeID) if err != nil { s.logger.Error("Failed getting promo stats", zap.Error(err)) return nil, err diff --git a/tests/e2e/promo_test.go b/tests/e2e/promo_test.go index b42461a..822dfd1 100644 --- a/tests/e2e/promo_test.go +++ b/tests/e2e/promo_test.go @@ -316,7 +316,11 @@ func TestCreateFastLink(t *testing.T) { client := fiber.AcquireClient() t.Run("CreateFastLink-success", func(t *testing.T) { - reqBody := map[string]string{"id": promoID} + reqBody := struct { + PromoCodeID string `json:"id"` + }{ + PromoCodeID: promoID, + } reqJSON, _ := json.Marshal(reqBody) @@ -326,7 +330,7 @@ func TestCreateFastLink(t *testing.T) { if len(errs) != 0 { assert.NoError(t, errs[0]) } - + fmt.Println(string(resBody)) assert.Equal(t, fiber.StatusCreated, statusCode) var response map[string]string @@ -537,7 +541,15 @@ func TestGetPromoStats(t *testing.T) { client := fiber.AcquireClient() t.Run("GetAllStats", func(t *testing.T) { - req := client.Get(BaseUrl + "/promocode/stats") + + reqBody := struct { + PromoCodeID string `json:"id"` + }{ + PromoCodeID: promoID, + } + + reqJSON, _ := json.Marshal(reqBody) + req := client.Get(BaseUrl+"/promocode/stats").Set("Content-Type", "application/json").Body(reqJSON) statusCode, resBody, errs := req.Bytes() if len(errs) != 0 { From 3a68da81e1b82dfa14e000a0a7333f5b78e99baf Mon Sep 17 00:00:00 2001 From: Pavel Date: Sun, 3 Mar 2024 21:26:46 +0300 Subject: [PATCH 48/66] change promo history --- internal/repository/promocode_stats.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/repository/promocode_stats.go b/internal/repository/promocode_stats.go index c439549..1daf023 100644 --- a/internal/repository/promocode_stats.go +++ b/internal/repository/promocode_stats.go @@ -26,7 +26,7 @@ func (r *StatsRepository) UpdateStatistics(ctx context.Context, req *models.Acti return err } - if count == 1 { + if count >= 1 { return ErrPromoCodeAlreadyActivated } From d8abc9c16f56bf8ca14eb0924a70908e46fc8af0 Mon Sep 17 00:00:00 2001 From: Pavel Date: Sun, 3 Mar 2024 22:31:40 +0300 Subject: [PATCH 49/66] update openapi --- docs/openapi.yaml | 606 +++++++++--------- .../promocode/promocode_controller.go | 6 +- 2 files changed, 292 insertions(+), 320 deletions(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 0ee074a..2d59bb6 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -2,68 +2,64 @@ openapi: 3.0.0 info: title: Codeword Recovery Service API version: 1.0.0 - description: API for handling password recovery for the Codeword service. + description: API для обработки восстановления паролей для сервиса Codeword. +tags: + - name: recover + description: Операции связанные с восстановлением пароля + - name: promocode + description: Операции связанные с промокодами + - name: stats + description: Операции связанные со статистикой paths: /liveness: get: + operationId: Liveness summary: Роут проверки активности + tags: + - recover responses: '200': description: Успех – сервис запущен /readiness: get: - summary: Роут проверки базы данных + operationId: Readiness + summary: Роут проверки баз данных + tags: + - recover responses: '200': description: Успех — сервис готов и соединение с БД живо '503': description: Служба недоступна — не удалось выполнить проверку связи с БД - /recover: post: - summary: Запустите процесс восстановления пароля + operationId: Recovery + summary: Восстановления пароля + tags: + - recover requestBody: required: true content: application/json: schema: - type: object - required: - - email - properties: - email: - type: string - format: email - description: Электронная почта, на которую нужно отправить инструкции по восстановлению - Referrer: - type: string - description: URL-адрес referral, если он доступен - RedirectionURL: - type: string - description: URL-адрес, на который перенаправляется пользователь после отправки электронного письма - + $ref: '#/components/schemas/RecoveryRequest' responses: '200': - description: Запрос на восстановление принят, и возвращен идентификатор записи восстановления - content: - application/json: - schema: - type: object - properties: - id: - type: string - description: Идентификатор запроса на восстановление + description: Запрос на восстановление принят '404': description: Пользователь не найден по электронной почте '500': - description: Внутренняя ошибка сервера – разные причины + description: Внутренняя ошибка сервера /recover/{sign}: get: - summary: Обработать ссылку восстановления, в которой содержится подпись и обменять ее на токены + operationId: RecoveryLink + summary: Обработать ссылку восстановления и обменять ее на токены + tags: + - recover parameters: - in: path name: sign @@ -73,59 +69,41 @@ paths: description: Подпись восстановления как часть URL-адреса восстановления responses: '200': - description: Восстановление успешно, информация для обмена токенов возвращена - content: - application/json: - schema: - type: object - properties: - accessToken: - type: string - refreshToken: - type: string + description: Восстановление успешно, информация для обмена токенов возвращена в cookie '406': description: NotAcceptable - срок действия ссылки для восстановления истек или она недействительна '500': - description: Внутренняя ошибка сервера – разные причины + description: Внутренняя ошибка сервера /promocode/create: post: + operationId: CreatePromoCode summary: Создать новый промокод + tags: + - promocode requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/PromoCodeRequest' + $ref: '#/components/schemas/PromoCodeReq' responses: - '201': + '200': description: Новый промокод успешно создан content: application/json: schema: - $ref: '#/components/schemas/PromoCodeResponse' + $ref: '#/components/schemas/PromoCode' '400': - description: Invalid request payload / Duplicate Codeword - content: - application/json: - schema: - type: object - properties: - error: - type: string + description: Неверный формат запроса или дублирующийся codeword '500': description: Внутренняя ошибка сервера - content: - application/json: - schema: - type: object - properties: - error: - type: string - /promocode/edit: put: + operationId: EditPromoCode summary: Обновить существующий промокод + tags: + - promocode requestBody: required: true content: @@ -138,38 +116,20 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PromoCodeResponse' + $ref: '#/components/schemas/PromoCode' '400': description: Неверный формат запроса - content: - application/json: - schema: - type: object - properties: - error: - type: string '404': description: Промокод не найден - content: - application/json: - schema: - type: object - properties: - error: - type: string '500': description: Внутренняя ошибка сервера - content: - application/json: - schema: - type: object - properties: - error: - type: string /promocode/getList: post: - summary: Получить список промокодов + operationId: GetList + summary: Получить список промокодов с пагинацией + tags: + - promocode requestBody: required: true content: @@ -185,19 +145,15 @@ paths: $ref: '#/components/schemas/GetPromoCodesListResp' '400': description: Неверный запрос из-за невалидных данных - content: - application/json: - schema: - type: object - properties: - error: - type: string '500': description: Внутренняя ошибка сервера /promocode/activate: post: + operationId: Activate summary: Активировать промокод + tags: + - promocode requestBody: required: true content: @@ -212,7 +168,7 @@ paths: schema: $ref: '#/components/schemas/ActivateResp' '400': - description: Невалидный запрос или отсутствует обязательное поле codeword + description: Невалидный запрос или отсутствует обязательное поле codeword или fastLink '404': description: Промокод не найден '500': @@ -220,7 +176,10 @@ paths: /promocode/{promocodeID}: delete: + operationId: Delete summary: Мягко удалить промокод по его id + tags: + - promocode parameters: - in: path name: promocodeID @@ -233,98 +192,50 @@ paths: description: Промокод успешно помечен как удаленный '400': description: Неверный запрос, отсутствует идентификатор промокода - content: - application/json: - schema: - type: object - properties: - error: - type: string '404': description: Промокод не найден - content: - application/json: - schema: - type: object - properties: - error: - type: string '500': description: Внутренняя ошибка сервера - content: - application/json: - schema: - type: object - properties: - error: - type: string /promocode/fastlink: post: + operationId: CreateFastLink summary: Создать быструю ссылку для промокода + tags: + - promocode requestBody: required: true content: application/json: schema: - type: object - required: - - id - properties: - id: - type: string - description: ID промокода, для которого нужно создать быструю ссылку + $ref: '#/components/schemas/CreateFastLinkRequest' responses: - '201': + '200': description: Быстрая ссылка для промокода успешно создана content: application/json: schema: - type: object - properties: - fastlink: - type: string - description: Быстрая ссылка для активации промокода + $ref: '#/components/schemas/CreateFastLinkResponse' '400': description: Неверный запрос, отсутствует идентификатор промокода - content: - application/json: - schema: - type: object - properties: - error: - type: string '404': description: Промокод не найден - content: - application/json: - schema: - type: object - properties: - error: - type: string '500': description: Внутренняя ошибка сервера - content: - application/json: - schema: - type: object - properties: - error: - type: string + /promocode/stats: - post: + get: + operationId: GetStats summary: Получить статистику промокода + tags: + - stats + description: Идентификатор промокода requestBody: required: true content: application/json: schema: - type: object - properties: - promoCodeID: - type: string - description: Идентификатор промокода + $ref: '#/components/schemas/PromoCodeStatsRequest' responses: '200': description: Статистика промокода успешно получена @@ -334,199 +245,83 @@ paths: $ref: '#/components/schemas/PromoCodeStats' '400': description: Неверный запрос - content: - application/json: - schema: - type: object - properties: - error: - type: string '500': description: Внутренняя ошибка сервера - content: - application/json: - schema: - type: object - properties: - error: - type: string + components: schemas: - PromoCodeRequest: + RecoveryRequest: + type: object + required: + - email + properties: + email: + type: string + format: email + description: Электронная почта, на которую нужно отправить инструкции по восстановлению + redirectionURL: + type: string + description: URL-адрес, на который перенаправляется пользователь + PromoCodeStatsRequest: type: object properties: - codeword: + promoCodeID: type: string - description: Кодовое слово, которое должен ввести пользователь - description: - type: string - description: Описание, необходимое для администратора в панели управления - greetings: - type: string - description: Текст, который будет отправлен пользователю в ответ на активацию кода - dueTo: - type: integer - format: int64 - description: Временная метка окончания активации кода - activationCount: - type: integer - format: int64 - description: Лимит активации кода - bonus: - type: object - properties: - privilege: - type: object - properties: - privilegeID: - type: string - description: Идентификатор привилегии, которую необходимо предоставить - amount: - type: integer - format: uint64 - description: Размер привилегии - discount: - type: object - properties: - layer: - type: integer - factor: - type: number - target: - type: string - threshold: - type: integer - description: Информация о бонусах - - PromoCodeResponse: + required: + - promoCodeID + PromoCodeStats: type: object properties: id: type: string - description: ID созданного промокода - codeword: - type: string - description: Кодовое слово промокода - description: - type: string - description: Описание промокода - greetings: - type: string - description: Текст, который будет отправлен пользователю в ответ на активацию кода - dueTo: + description: Идентификатор промокода + usageCount: type: integer - format: int64 - description: Временная метка окончания активации кода - activationCount: - type: integer - format: int64 - description: Лимит активации кода - bonus: + description: Количество использований промокода + usageMap: type: object - properties: - privilege: - type: object - properties: - privilegeID: - type: string - description: Идентификатор привилегии, которую необходимо предоставить - amount: - type: integer - format: uint64 - description: Размер привилегии - discount: - type: object - properties: - layer: - type: integer - factor: - type: number - target: - type: string - threshold: - type: integer - description: Информация о бонусах - outdated: - type: boolean - offLimit: - type: boolean - delete: - type: boolean - createdAt: + description: Карта использования промокода + additionalProperties: + type: array + items: + $ref: '#/components/schemas/Usage' + + Usage: + type: object + properties: + key: + type: string + description: fastlink или codeword в зависимости от того что применялось + time: type: string format: date-time - description: Время создания промокода + description: Время использования промокода - ReqEditPromoCode: + CreateFastLinkRequest: type: object properties: id: type: string - description: ID промокода, который обновляем - description: - type: string - description: Описание, необходимое менеджеру в админке - greetings: - type: string - description: Текст, выдаваемый пользователю в ответ на активацию промокода - dueTo: - type: integer - format: int64 - description: Временная метка окончания активации кода - activationCount: - type: integer - format: int64 - description: Предел количества активаций промокода - delete: - type: boolean - description: Флаг удаления промокода + description: ID промокода, для которого нужно создать быструю ссылку required: - id - GetPromoCodesListReq: - type: object - required: - - page - - limit - - filter - properties: - page: - type: integer - description: Номер страницы выборки, начиная с 0 - limit: - type: integer - description: Размер страницы выборки - filter: - $ref: '#/components/schemas/GetPromoCodesListReqFilter' - GetPromoCodesListReqFilter: + CreateFastLinkResponse: type: object properties: - text: + fastlink: type: string - description: Полнотекстовый поиск по полям Codeword, Description, Greetings - active: - type: boolean - description: Если true, выбираются записи, где delete, outdated и offLimit равны false - - GetPromoCodesListResp: - type: object - properties: - count: - type: integer - format: int64 - description: Общее количество промокодов в выборке - items: - type: array - items: - $ref: '#/components/schemas/PromoCodeResponse' + description: Быстрая ссылка для активации промокода ActivateReq: type: object + required: + - codeword properties: codeword: type: string - description: Кодовое слово промокода, которое требуется активировать + description: Кодовое слово для активации промокода fastLink: type: string description: Быстрая ссылка для активации промокода @@ -536,17 +331,190 @@ components: properties: greetings: type: string - description: Поле из активированного промокода + description: Слово успешной активации промокода - PromoCodeStats: + GetPromoCodesListReq: + type: object + required: + - page + - limit + properties: + page: + type: integer + description: Номер страницы + limit: + type: integer + description: Максимальное количество элементов на странице + filter: + $ref: '#/components/schemas/Filter' + + Filter: + type: object + properties: + text: + type: string + description: Текстовый фильтр для поиска промокодов + active: + type: boolean + description: Флаг для фильтрации активных промокодов + + GetPromoCodesListResp: + type: object + properties: + count: + type: integer + description: Общее количество промокодов + items: + type: array + items: + $ref: '#/components/schemas/PromoCode' + + PromoCode: type: object properties: id: type: string - description: ID промокода - usageCount: + description: Идентификатор промокода + codeword: + type: string + description: Кодовое слово для активации промокода + description: + type: string + description: Описание промокода + greetings: + type: string + description: Приветственное сообщение после активации промокода + dueTo: + type: integer + description: Дата истечения действия промокода в формате Unix time + activationCount: + type: integer + description: Количество активаций промокода + bonus: type: object - description: Количество использований промокода для каждого пользователя - usageHistory: + description: Бонус, предоставляемый с промокодом + properties: + privilege: + type: object + description: Привилегия + properties: + privilegeID: + type: string + description: Идентификатор привилегии + amount: + type: integer + description: Количество привилегии + discount: + type: object + description: Скидка + properties: + layer: + type: integer + description: Уровень скидки + factor: + type: number + description: Множитель скидки + target: + type: string + description: Слой + threshold: + type: integer + description: Граничное значение + outdated: + type: boolean + description: Флаг + offLimit: + type: boolean + description: Флаг + delete: + type: boolean + description: Флаг + createdAt: + type: string + format: date-time + description: Дата и время создания промокода + fastLinks: + type: array + items: + type: string + description: Список быстрых ссылок для активации промокода + + ReqEditPromoCode: + type: object + properties: + ID: + type: string + description: Идентификатор промокода, который требуется обновить + Description: + type: string + nullable: true + description: Описание промокода + Greetings: + type: string + nullable: true + description: Приветственное сообщение после активации промокода + DueTo: + type: integer + nullable: true + description: Дата окончания промокода в формате Unix time + ActivationCount: + type: integer + nullable: true + description: Количество активаций промокода + Delete: + type: boolean + nullable: true + description: Флаг удаления промокода + PromoCodeReq: + type: object + properties: + codeword: + type: string + description: Кодовое слово для активации промокода + description: + type: string + description: Описание промокода + greetings: + type: string + description: Приветственное сообщение после активации промокода + dueTo: + type: integer + description: Дата истечения действия промокода в формате Unix time + activationCount: + type: integer + description: Количество активаций промокода + bonus: type: object - description: История использования промокода для каждого пользователя + description: Бонус + properties: + privilege: + type: object + description: Привилегия + properties: + privilegeID: + type: string + description: Идентификатор привилегии + amount: + type: integer + description: Количество привилегии + discount: + type: object + description: Скидка + properties: + layer: + type: integer + description: Уровень скидки + factor: + type: number + description: Множитель скидки + target: + type: string + description: Слой + threshold: + type: integer + description: Граничное значение + fastLinks: + type: array + items: + type: string + description: Список быстрых ссылок для активации промокода diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index 7e8c642..bc822a0 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -50,7 +50,7 @@ func (p *PromoCodeController) CreatePromoCode(c *fiber.Ctx) error { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } - return c.Status(fiber.StatusCreated).JSON(createdPromoCode) + return c.Status(fiber.StatusOK).JSON(createdPromoCode) } func (p *PromoCodeController) EditPromoCode(c *fiber.Ctx) error { @@ -193,6 +193,10 @@ func (p *PromoCodeController) GetStats(c *fiber.Ctx) error { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) } + if req.PromoCodeID == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "PromoCode ID is required"}) + } + promoStats, err := p.promoCodeService.GetStats(c.Context(), req.PromoCodeID) if err != nil { p.logger.Error("Failed getting promo stats", zap.Error(err)) From 012ad98004d13d1541fc59f18c49e4ea0caf65bc Mon Sep 17 00:00:00 2001 From: Pavel Date: Sun, 3 Mar 2024 23:22:19 +0300 Subject: [PATCH 50/66] update docks for bp --- docs/openapi.yaml | 120 +++++++++++++++++++++------------------------- 1 file changed, 54 insertions(+), 66 deletions(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 2d59bb6..fc3d1f9 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -45,7 +45,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RecoveryRequest' + $ref: '#/components/schemas/RecoveryReq' responses: '200': description: Запрос на восстановление принят @@ -109,7 +109,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ReqEditPromoCode' + $ref: '#/components/schemas/EditPromoCodeReq' responses: '200': description: Промокод успешно обновлен @@ -208,14 +208,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateFastLinkRequest' + $ref: '#/components/schemas/CreateFastLinkReq' responses: '200': description: Быстрая ссылка для промокода успешно создана content: application/json: schema: - $ref: '#/components/schemas/CreateFastLinkResponse' + $ref: '#/components/schemas/CreateFastLinkResp' '400': description: Неверный запрос, отсутствует идентификатор промокода '404': @@ -235,14 +235,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PromoCodeStatsRequest' + $ref: '#/components/schemas/PromoCodeStatsReq' responses: '200': description: Статистика промокода успешно получена content: application/json: schema: - $ref: '#/components/schemas/PromoCodeStats' + $ref: '#/components/schemas/PromoCodeStatsResp' '400': description: Неверный запрос '500': @@ -251,7 +251,7 @@ paths: components: schemas: - RecoveryRequest: + RecoveryReq: type: object required: - email @@ -263,14 +263,14 @@ components: redirectionURL: type: string description: URL-адрес, на который перенаправляется пользователь - PromoCodeStatsRequest: + PromoCodeStatsReq: type: object properties: promoCodeID: type: string required: - promoCodeID - PromoCodeStats: + PromoCodeStatsResp: type: object properties: id: @@ -298,7 +298,7 @@ components: format: date-time description: Время использования промокода - CreateFastLinkRequest: + CreateFastLinkReq: type: object properties: id: @@ -307,7 +307,7 @@ components: required: - id - CreateFastLinkResponse: + CreateFastLinkResp: type: object properties: fastlink: @@ -393,33 +393,8 @@ components: bonus: type: object description: Бонус, предоставляемый с промокодом - properties: - privilege: - type: object - description: Привилегия - properties: - privilegeID: - type: string - description: Идентификатор привилегии - amount: - type: integer - description: Количество привилегии - discount: - type: object - description: Скидка - properties: - layer: - type: integer - description: Уровень скидки - factor: - type: number - description: Множитель скидки - target: - type: string - description: Слой - threshold: - type: integer - description: Граничное значение + items: + $ref: '#/components/schemas/Bonus' outdated: type: boolean description: Флаг @@ -439,7 +414,7 @@ components: type: string description: Список быстрых ссылок для активации промокода - ReqEditPromoCode: + EditPromoCodeReq: type: object properties: ID: @@ -486,35 +461,48 @@ components: bonus: type: object description: Бонус - properties: - privilege: - type: object - description: Привилегия - properties: - privilegeID: - type: string - description: Идентификатор привилегии - amount: - type: integer - description: Количество привилегии - discount: - type: object - description: Скидка - properties: - layer: - type: integer - description: Уровень скидки - factor: - type: number - description: Множитель скидки - target: - type: string - description: Слой - threshold: - type: integer - description: Граничное значение + items: + $ref: '#/components/schemas/Bonus' fastLinks: type: array items: type: string description: Список быстрых ссылок для активации промокода + + Bonus: + type: object + description: Бонус + properties: + privilege: + $ref: '#/components/schemas/Privilege' + discount: + $ref: '#/components/schemas/Discount' + + Privilege: + type: object + description: Привилегия + properties: + privilegeID: + type: string + description: Идентификатор привилегии + amount: + type: integer + description: Количество привилегии + + Discount: + type: object + description: Скидка + properties: + layer: + type: integer + description: Уровень скидки + factor: + type: integer + description: Множитель скидки + target: + type: string + description: Цель скидки + threshold: + type: integer + description: Порог скидки + From 1cac8fa10232a75cd1de7483d822db11908a36cf Mon Sep 17 00:00:00 2001 From: skeris Date: Sat, 9 Mar 2024 18:51:59 +0300 Subject: [PATCH 51/66] fix: change promocode expiration logic to only expiration --- internal/services/promocode_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index 2150cd4..3947f3e 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -97,7 +97,7 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa return "", err } //todo такая реализация проверок кажется довольно массивной, думаю как то это стоит сделать параллельно обхаживая все условия - if promoCode.DueTo < time.Now().Unix() && promoCode.OffLimit { + if promoCode.DueTo < time.Now().Unix() && promoCode.DueTo > 0 { err := s.promoCodeRepo.IncreaseActivationCount(ctx, promoCode.ID) if err != nil { return "", err From a9bf4486c362e292d19723d70dcda2726bdd061b Mon Sep 17 00:00:00 2001 From: skeris Date: Thu, 14 Mar 2024 12:55:33 +0300 Subject: [PATCH 52/66] fix: Dockerfile for private repo --- Dockerfile | 4 ++++ deployments/staging/docker-compose.yaml | 6 ++++++ internal/controller/recovery/recovery_controller.go | 8 ++------ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3a3fab5..b358f74 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,11 @@ WORKDIR /app # Create binary directory RUN mkdir /app/bin -p # Add main files to app +RUN apk add git ADD . . +ENV GOPRIVATE=penahub.gitlab.yandexcloud.net/backend/penahub_common +RUN git config --global url."https://buildToken:glpat-axA8ttckx3aPf_xd2Dym@penahub.gitlab.yandexcloud.net/".insteadOf "https://penahub.gitlab.yandexcloud.net/" +RUN go mod download # Build app RUN GOOS=linux go build -o bin ./... diff --git a/deployments/staging/docker-compose.yaml b/deployments/staging/docker-compose.yaml index 57d3792..34ad3c2 100644 --- a/deployments/staging/docker-compose.yaml +++ b/deployments/staging/docker-compose.yaml @@ -32,6 +32,12 @@ services: DEFAULT_REDIRECTION_URL: 'https://shub.pena.digital/' AUTH_EXCHANGE_URL: 'http://10.6.0.11:59300/auth/exchange' RECOVER_URL: 'https://shub.pena.digital/codeword/recover/' + JWT_AUDIENCE: 'pena' + JWT_ISSUER: 'pena-auth-service' + JWT_PUBLIC_KEY: "-----BEGIN PUBLIC KEY-----\nMIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHgnvr7O2tiApjJfid1orFnIGm6980fZp+Lpbjo+NC/0whMFga2Biw5b1G2Q/B2u0tpO1Fs/E8z7Lv1nYfr5jx2S8x6BdA4TS2kB9Kf0wn0+7wSlyikHoKhbtzwXHZl17GsyEi6wHnsqNBSauyIWhpha8i+Y+3GyaOY536H47qyXAgMBAAE=\n-----END PUBLIC KEY-----" + KAFKA_BROKERS: "10.6.0.11:9092" + KAFKA_TOPIC_TARIFF: "tariffs" + DISCOUNT_ADDRESS: "http://10.6.0.11:9001" ports: - 59664:3000 networks: diff --git a/internal/controller/recovery/recovery_controller.go b/internal/controller/recovery/recovery_controller.go index 5a85980..3f0f8cd 100644 --- a/internal/controller/recovery/recovery_controller.go +++ b/internal/controller/recovery/recovery_controller.go @@ -75,11 +75,7 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } -<<<<<<< HEAD - signUrl := redirectionURL -======= signUrl := req.RedirectionURL ->>>>>>> dev sign := base64.URLEncoding.EncodeToString(key) id, err := r.service.StoreRecoveryRecord(c.Context(), models.StoreRecDeps{ @@ -114,12 +110,12 @@ func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error { record, err := r.service.GetRecoveryRecord(c.Context(), sign) if err != nil { - r.logger.Error("Recovery link expired", zap.String("signature", key)) + r.logger.Error("Recovery link expired", zap.String("signature", sign)) return c.Redirect("https://shub.pena.digital/recover/expired") } if time.Since(record.CreatedAt) > 15*time.Minute { - r.logger.Error("Recovery link expired", zap.String("signature", key)) + r.logger.Error("Recovery link expired", zap.String("signature", sign)) return c.Redirect(record.SignUrl+"/expired") } From a388c298f1cc509bedee6cbc23c6760fecd644c9 Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 21 Mar 2024 12:42:32 +0300 Subject: [PATCH 53/66] update open api --- docs/openapi.yaml | 103 ++-------------------------------------------- 1 file changed, 4 insertions(+), 99 deletions(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 308a548..fc3d1f9 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -268,48 +268,9 @@ components: properties: promoCodeID: type: string - description: Кодовое слово, которое должен ввести пользователь - description: - type: string - description: Описание, необходимое для администратора в панели управления - greetings: - type: string - description: Текст, который будет отправлен пользователю в ответ на активацию кода - dueTo: - type: integer - format: int64 - description: Временная метка окончания активации кода - activationCount: - type: integer - format: int64 - description: Лимит активации кода - bonus: - type: object - properties: - privilege: - type: object - properties: - privilegeID: - type: string - description: Идентификатор привилегии, которую необходимо предоставить - amount: - type: integer - format: uint64 - description: Размер привилегии - discount: - type: object - properties: - layer: - type: integer - factor: - type: number - target: - type: string - threshold: - type: integer - description: Информация о бонусах - - PromoCodeResponse: + required: + - promoCodeID + PromoCodeStatsResp: type: object properties: id: @@ -342,65 +303,9 @@ components: properties: id: type: string - description: ID промокода, который обновляем - description: - type: string - description: Описание, необходимое менеджеру в админке - greetings: - type: string - description: Текст, выдаваемый пользователю в ответ на активацию промокода - dueTo: - type: integer - format: int64 - description: Временная метка окончания активации кода - activationCount: - type: integer - format: int64 - description: Предел количества активаций промокода - delete: - type: boolean - description: Флаг удаления промокода - bonus: - type: object - properties: - privilege: - type: object - properties: - privilegeID: - type: string - description: Идентификатор привилегии, которую необходимо предоставить - amount: - type: integer - format: uint64 - description: Размер привилегии - discount: - type: object - properties: - layer: - type: integer - factor: - type: number - target: - type: string - threshold: - type: integer - description: Информация о бонусах + description: ID промокода, для которого нужно создать быструю ссылку required: - id - GetPromoCodesListReq: - type: object - required: - - page - - limit - properties: - page: - type: integer - description: Номер страницы выборки, начиная с 0 - limit: - type: integer - description: Размер страницы выборки - filter: - $ref: '#/components/schemas/GetPromoCodesListReqFilter' CreateFastLinkResp: type: object From 7b0de776080dbffab1d658f0fd420d3e3266c058 Mon Sep 17 00:00:00 2001 From: skeris Date: Thu, 21 Mar 2024 17:49:35 +0300 Subject: [PATCH 54/66] dirty logging --- deployments/staging/docker-compose.yaml | 2 +- internal/controller/promocode/promocode_controller.go | 5 +++++ internal/services/promocode_service.go | 11 +++++++++++ utils/authenticator.go | 2 +- utils/jwt.go | 1 + 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/deployments/staging/docker-compose.yaml b/deployments/staging/docker-compose.yaml index 34ad3c2..e53be5b 100644 --- a/deployments/staging/docker-compose.yaml +++ b/deployments/staging/docker-compose.yaml @@ -34,7 +34,7 @@ services: RECOVER_URL: 'https://shub.pena.digital/codeword/recover/' JWT_AUDIENCE: 'pena' JWT_ISSUER: 'pena-auth-service' - JWT_PUBLIC_KEY: "-----BEGIN PUBLIC KEY-----\nMIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHgnvr7O2tiApjJfid1orFnIGm6980fZp+Lpbjo+NC/0whMFga2Biw5b1G2Q/B2u0tpO1Fs/E8z7Lv1nYfr5jx2S8x6BdA4TS2kB9Kf0wn0+7wSlyikHoKhbtzwXHZl17GsyEi6wHnsqNBSauyIWhpha8i+Y+3GyaOY536H47qyXAgMBAAE=\n-----END PUBLIC KEY-----" + JWT_PUBLIC_KEY: $JWT_PUBLIC_KEY KAFKA_BROKERS: "10.6.0.11:9092" KAFKA_TOPIC_TARIFF: "tariffs" DISCOUNT_ADDRESS: "http://10.6.0.11:9001" diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index f7ef478..9758631 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -5,6 +5,7 @@ import ( "codeword/internal/repository" "codeword/internal/services" "errors" + "fmt" "github.com/gofiber/fiber/v2" "go.uber.org/zap" ) @@ -97,11 +98,13 @@ func (p *PromoCodeController) GetList(c *fiber.Ctx) error { func (p *PromoCodeController) Activate(c *fiber.Ctx) error { err := p.authMiddleware(c) + fmt.Println("SKER0",err) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err}) } userID := c.Locals(models.AuthJWTDecodedUserIDKey).(string) + fmt.Println("SKER1",userID) if userID == "" { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "failed to get jwt payload"}) } @@ -114,8 +117,10 @@ func (p *PromoCodeController) Activate(c *fiber.Ctx) error { if req.Codeword == "" && req.FastLink == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "codeword or fastlink is required"}) } + fmt.Println("SKER2",req) greetings, err := p.promoCodeService.ActivatePromo(c.Context(), &req, userID) + fmt.Println("SKER3",err) if err != nil { p.logger.Error("Failed to activate promocode", zap.Error(err)) diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index 3947f3e..337f521 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -92,6 +92,7 @@ func (s *PromoCodeService) GetPromoCodesList(ctx context.Context, req *models.Ge func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.ActivateReq, userID string) (string, error) { promoCode, err := s.promoCodeRepo.ActivatePromo(ctx, req) + fmt.Println("SKER20", err, promoCode) if err != nil { s.logger.Error("Failed to activate promocode", zap.Error(err)) return "", err @@ -99,6 +100,7 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa //todo такая реализация проверок кажется довольно массивной, думаю как то это стоит сделать параллельно обхаживая все условия if promoCode.DueTo < time.Now().Unix() && promoCode.DueTo > 0 { err := s.promoCodeRepo.IncreaseActivationCount(ctx, promoCode.ID) + fmt.Println("SKER21", err) if err != nil { return "", err } @@ -107,6 +109,7 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa if promoCode.DueTo == 0 && promoCode.ActivationCount < 0 { err := s.promoCodeRepo.IncreaseActivationCount(ctx, promoCode.ID) + fmt.Println("SKER22", err) if err != nil { return "", err } @@ -114,6 +117,7 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa } err = s.statsRepo.UpdateStatistics(ctx, req, promoCode, userID) + fmt.Println("SKER23", err) if err != nil { if errors.Is(err, repository.ErrPromoCodeAlreadyActivated) { err := s.promoCodeRepo.IncreaseActivationCount(ctx, promoCode.ID) @@ -132,6 +136,8 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa postfix = fmt.Sprintf(":(%s)", req.FastLink) } + if promoCode.Bonus.Privilege.PrivilegeID != "" { + var privileges []models.Privilege privilege := models.Privilege{ PrivilegeID: promoCode.Bonus.Privilege.PrivilegeID, @@ -145,11 +151,14 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa Deleted: promoCode.Delete, CreatedAt: promoCode.CreatedAt, } + fmt.Println("SKER24", err) if err := s.kafka.Send(ctx, userID, fakeTariff); err != nil { s.logger.Error("Failed to send fake tariff to Kafka", zap.Error(err)) return "", err } +} +if promoCode.Bonus.Discount.Factor != 0 { disOverHelm := true discountRequest := &discount.CreateDiscountRequest{ Name: promoCode.Codeword + postfix, @@ -165,11 +174,13 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa }, } + fmt.Println("SKER25", err) _, err = s.discountClient.CreateDiscount(ctx, discountRequest) if err != nil { s.logger.Error("Failed to create discount", zap.Error(err)) return "", err } +} return promoCode.Greetings, nil } diff --git a/utils/authenticator.go b/utils/authenticator.go index 193fcd4..24af308 100644 --- a/utils/authenticator.go +++ b/utils/authenticator.go @@ -2,8 +2,8 @@ package utils import ( "codeword/internal/models" - "fmt" "strings" + "fmt" "github.com/gofiber/fiber/v2" ) diff --git a/utils/jwt.go b/utils/jwt.go index 4584dee..56004a1 100644 --- a/utils/jwt.go +++ b/utils/jwt.go @@ -64,6 +64,7 @@ func (j *JWT) Validate(tokenString string) (string, error) { return key, nil } + fmt.Println("SKER-1", tokenString, j.audience,j.issuer) token, err := jwt.Parse( tokenString, From 6c51c0eafd8965242d992c463beb8969d6b92a65 Mon Sep 17 00:00:00 2001 From: skeris Date: Fri, 22 Mar 2024 17:49:48 +0300 Subject: [PATCH 55/66] fix: remove protocol from discount service adress --- deployments/staging/docker-compose.yaml | 2 +- utils/jwt.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/deployments/staging/docker-compose.yaml b/deployments/staging/docker-compose.yaml index e53be5b..6c53ee7 100644 --- a/deployments/staging/docker-compose.yaml +++ b/deployments/staging/docker-compose.yaml @@ -37,7 +37,7 @@ services: JWT_PUBLIC_KEY: $JWT_PUBLIC_KEY KAFKA_BROKERS: "10.6.0.11:9092" KAFKA_TOPIC_TARIFF: "tariffs" - DISCOUNT_ADDRESS: "http://10.6.0.11:9001" + DISCOUNT_ADDRESS: "10.6.0.11:9001" ports: - 59664:3000 networks: diff --git a/utils/jwt.go b/utils/jwt.go index 56004a1..4584dee 100644 --- a/utils/jwt.go +++ b/utils/jwt.go @@ -64,7 +64,6 @@ func (j *JWT) Validate(tokenString string) (string, error) { return key, nil } - fmt.Println("SKER-1", tokenString, j.audience,j.issuer) token, err := jwt.Parse( tokenString, From 2205bc204ef0b82e7ac688f023e1a199bbc56c85 Mon Sep 17 00:00:00 2001 From: skeris Date: Fri, 22 Mar 2024 21:57:28 +0300 Subject: [PATCH 56/66] =?UTF-8?q?xxfix:=20=20=D0=BF=D1=80=D0=BE=D0=BC?= =?UTF-8?q?=D0=BE=D0=BA=D0=BE=D0=B4=D0=BE=D0=B2=D1=8B=D0=B5=20=D1=81=D0=BA?= =?UTF-8?q?=D0=B8=D0=B4=D0=BA=D0=B8=20=D0=BD=D0=B5=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=D1=8C=D0=BD=D0=BE=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0?= =?UTF-8?q?=D0=B2=D0=B0=D0=BB=D0=B8=D1=81=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/repository/promocode_repository.go | 2 + internal/services/promocode_service.go | 101 ++++++++++++-------- 2 files changed, 64 insertions(+), 39 deletions(-) diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index d17180f..a3e7a92 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -221,10 +221,12 @@ func (r *PromoCodeRepository) ActivatePromo(ctx context.Context, req *models.Act if req.Codeword != "" { filter = bson.M{ "codeword": req.Codeword, + "delete": false, } } else if req.FastLink != "" { filter = bson.M{ "fastLinks": req.FastLink, + "delete": false, } } diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index 337f521..95386a3 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -138,49 +138,72 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa if promoCode.Bonus.Privilege.PrivilegeID != "" { - var privileges []models.Privilege - privilege := models.Privilege{ - PrivilegeID: promoCode.Bonus.Privilege.PrivilegeID, - Amount: promoCode.Bonus.Privilege.Amount, - } - privileges = append(privileges, privilege) + var privileges []models.Privilege + privilege := models.Privilege{ + PrivilegeID: promoCode.Bonus.Privilege.PrivilegeID, + Amount: promoCode.Bonus.Privilege.Amount, + } + privileges = append(privileges, privilege) - fakeTariff := &models.Tariff{ - Name: promoCode.Codeword + postfix, - Privileges: privileges, - Deleted: promoCode.Delete, - CreatedAt: promoCode.CreatedAt, - } - fmt.Println("SKER24", err) - if err := s.kafka.Send(ctx, userID, fakeTariff); err != nil { - s.logger.Error("Failed to send fake tariff to Kafka", zap.Error(err)) - return "", err - } -} - -if promoCode.Bonus.Discount.Factor != 0 { - disOverHelm := true - discountRequest := &discount.CreateDiscountRequest{ - Name: promoCode.Codeword + postfix, - Layer: promoCode.Bonus.Discount.Layer, - Description: "", - Condition: &discount.DiscountCondition{ - Coupon: &promoCode.Codeword, - User: &userID, - }, - Target: &discount.DiscountCalculationTarget{ - Factor: promoCode.Bonus.Discount.Factor, - Overhelm: &disOverHelm, - }, + fakeTariff := &models.Tariff{ + Name: promoCode.Codeword + postfix, + Privileges: privileges, + Deleted: promoCode.Delete, + CreatedAt: promoCode.CreatedAt, + } + fmt.Println("SKER24", err) + if err := s.kafka.Send(ctx, userID, fakeTariff); err != nil { + s.logger.Error("Failed to send fake tariff to Kafka", zap.Error(err)) + return "", err + } } - fmt.Println("SKER25", err) - _, err = s.discountClient.CreateDiscount(ctx, discountRequest) - if err != nil { - s.logger.Error("Failed to create discount", zap.Error(err)) - return "", err + if promoCode.Bonus.Discount.Factor != 0 { + disOverHelm := true + emptyString := "" + zero := uint64(0) + discountRequest := &discount.CreateDiscountRequest{ + Name: promoCode.Codeword + postfix, + Layer: promoCode.Bonus.Discount.Layer, + Description: "", + Condition: &discount.DiscountCondition{ + Coupon: &promoCode.Codeword, + User: &userID, + Group: &promoCode.Bonus.Discount.Target, + Product: &promoCode.Bonus.Discount.Target, + UserType: &emptyString, + PurchasesAmount: &zero, + CartPurchasesAmount: &zero, + Term: &zero, + Usage: &zero, + PriceFrom: &zero, + }, + } + + if promoCode.Bonus.Discount.Layer == 1 { + discountRequest.Target = &discount.DiscountCalculationTarget{ + Products: []*discount.ProductTarget{{ + ID: promoCode.Bonus.Discount.Target, + Factor: promoCode.Bonus.Discount.Factor, + }}, + Overhelm: &disOverHelm, + } + } + + if promoCode.Bonus.Discount.Layer == 2 { + discountRequest.Target = &discount.DiscountCalculationTarget{ + TargetGroup: &promoCode.Bonus.Discount.Target, + Factor: promoCode.Bonus.Discount.Factor, + Overhelm: &disOverHelm, + } + } + + _, err = s.discountClient.CreateDiscount(ctx, discountRequest) + if err != nil { + s.logger.Error("Failed to create discount", zap.Error(err)) + return "", err + } } -} return promoCode.Greetings, nil } From 7d555618fb834ff7d3f8b45aa7617ee8483b44f7 Mon Sep 17 00:00:00 2001 From: Pavel Date: Wed, 27 Mar 2024 15:29:49 +0300 Subject: [PATCH 57/66] update stats --- docs/openapi.yaml | 37 ++++++++-------- .../promocode/promocode_controller.go | 6 +-- internal/controller/promocode/route.go | 2 +- internal/models/bonus.go | 21 +++++++--- internal/repository/promocode_stats.go | 42 ++++++++++--------- internal/services/promocode_service.go | 34 ++++++++++++--- 6 files changed, 88 insertions(+), 54 deletions(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index fc3d1f9..6f64c28 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -224,12 +224,12 @@ paths: description: Внутренняя ошибка сервера /promocode/stats: - get: + post: operationId: GetStats summary: Получить статистику промокода tags: - stats - description: Идентификатор промокода + description: Идентификатор промокода и временной интерфал от до unix requestBody: required: true content: @@ -266,37 +266,34 @@ components: PromoCodeStatsReq: type: object properties: - promoCodeID: + id: type: string + from: + type: integer + to: + type: integer required: - - promoCodeID + - id PromoCodeStatsResp: type: object properties: id: type: string - description: Идентификатор промокода + description: id промокода usageCount: type: integer - description: Количество использований промокода + description: общее количество использований промокода + example: 18 usageMap: type: object - description: Карта использования промокода + description: мапа использования промокода ранжированая по способу additionalProperties: - type: array - items: - $ref: '#/components/schemas/Usage' + type: integer + example: + "-": 10 + "fastlinkID1": 5 + "fastlinkID2": 3 - Usage: - type: object - properties: - key: - type: string - description: fastlink или codeword в зависимости от того что применялось - time: - type: string - format: date-time - description: Время использования промокода CreateFastLinkReq: type: object diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index f7ef478..21c0065 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -184,9 +184,7 @@ func (p *PromoCodeController) CreateFastLink(c *fiber.Ctx) error { } func (p *PromoCodeController) GetStats(c *fiber.Ctx) error { - var req struct { - PromoCodeID string `json:"id"` - } + var req models.PromoStatReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) @@ -196,7 +194,7 @@ func (p *PromoCodeController) GetStats(c *fiber.Ctx) error { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "PromoCode ID is required"}) } - promoStats, err := p.promoCodeService.GetStats(c.Context(), req.PromoCodeID) + promoStats, err := p.promoCodeService.GetStats(c.Context(), req) 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"}) diff --git a/internal/controller/promocode/route.go b/internal/controller/promocode/route.go index af93df7..d89fcdc 100644 --- a/internal/controller/promocode/route.go +++ b/internal/controller/promocode/route.go @@ -9,7 +9,7 @@ func (p *PromoCodeController) Register(router fiber.Router) { router.Post("/activate", p.Activate) router.Delete("/:promocodeID", p.Delete) router.Post("/fastlink", p.CreateFastLink) - router.Get("/stats", p.GetStats) + router.Post("/stats", p.GetStats) } diff --git a/internal/models/bonus.go b/internal/models/bonus.go index 9c3cf6e..13333b5 100644 --- a/internal/models/bonus.go +++ b/internal/models/bonus.go @@ -81,13 +81,24 @@ type ActivateResp struct { Greetings string `json:"greetings"` // поле из активированного промокода } +type PromoStatReq struct { + PromoCodeID string `json:"id,omitempty"` + From uint64 `json:"from"` + To uint64 `json:"to"` +} + type PromoCodeStats struct { - ID string `bson:"_id,omitempty" json:"id,omitempty"` - UsageCount int `bson:"usageCount" json:"usageCount"` - UsageMap map[string][]Usage `bson:"usageMap" json:"usageMap"` + ID string `json:"id,omitempty"` + UsageMap map[string][]Usage `json:"usageMap"` } type Usage struct { - Key string `bson:"key" json:"key"` - Time time.Time `bson:"time" json:"time"` + UserID string `bson:"userID" json:"userID"` + Time uint64 `bson:"time" json:"time"` +} + +type PromoCodeStatsResp struct { + ID string `json:"id,omitempty"` + UsageCount int `json:"usageCount"` + UsageMap map[string]int `json:"usageMap"` } diff --git a/internal/repository/promocode_stats.go b/internal/repository/promocode_stats.go index 1daf023..218709f 100644 --- a/internal/repository/promocode_stats.go +++ b/internal/repository/promocode_stats.go @@ -20,32 +20,36 @@ func NewStatsRepository(deps Deps) *StatsRepository { } func (r *StatsRepository) UpdateStatistics(ctx context.Context, req *models.ActivateReq, promoCode *models.PromoCode, userID string) error { - filter := bson.M{"_id": promoCode.ID, "usageMap." + userID: bson.M{"$exists": true}} - count, err := r.mdb.CountDocuments(ctx, filter) - if err != nil { - return err - } - - if count >= 1 { - return ErrPromoCodeAlreadyActivated - } - var key string if req.FastLink != "" { key = req.FastLink } else { - key = req.Codeword + key = "-" + } + + var promoCodeStats models.PromoCodeStats + err := r.mdb.FindOne(ctx, bson.M{"_id": promoCode.ID}).Decode(&promoCodeStats) + if err != nil && mongo.ErrNoDocuments == nil { + return err + } + + if promoCodeStats.UsageMap != nil { + usageList := promoCodeStats.UsageMap[key] + for _, usage := range usageList { + if usage.UserID == userID { + return ErrPromoCodeAlreadyActivated + } + } } usage := models.Usage{ - Key: key, - Time: time.Now(), + UserID: userID, + Time: uint64(time.Now().Unix()), } update := bson.M{ - "$inc": bson.M{"usageCount": 1}, "$push": bson.M{ - "usageMap." + userID: usage, + "usageMap." + key: usage, }, } @@ -54,10 +58,10 @@ func (r *StatsRepository) UpdateStatistics(ctx context.Context, req *models.Acti return err } -func (r *StatsRepository) GetStatistics(ctx context.Context, promoCodeID string) (*models.PromoCodeStats, error) { +func (r *StatsRepository) GetStatistics(ctx context.Context, promoCodeID string) (models.PromoCodeStats, error) { objID, err := primitive.ObjectIDFromHex(promoCodeID) if err != nil { - return nil, err + return models.PromoCodeStats{}, err } filter := bson.M{"_id": objID} @@ -65,8 +69,8 @@ func (r *StatsRepository) GetStatistics(ctx context.Context, promoCodeID string) var promoCodeStats models.PromoCodeStats err = r.mdb.FindOne(ctx, filter).Decode(&promoCodeStats) if err != nil { - return nil, err + return models.PromoCodeStats{}, err } - return &promoCodeStats, nil + return promoCodeStats, nil } diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index 3947f3e..dcb55a2 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -27,7 +27,7 @@ type PromoCodeRepository interface { type PromoStatsRepository interface { UpdateStatistics(ctx context.Context, req *models.ActivateReq, promoCode *models.PromoCode, userID string) error - GetStatistics(ctx context.Context, promoCodeID string) (*models.PromoCodeStats, error) + GetStatistics(ctx context.Context, promoCodeID string) (models.PromoCodeStats, error) } type PromoDeps struct { @@ -201,11 +201,35 @@ func (s *PromoCodeService) CreateFastLink(ctx context.Context, promoCodeID strin return xid, nil } -func (s *PromoCodeService) GetStats(ctx context.Context, promoCodeID string) (*models.PromoCodeStats, error) { - promoStats, err := s.statsRepo.GetStatistics(ctx, promoCodeID) +func (s *PromoCodeService) GetStats(ctx context.Context, req models.PromoStatReq) (models.PromoCodeStatsResp, error) { + promoStats, err := s.statsRepo.GetStatistics(ctx, req.PromoCodeID) if err != nil { s.logger.Error("Failed getting promo stats", zap.Error(err)) - return nil, err + return models.PromoCodeStatsResp{}, err } - return promoStats, nil + + var resp models.PromoCodeStatsResp + + stats := make(map[string]int) + + for key, usageCount := range promoStats.UsageMap { + count := 0 + for _, usage := range usageCount { + if (req.From == 0 || usage.Time >= req.From) && (req.To == 0 || usage.Time <= req.To) { + count++ + } + } + stats[key] = count + } + + totalUsageCount := 0 + for _, count := range stats { + totalUsageCount += count + } + + resp.UsageMap = stats + resp.UsageCount = totalUsageCount + resp.ID = req.PromoCodeID + + return resp, nil } From b177a804d3873e270002ae7020ab6073246c27e6 Mon Sep 17 00:00:00 2001 From: skeris Date: Fri, 29 Mar 2024 21:48:36 +0300 Subject: [PATCH 58/66] -- --- internal/controller/promocode/promocode_controller.go | 2 +- internal/repository/promocode_stats.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index ab0525e..4e4158f 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -202,7 +202,7 @@ func (p *PromoCodeController) GetStats(c *fiber.Ctx) error { promoStats, err := p.promoCodeService.GetStats(c.Context(), req) 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.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error: "+err.Error()}) } return c.Status(fiber.StatusOK).JSON(promoStats) } diff --git a/internal/repository/promocode_stats.go b/internal/repository/promocode_stats.go index 218709f..dcca919 100644 --- a/internal/repository/promocode_stats.go +++ b/internal/repository/promocode_stats.go @@ -69,6 +69,9 @@ func (r *StatsRepository) GetStatistics(ctx context.Context, promoCodeID string) var promoCodeStats models.PromoCodeStats err = r.mdb.FindOne(ctx, filter).Decode(&promoCodeStats) if err != nil { + if err == mongo.ErrNoDocuments { + return models.PromoCodeStats{}, nil + } return models.PromoCodeStats{}, err } From bfe80c2500643d21dd2984e39e95363571e98132 Mon Sep 17 00:00:00 2001 From: Pavel Date: Mon, 8 Apr 2024 20:42:04 +0300 Subject: [PATCH 59/66] add activationLimit --- docs/openapi.yaml | 6 ++++ internal/models/bonus.go | 2 ++ internal/repository/promocode_repository.go | 7 ++-- internal/services/promocode_service.go | 36 ++++++++++----------- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 6f64c28..cfd2a32 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -433,6 +433,9 @@ components: type: integer nullable: true description: Количество активаций промокода + ActivationLimit: + type: integer + description: Лимит, есть или нет если 0 то нет Delete: type: boolean nullable: true @@ -455,6 +458,9 @@ components: activationCount: type: integer description: Количество активаций промокода + activationLimit: + type: integer + description: Лимит, есть или нет если 0 то нет bonus: type: object description: Бонус diff --git a/internal/models/bonus.go b/internal/models/bonus.go index 13333b5..ccbdc7d 100644 --- a/internal/models/bonus.go +++ b/internal/models/bonus.go @@ -12,6 +12,7 @@ type PromoCode struct { Greetings string `json:"greetings" bson:"greetings"` // текст, выдаваемый пользователю в ответ на активацию промокода DueTo int64 `json:"dueTo" bson:"dueTo"` // таймштамп времени окончания работы активации промокода ActivationCount int64 `json:"activationCount" bson:"activationCount"` // предел количества активаций промокода + ActivationLimit int64 `json:"activationLimit" bson:"activationLimit"` // лимит если 0 то без лимита Bonus struct { Privilege struct { PrivilegeID string `json:"privilegeID" bson:"privilegeID"` // айдишник привилегии, которая будет выдаваться @@ -38,6 +39,7 @@ type ReqEditPromoCode struct { DueTo *int64 `json:"dueTo,omitempty" bson:"dueTo"` // таймштамп времени окончания работы активации промокода ActivationCount *int64 `json:"activationCount,omitempty" bson:"activationCount"` // предел количества активаций промокода + ActivationLimit *int64 `json:"activationLimit,omitempty" bson:"activationLimit"` // лимит если 0 то без лимита Delete *bool `json:"delete,omitempty" bson:"delete"` diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index a3e7a92..27fa945 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -95,6 +95,9 @@ func (r *PromoCodeRepository) EditPromoCode(ctx context.Context, req *models.Req if req.ActivationCount != nil { updateFields["activationCount"] = *req.ActivationCount } + if req.ActivationLimit != nil { + updateFields["activationLimit"] = *req.ActivationLimit + } if req.Delete != nil { updateFields["delete"] = *req.Delete } @@ -221,12 +224,12 @@ func (r *PromoCodeRepository) ActivatePromo(ctx context.Context, req *models.Act if req.Codeword != "" { filter = bson.M{ "codeword": req.Codeword, - "delete": false, + "delete": false, } } else if req.FastLink != "" { filter = bson.M{ "fastLinks": req.FastLink, - "delete": false, + "delete": false, } } diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index 3904ccc..3a75698 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -98,18 +98,18 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa return "", err } //todo такая реализация проверок кажется довольно массивной, думаю как то это стоит сделать параллельно обхаживая все условия - if promoCode.DueTo < time.Now().Unix() && promoCode.DueTo > 0 { + if promoCode.DueTo < time.Now().Unix() && promoCode.DueTo > 0 && promoCode.ActivationLimit != 0 { err := s.promoCodeRepo.IncreaseActivationCount(ctx, promoCode.ID) - fmt.Println("SKER21", err) + fmt.Println("SKER21", err) if err != nil { return "", err } return "", fmt.Errorf("%w: expired on %s", repository.ErrPromoCodeExpired, time.Unix(promoCode.DueTo, 0).Format(time.RFC3339)) } - if promoCode.DueTo == 0 && promoCode.ActivationCount < 0 { + if promoCode.DueTo == 0 && promoCode.ActivationCount < 0 && promoCode.ActivationLimit != 0 { err := s.promoCodeRepo.IncreaseActivationCount(ctx, promoCode.ID) - fmt.Println("SKER22", err) + fmt.Println("SKER22", err) if err != nil { return "", err } @@ -167,24 +167,24 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa Layer: promoCode.Bonus.Discount.Layer, Description: "", Condition: &discount.DiscountCondition{ - Coupon: &promoCode.Codeword, - User: &userID, - Group: &promoCode.Bonus.Discount.Target, - Product: &promoCode.Bonus.Discount.Target, - UserType: &emptyString, - PurchasesAmount: &zero, + Coupon: &promoCode.Codeword, + User: &userID, + Group: &promoCode.Bonus.Discount.Target, + Product: &promoCode.Bonus.Discount.Target, + UserType: &emptyString, + PurchasesAmount: &zero, CartPurchasesAmount: &zero, - Term: &zero, - Usage: &zero, - PriceFrom: &zero, + Term: &zero, + Usage: &zero, + PriceFrom: &zero, }, } if promoCode.Bonus.Discount.Layer == 1 { discountRequest.Target = &discount.DiscountCalculationTarget{ Products: []*discount.ProductTarget{{ - ID: promoCode.Bonus.Discount.Target, - Factor: promoCode.Bonus.Discount.Factor, + ID: promoCode.Bonus.Discount.Target, + Factor: promoCode.Bonus.Discount.Factor, }}, Overhelm: &disOverHelm, } @@ -193,11 +193,11 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa if promoCode.Bonus.Discount.Layer == 2 { discountRequest.Target = &discount.DiscountCalculationTarget{ TargetGroup: &promoCode.Bonus.Discount.Target, - Factor: promoCode.Bonus.Discount.Factor, - Overhelm: &disOverHelm, + Factor: promoCode.Bonus.Discount.Factor, + Overhelm: &disOverHelm, } } - + _, err = s.discountClient.CreateDiscount(ctx, discountRequest) if err != nil { s.logger.Error("Failed to create discount", zap.Error(err)) From e8ad68791ffc09b3fdf7aa07839a2d82a3049e36 Mon Sep 17 00:00:00 2001 From: skeris Date: Tue, 9 Apr 2024 01:07:43 +0300 Subject: [PATCH 60/66] -- --- internal/controller/promocode/promocode_controller.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index 4e4158f..630a416 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -40,6 +40,8 @@ func (p *PromoCodeController) CreatePromoCode(c *fiber.Ctx) error { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "codeword is required"}) } + req.ActivationLimit = req.ActivationCount + createdPromoCode, err := p.promoCodeService.CreatePromoCode(c.Context(), &req) if err != nil { p.logger.Error("Failed to create promocode", zap.Error(err)) @@ -64,6 +66,8 @@ func (p *PromoCodeController) EditPromoCode(c *fiber.Ctx) error { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "promocode ID is required"}) } + req.ActivationLimit = req.ActivationCount + editedPromoCode, err := p.promoCodeService.EditPromoCode(c.Context(), &req) if err != nil { p.logger.Error("Failed to edit promocode", zap.Error(err)) From 11d85ac864f2e9f9c0bfecab37834105f620d622 Mon Sep 17 00:00:00 2001 From: Pavel Date: Tue, 9 Apr 2024 22:03:20 +0300 Subject: [PATCH 61/66] add ServiceKey --- docs/openapi.yaml | 3 +++ internal/models/bonus.go | 2 ++ internal/repository/promocode_repository.go | 3 +++ internal/services/promocode_service.go | 1 + 4 files changed, 9 insertions(+) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index cfd2a32..b1a7323 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -491,6 +491,9 @@ components: amount: type: integer description: Количество привилегии + serviceKey: + type: string + description: Тип сервиса Discount: type: object diff --git a/internal/models/bonus.go b/internal/models/bonus.go index ccbdc7d..f05ac94 100644 --- a/internal/models/bonus.go +++ b/internal/models/bonus.go @@ -17,6 +17,7 @@ type PromoCode struct { Privilege struct { PrivilegeID string `json:"privilegeID" bson:"privilegeID"` // айдишник привилегии, которая будет выдаваться Amount uint64 `json:"amount" bson:"amount"` // количество + ServiceKey string `json:"serviceKey" bson:"serviceKey"` // тип сервиса } `json:"privilege" bson:"privilege"` Discount struct { Layer uint32 `json:"layer" bson:"layer"` // 1|2 @@ -47,6 +48,7 @@ type ReqEditPromoCode struct { Privilege *struct { PrivilegeID string `json:"privilegeID,omitempty" bson:"privilegeID"` Amount uint64 `json:"amount,omitempty" bson:"amount"` + ServiceKey string `json:"serviceKey,omitempty" bson:"serviceKey"` // тип сервиса } `json:"privilege,omitempty" bson:"privilege"` Discount *struct { diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index 27fa945..caf9682 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -110,6 +110,9 @@ func (r *PromoCodeRepository) EditPromoCode(ctx context.Context, req *models.Req if req.Bonus.Privilege.Amount != 0 { updateFields["bonus.privilege.amount"] = req.Bonus.Privilege.Amount } + if req.Bonus.Privilege.ServiceKey != "" { + updateFields["bonus.privilege.serviceKey"] = req.Bonus.Privilege.ServiceKey + } } if req.Bonus.Discount != nil { if req.Bonus.Discount.Layer != 0 { diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index 3a75698..33614fe 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -142,6 +142,7 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa privilege := models.Privilege{ PrivilegeID: promoCode.Bonus.Privilege.PrivilegeID, Amount: promoCode.Bonus.Privilege.Amount, + ServiceKey: promoCode.Bonus.Privilege.ServiceKey, } privileges = append(privileges, privilege) From 84456031a006f942dd5aa84710cb4b261882748e Mon Sep 17 00:00:00 2001 From: Mikhail Date: Thu, 11 Apr 2024 18:45:00 +0000 Subject: [PATCH 62/66] Update producer.go --- internal/kafka/tariff/producer.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/kafka/tariff/producer.go b/internal/kafka/tariff/producer.go index 69a5e92..f386513 100644 --- a/internal/kafka/tariff/producer.go +++ b/internal/kafka/tariff/producer.go @@ -45,6 +45,8 @@ func (p *Producer) Send(ctx context.Context, userID string, tariff *models.Tarif return err } + p.logger.Info("PRESEND", zap.String(string(bytes))) + // упростил, возможно зря, но теперь возвращаем одну ошибку, просто прерываем цикл при первой встретившейся ошибке err = p.client.ProduceSync(ctx, &kgo.Record{Topic: p.topic, Value: bytes}).FirstErr() if err != nil { From 17666e2577608c5dee4a76870991a0d1247eca12 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Thu, 11 Apr 2024 18:48:08 +0000 Subject: [PATCH 63/66] Update producer.go --- internal/kafka/tariff/producer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/kafka/tariff/producer.go b/internal/kafka/tariff/producer.go index f386513..537f5db 100644 --- a/internal/kafka/tariff/producer.go +++ b/internal/kafka/tariff/producer.go @@ -45,7 +45,7 @@ func (p *Producer) Send(ctx context.Context, userID string, tariff *models.Tarif return err } - p.logger.Info("PRESEND", zap.String(string(bytes))) + p.logger.Info("PRESEND", zap.String("data", string(bytes))) // упростил, возможно зря, но теперь возвращаем одну ошибку, просто прерываем цикл при первой встретившейся ошибке err = p.client.ProduceSync(ctx, &kgo.Record{Topic: p.topic, Value: bytes}).FirstErr() From 09ba8649a56d6656b5a401e3c368201b1e4dce11 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Thu, 11 Apr 2024 18:58:29 +0000 Subject: [PATCH 64/66] Update 2 files - /internal/utils/transfer/privilege.go - /internal/kafka/tariff/producer.go --- internal/kafka/tariff/producer.go | 1 + internal/utils/transfer/privilege.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/internal/kafka/tariff/producer.go b/internal/kafka/tariff/producer.go index 537f5db..9f2cd71 100644 --- a/internal/kafka/tariff/producer.go +++ b/internal/kafka/tariff/producer.go @@ -39,6 +39,7 @@ func NewProducer(deps ProducerDeps) *Producer { } func (p *Producer) Send(ctx context.Context, userID string, tariff *models.Tariff) error { + fmt.Println("PMTP0", tariff) bytes, err := proto.Marshal(transfer.TariffModelToProtoMessage(userID, tariff)) if err != nil { p.logger.Error("failed to marshal tariff model", zap.Error(err)) diff --git a/internal/utils/transfer/privilege.go b/internal/utils/transfer/privilege.go index 60bb9c5..6affadc 100644 --- a/internal/utils/transfer/privilege.go +++ b/internal/utils/transfer/privilege.go @@ -3,12 +3,14 @@ package transfer import ( "codeword/internal/models" "codeword/internal/proto/broker" + "fmt" ) func PrivilegeModelToProto(privilege *models.Privilege) *broker.PrivilegeMessage { if privilege == nil { return &broker.PrivilegeMessage{} } + fmt.Println("PMTP", privilege.ServiceKey) return &broker.PrivilegeMessage{ PrivilegeID: privilege.PrivilegeID, From f9b4033a6d930d629449bc3c407e0f8c0b413bd8 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Thu, 11 Apr 2024 19:01:31 +0000 Subject: [PATCH 65/66] Update file producer.go --- internal/kafka/tariff/producer.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/kafka/tariff/producer.go b/internal/kafka/tariff/producer.go index 9f2cd71..b7fd098 100644 --- a/internal/kafka/tariff/producer.go +++ b/internal/kafka/tariff/producer.go @@ -8,6 +8,7 @@ import ( "go.uber.org/zap" "google.golang.org/protobuf/proto" "log" + "fmt" ) type ProducerDeps struct { From ffa0ce41746c77481b5e07f019b9268679ab3c11 Mon Sep 17 00:00:00 2001 From: skeris Date: Tue, 16 Apr 2024 10:21:05 +0300 Subject: [PATCH 66/66] fix typo in recovery url config env tag --- deployments/staging/docker-compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployments/staging/docker-compose.yaml b/deployments/staging/docker-compose.yaml index 6c53ee7..24af869 100644 --- a/deployments/staging/docker-compose.yaml +++ b/deployments/staging/docker-compose.yaml @@ -31,7 +31,7 @@ services: SMTP_SENDER: 'noreply@mailing.pena.digital' DEFAULT_REDIRECTION_URL: 'https://shub.pena.digital/' AUTH_EXCHANGE_URL: 'http://10.6.0.11:59300/auth/exchange' - RECOVER_URL: 'https://shub.pena.digital/codeword/recover/' + RECOVERY_URL: 'https://shub.pena.digital/codeword/recover/' JWT_AUDIENCE: 'pena' JWT_ISSUER: 'pena-auth-service' JWT_PUBLIC_KEY: $JWT_PUBLIC_KEY