добавлены разграничения в ошибках, убрана транзакция
This commit is contained in:
parent
cbd82e8779
commit
ca0e4f7708
@ -103,7 +103,6 @@ func (p *PromoCodeController) Activate(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
userID := c.Locals(models.AuthJWTDecodedUserIDKey).(string)
|
userID := c.Locals(models.AuthJWTDecodedUserIDKey).(string)
|
||||||
|
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "failed to get jwt payload"})
|
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 {
|
if err != nil {
|
||||||
p.logger.Error("Failed to activate promocode", zap.Error(err))
|
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"})
|
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})
|
return c.Status(fiber.StatusOK).JSON(models.ActivateResp{Greetings: greetings})
|
||||||
|
|||||||
@ -3,8 +3,11 @@ package repository
|
|||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrPromoUserNotFound = errors.New("user not found")
|
ErrPromoUserNotFound = errors.New("user not found")
|
||||||
ErrAlreadyReported = errors.New("already reported")
|
ErrAlreadyReported = errors.New("already reported")
|
||||||
ErrDuplicateCodeword = errors.New("duplicate codeword")
|
ErrDuplicateCodeword = errors.New("duplicate codeword")
|
||||||
ErrPromoCodeNotFound = errors.New("promo code not found")
|
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")
|
||||||
)
|
)
|
||||||
|
|||||||
@ -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) {
|
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
|
var promoCode models.PromoCode
|
||||||
|
|
||||||
transactionErr := mongo.WithSession(ctx, session, func(sc mongo.SessionContext) error {
|
var filter bson.M
|
||||||
var filter bson.M
|
if req.Codeword != "" {
|
||||||
|
filter = bson.M{
|
||||||
if req.Codeword != "" {
|
"codeword": 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 if req.FastLink != "" {
|
||||||
update := bson.M{
|
filter = bson.M{
|
||||||
"$inc": bson.M{"activationCount": -1},
|
"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
|
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 {
|
func (r *PromoCodeRepository) DeletePromoCode(ctx context.Context, promoCodeID string) error {
|
||||||
id, err := primitive.ObjectIDFromHex(promoCodeID)
|
id, err := primitive.ObjectIDFromHex(promoCodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -19,15 +19,26 @@ func NewStatsRepository(deps Deps) *StatsRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *StatsRepository) UpdateStatistics(ctx context.Context, key, userID string) error {
|
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{
|
update := bson.M{
|
||||||
"$inc": bson.M{"usageCount." + userID: 1},
|
"$inc": bson.M{"usageCount." + userID: 1},
|
||||||
"$push": bson.M{"usageHistory." + userID: time.Now()},
|
"$push": bson.M{"usageHistory." + userID: time.Now()},
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := options.Update().SetUpsert(true)
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,11 +4,14 @@ import (
|
|||||||
"codeword/internal/kafka/tariff"
|
"codeword/internal/kafka/tariff"
|
||||||
"codeword/internal/models"
|
"codeword/internal/models"
|
||||||
"codeword/internal/proto/discount"
|
"codeword/internal/proto/discount"
|
||||||
|
"codeword/internal/repository"
|
||||||
"codeword/internal/utils/genID"
|
"codeword/internal/utils/genID"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PromoCodeRepository interface {
|
type PromoCodeRepository interface {
|
||||||
@ -19,6 +22,7 @@ type PromoCodeRepository interface {
|
|||||||
DeletePromoCode(ctx context.Context, promoCodeID string) error
|
DeletePromoCode(ctx context.Context, promoCodeID string) error
|
||||||
GetPromoCodeByID(ctx context.Context, promoCodeID primitive.ObjectID) (*models.PromoCode, error)
|
GetPromoCodeByID(ctx context.Context, promoCodeID primitive.ObjectID) (*models.PromoCode, error)
|
||||||
AddFastLink(ctx context.Context, promoCodeID primitive.ObjectID, xid string) error
|
AddFastLink(ctx context.Context, promoCodeID primitive.ObjectID, xid string) error
|
||||||
|
IncreaseActivationCount(ctx context.Context, promoCodeID primitive.ObjectID) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type PromoStatsRepository interface {
|
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))
|
s.logger.Error("Failed to activate promocode", zap.Error(err))
|
||||||
return "", 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)
|
err = s.statsRepo.UpdateStatistics(ctx, promoCode.ID.Hex(), userID)
|
||||||
if err != nil {
|
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))
|
s.logger.Error("Failed add in stats", zap.Error(err))
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -466,7 +466,6 @@ func TestActivatePromoCode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, fiber.StatusOK, statusCode)
|
assert.Equal(t, fiber.StatusOK, statusCode)
|
||||||
|
|
||||||
var response models.ActivateResp
|
var response models.ActivateResp
|
||||||
err := json.Unmarshal(resBody, &response)
|
err := json.Unmarshal(resBody, &response)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user