update stats

This commit is contained in:
Pavel 2024-03-27 15:29:49 +03:00
parent 3ba931a093
commit 7d555618fb
6 changed files with 88 additions and 54 deletions

@ -224,12 +224,12 @@ paths:
description: Внутренняя ошибка сервера description: Внутренняя ошибка сервера
/promocode/stats: /promocode/stats:
get: post:
operationId: GetStats operationId: GetStats
summary: Получить статистику промокода summary: Получить статистику промокода
tags: tags:
- stats - stats
description: Идентификатор промокода description: Идентификатор промокода и временной интерфал от до unix
requestBody: requestBody:
required: true required: true
content: content:
@ -266,37 +266,34 @@ components:
PromoCodeStatsReq: PromoCodeStatsReq:
type: object type: object
properties: properties:
promoCodeID: id:
type: string type: string
from:
type: integer
to:
type: integer
required: required:
- promoCodeID - id
PromoCodeStatsResp: PromoCodeStatsResp:
type: object type: object
properties: properties:
id: id:
type: string type: string
description: Идентификатор промокода description: id промокода
usageCount: usageCount:
type: integer type: integer
description: Количество использований промокода description: общее количество использований промокода
example: 18
usageMap: usageMap:
type: object type: object
description: Карта использования промокода description: мапа использования промокода ранжированая по способу
additionalProperties: additionalProperties:
type: array type: integer
items: example:
$ref: '#/components/schemas/Usage' "-": 10
"fastlinkID1": 5
"fastlinkID2": 3
Usage:
type: object
properties:
key:
type: string
description: fastlink или codeword в зависимости от того что применялось
time:
type: string
format: date-time
description: Время использования промокода
CreateFastLinkReq: CreateFastLinkReq:
type: object type: object

@ -184,9 +184,7 @@ func (p *PromoCodeController) CreateFastLink(c *fiber.Ctx) error {
} }
func (p *PromoCodeController) GetStats(c *fiber.Ctx) error { func (p *PromoCodeController) GetStats(c *fiber.Ctx) error {
var req struct { var req models.PromoStatReq
PromoCodeID string `json:"id"`
}
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) 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"}) 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 { 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.GetStats) router.Post("/stats", p.GetStats)
} }

@ -81,13 +81,24 @@ type ActivateResp struct {
Greetings string `json:"greetings"` // поле из активированного промокода Greetings string `json:"greetings"` // поле из активированного промокода
} }
type PromoStatReq struct {
PromoCodeID string `json:"id,omitempty"`
From uint64 `json:"from"`
To uint64 `json:"to"`
}
type PromoCodeStats struct { type PromoCodeStats struct {
ID string `bson:"_id,omitempty" json:"id,omitempty"` ID string `json:"id,omitempty"`
UsageCount int `bson:"usageCount" json:"usageCount"` UsageMap map[string][]Usage `json:"usageMap"`
UsageMap map[string][]Usage `bson:"usageMap" json:"usageMap"`
} }
type Usage struct { type Usage struct {
Key string `bson:"key" json:"key"` UserID string `bson:"userID" json:"userID"`
Time time.Time `bson:"time" json:"time"` Time uint64 `bson:"time" json:"time"`
}
type PromoCodeStatsResp struct {
ID string `json:"id,omitempty"`
UsageCount int `json:"usageCount"`
UsageMap map[string]int `json:"usageMap"`
} }

@ -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 { 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 var key string
if req.FastLink != "" { if req.FastLink != "" {
key = req.FastLink key = req.FastLink
} else { } 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{ usage := models.Usage{
Key: key, UserID: userID,
Time: time.Now(), Time: uint64(time.Now().Unix()),
} }
update := bson.M{ update := bson.M{
"$inc": bson.M{"usageCount": 1},
"$push": bson.M{ "$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 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) objID, err := primitive.ObjectIDFromHex(promoCodeID)
if err != nil { if err != nil {
return nil, err return models.PromoCodeStats{}, err
} }
filter := bson.M{"_id": objID} filter := bson.M{"_id": objID}
@ -65,8 +69,8 @@ func (r *StatsRepository) GetStatistics(ctx context.Context, promoCodeID string)
var promoCodeStats models.PromoCodeStats var promoCodeStats models.PromoCodeStats
err = r.mdb.FindOne(ctx, filter).Decode(&promoCodeStats) err = r.mdb.FindOne(ctx, filter).Decode(&promoCodeStats)
if err != nil { if err != nil {
return nil, err return models.PromoCodeStats{}, err
} }
return &promoCodeStats, nil return promoCodeStats, nil
} }

@ -27,7 +27,7 @@ type PromoCodeRepository interface {
type PromoStatsRepository interface { type PromoStatsRepository interface {
UpdateStatistics(ctx context.Context, req *models.ActivateReq, promoCode *models.PromoCode, userID string) error 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 { type PromoDeps struct {
@ -201,11 +201,35 @@ func (s *PromoCodeService) CreateFastLink(ctx context.Context, promoCodeID strin
return xid, nil return xid, nil
} }
func (s *PromoCodeService) GetStats(ctx context.Context, promoCodeID string) (*models.PromoCodeStats, error) { func (s *PromoCodeService) GetStats(ctx context.Context, req models.PromoStatReq) (models.PromoCodeStatsResp, error) {
promoStats, err := s.statsRepo.GetStatistics(ctx, promoCodeID) promoStats, err := s.statsRepo.GetStatistics(ctx, req.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 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
} }