change promo history

This commit is contained in:
Pavel 2024-03-03 21:24:28 +03:00
parent ca0e4f7708
commit 0b17ab396f
7 changed files with 87 additions and 41 deletions

@ -313,8 +313,18 @@ paths:
error: error:
type: string type: string
/promocode/stats: /promocode/stats:
get: post:
summary: Получить статистику промокода summary: Получить статистику промокода
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
promoCodeID:
type: string
description: Идентификатор промокода
responses: responses:
'200': '200':
description: Статистика промокода успешно получена description: Статистика промокода успешно получена
@ -322,6 +332,15 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/PromoCodeStats' $ref: '#/components/schemas/PromoCodeStats'
'400':
description: Неверный запрос
content:
application/json:
schema:
type: object
properties:
error:
type: string
'500': '500':
description: Внутренняя ошибка сервера description: Внутренняя ошибка сервера
content: content:

@ -184,8 +184,16 @@ func (p *PromoCodeController) CreateFastLink(c *fiber.Ctx) error {
return c.Status(fiber.StatusCreated).JSON(fiber.Map{"fastlink": fastLink}) return c.Status(fiber.StatusCreated).JSON(fiber.Map{"fastlink": fastLink})
} }
func (p *PromoCodeController) GetAllStats(c *fiber.Ctx) error { func (p *PromoCodeController) GetStats(c *fiber.Ctx) error {
promoStats, err := p.promoCodeService.GetAllStats(c.Context()) 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 { if err != nil {
p.logger.Error("Failed getting promo stats", zap.Error(err)) 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"})

@ -9,7 +9,7 @@ func (p *PromoCodeController) Register(router fiber.Router) {
router.Post("/activate", p.Activate) router.Post("/activate", p.Activate)
router.Delete("/:promocodeID", p.Delete) router.Delete("/:promocodeID", p.Delete)
router.Post("/fastlink", p.CreateFastLink) router.Post("/fastlink", p.CreateFastLink)
router.Get("/stats", p.GetAllStats) router.Get("/stats", p.GetStats)
} }

@ -68,7 +68,12 @@ type ActivateResp struct {
} }
type PromoCodeStats struct { type PromoCodeStats struct {
ID string `bson:"_id,omitempty" json:"id,omitempty"` ID string `bson:"_id,omitempty" json:"id,omitempty"`
UsageCount map[string]int `bson:"usageCount" json:"usageCount"` UsageCount int `bson:"usageCount" json:"usageCount"`
UsageHistory map[string][]time.Time `bson:"usageHistory" json:"usageHistory"` UsageMap map[string][]Usage `bson:"usageMap" json:"usageMap"`
}
type Usage struct {
Key string `bson:"key" json:"key"`
Time time.Time `bson:"time" json:"time"`
} }

@ -4,6 +4,7 @@ import (
"codeword/internal/models" "codeword/internal/models"
"context" "context"
"go.mongodb.org/mongo-driver/bson" "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"
"go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/options"
"time" "time"
@ -18,53 +19,54 @@ func NewStatsRepository(deps Deps) *StatsRepository {
return &StatsRepository{mdb: deps.Mdb} return &StatsRepository{mdb: deps.Mdb}
} }
func (r *StatsRepository) UpdateStatistics(ctx context.Context, key, userID string) error { func (r *StatsRepository) UpdateStatistics(ctx context.Context, req *models.ActivateReq, promoCode *models.PromoCode, userID string) error {
filter := bson.M{"_id": key, "usageCount." + userID: bson.M{"$exists": true}} filter := bson.M{"_id": promoCode.ID, "usageMap." + userID: bson.M{"$exists": true}}
count, err := r.mdb.CountDocuments(ctx, filter) count, err := r.mdb.CountDocuments(ctx, filter)
if err != nil { if err != nil {
return err return err
} }
if count > 0 { if count == 1 {
return ErrPromoCodeAlreadyActivated 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{ update := bson.M{
"$inc": bson.M{"usageCount." + userID: 1}, "$inc": bson.M{"usageCount": 1},
"$push": bson.M{"usageHistory." + userID: time.Now()}, "$push": bson.M{
"usageMap." + userID: usage,
},
} }
opts := options.Update().SetUpsert(true) opts := options.Update().SetUpsert(true)
filter = bson.M{"_id": key} _, err = r.mdb.UpdateOne(ctx, bson.M{"_id": promoCode.ID}, update, opts)
_, err = r.mdb.UpdateOne(ctx, filter, update, opts)
return err return err
} }
func (r *StatsRepository) GetAllStatistics(ctx context.Context) ([]models.PromoCodeStats, error) { func (r *StatsRepository) GetStatistics(ctx context.Context, promoCodeID string) (*models.PromoCodeStats, error) {
filter := bson.M{} objID, err := primitive.ObjectIDFromHex(promoCodeID)
opts := options.Find()
cursor, err := r.mdb.Find(ctx, filter, opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer cursor.Close(ctx)
var promoCodeStatsList []models.PromoCodeStats filter := bson.M{"_id": objID}
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 { var promoCodeStats models.PromoCodeStats
err = r.mdb.FindOne(ctx, filter).Decode(&promoCodeStats)
if err != nil {
return nil, err return nil, err
} }
return promoCodeStatsList, nil return &promoCodeStats, nil
} }

@ -26,8 +26,8 @@ type PromoCodeRepository interface {
} }
type PromoStatsRepository interface { type PromoStatsRepository interface {
UpdateStatistics(ctx context.Context, key, userID string) error UpdateStatistics(ctx context.Context, req *models.ActivateReq, promoCode *models.PromoCode, userID string) error
GetAllStatistics(ctx context.Context) ([]models.PromoCodeStats, error) GetStatistics(ctx context.Context, promoCodeID string) (*models.PromoCodeStats, error)
} }
type PromoDeps struct { type PromoDeps struct {
@ -113,7 +113,7 @@ func (s *PromoCodeService) ActivatePromo(ctx context.Context, req *models.Activa
return "", repository.ErrPromoCodeExhausted return "", repository.ErrPromoCodeExhausted
} }
err = s.statsRepo.UpdateStatistics(ctx, promoCode.ID.Hex(), userID) err = s.statsRepo.UpdateStatistics(ctx, req, promoCode, userID)
if err != nil { if err != nil {
if errors.Is(err, repository.ErrPromoCodeAlreadyActivated) { if errors.Is(err, repository.ErrPromoCodeAlreadyActivated) {
err := s.promoCodeRepo.IncreaseActivationCount(ctx, promoCode.ID) err := s.promoCodeRepo.IncreaseActivationCount(ctx, promoCode.ID)
@ -201,8 +201,8 @@ func (s *PromoCodeService) CreateFastLink(ctx context.Context, promoCodeID strin
return xid, nil return xid, nil
} }
func (s *PromoCodeService) GetAllStats(ctx context.Context) ([]models.PromoCodeStats, error) { func (s *PromoCodeService) GetStats(ctx context.Context, promoCodeID string) (*models.PromoCodeStats, error) {
promoStats, err := s.statsRepo.GetAllStatistics(ctx) promoStats, err := s.statsRepo.GetStatistics(ctx, promoCodeID)
if err != nil { if err != nil {
s.logger.Error("Failed getting promo stats", zap.Error(err)) s.logger.Error("Failed getting promo stats", zap.Error(err))
return nil, err return nil, err

@ -316,7 +316,11 @@ func TestCreateFastLink(t *testing.T) {
client := fiber.AcquireClient() client := fiber.AcquireClient()
t.Run("CreateFastLink-success", func(t *testing.T) { 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) reqJSON, _ := json.Marshal(reqBody)
@ -326,7 +330,7 @@ func TestCreateFastLink(t *testing.T) {
if len(errs) != 0 { if len(errs) != 0 {
assert.NoError(t, errs[0]) assert.NoError(t, errs[0])
} }
fmt.Println(string(resBody))
assert.Equal(t, fiber.StatusCreated, statusCode) assert.Equal(t, fiber.StatusCreated, statusCode)
var response map[string]string var response map[string]string
@ -537,7 +541,15 @@ func TestGetPromoStats(t *testing.T) {
client := fiber.AcquireClient() client := fiber.AcquireClient()
t.Run("GetAllStats", func(t *testing.T) { 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() statusCode, resBody, errs := req.Bytes()
if len(errs) != 0 { if len(errs) != 0 {