diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 249c2fe..aff5967 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -193,6 +193,70 @@ 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: Внутренняя ошибка сервера + + /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: @@ -386,4 +450,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 diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index dfb23fb..903aef0 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -82,3 +82,51 @@ func (p *PromoCodeController) GetList(c *fiber.Ctx) error { Items: promoCodes, }) } + +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) +} + +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/models/bonus.go b/internal/models/bonus.go index f066fa1..de7de8c 100644 --- a/internal/models/bonus.go +++ b/internal/models/bonus.go @@ -70,3 +70,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 2206d48..5b0f04c 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -213,3 +213,69 @@ 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 +} + +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 1c32722..3133054 100644 --- a/internal/server/http/http_server.go +++ b/internal/server/http/http_server.go @@ -60,6 +60,8 @@ 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) + s.app.Delete("/promocode/:promocodeID", s.PromoCodeController.Delete) //... other } diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index 11cd344..2197c85 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -10,6 +10,8 @@ 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) + DeletePromoCode(ctx context.Context, promoCodeID string) error } type PromoDeps struct { @@ -58,3 +60,23 @@ 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 +} + +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 +}