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 }