From a74a149d06c69d2ee7f8a64187c471044d877299 Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 12 Jan 2024 14:28:22 +0300 Subject: [PATCH 1/2] add base logic for getList --- docs/openapi.yaml | 65 ++++++++++ .../promocode/promocode_controller.go | 33 ++++- internal/models/bonus.go | 16 +++ internal/repository/promocode_repository.go | 121 +++++++++++++++--- internal/server/http/http_server.go | 1 + internal/services/promocode_service.go | 15 ++- 6 files changed, 221 insertions(+), 30 deletions(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 8769379..4c5383f 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -165,6 +165,34 @@ paths: error: type: string + /promocode/getList: + post: + summary: Получить список промокодов + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GetPromoCodesListReq' + responses: + '200': + description: Список промокодов и общее количество успешно получены + content: + application/json: + schema: + $ref: '#/components/schemas/GetPromoCodesListResp' + '400': + description: Неверный запрос из-за невалидных данных + content: + application/json: + schema: + type: object + properties: + error: + type: string + '500': + description: Внутренняя ошибка сервера + components: schemas: @@ -304,3 +332,40 @@ components: description: Флаг удаления промокода required: - id + GetPromoCodesListReq: + type: object + required: + - page + - limit + - filter + properties: + page: + type: integer + description: Номер страницы выборки, начиная с 0 + limit: + type: integer + description: Размер страницы выборки + filter: + $ref: '#/components/schemas/GetPromoCodesListReqFilter' + + GetPromoCodesListReqFilter: + type: object + properties: + text: + type: string + description: Полнотекстовый поиск по полям Codeword, Description, Greetings + active: + type: boolean + description: Если true, выбираются записи, где delete, outdated и offLimit равны false + + GetPromoCodesListResp: + type: object + properties: + count: + type: integer + format: int64 + description: Общее количество промокодов в выборке + items: + type: array + items: + $ref: '#/components/schemas/PromoCodeResponse' \ No newline at end of file diff --git a/internal/controller/promocode/promocode_controller.go b/internal/controller/promocode/promocode_controller.go index 8c854cb..783d4ea 100644 --- a/internal/controller/promocode/promocode_controller.go +++ b/internal/controller/promocode/promocode_controller.go @@ -22,12 +22,12 @@ func NewPromoCodeController(logger *zap.Logger, promoCodeService *services.Promo } func (p *PromoCodeController) CreatePromoCode(c *fiber.Ctx) error { - var reqCreatePromoCode models.PromoCode - if err := c.BodyParser(&reqCreatePromoCode); err != nil { + var req models.PromoCode + if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) } - createdPromoCode, err := p.promoCodeService.CreatePromoCode(c.Context(), &reqCreatePromoCode) + createdPromoCode, err := p.promoCodeService.CreatePromoCode(c.Context(), &req) if err != nil { p.logger.Error("Failed to create promocode", zap.Error(err)) @@ -42,16 +42,16 @@ func (p *PromoCodeController) CreatePromoCode(c *fiber.Ctx) error { } func (p *PromoCodeController) EditPromoCode(c *fiber.Ctx) error { - var reqEditPromoCode models.ReqEditPromoCode - if err := c.BodyParser(&reqEditPromoCode); err != nil { + var req models.ReqEditPromoCode + if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) } - if reqEditPromoCode.ID == "" { + if req.ID == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "promocode ID is required"}) } - editedPromoCode, err := p.promoCodeService.EditPromoCode(c.Context(), &reqEditPromoCode) + editedPromoCode, err := p.promoCodeService.EditPromoCode(c.Context(), &req) if err != nil { p.logger.Error("Failed to edit promocode", zap.Error(err)) @@ -64,3 +64,22 @@ func (p *PromoCodeController) EditPromoCode(c *fiber.Ctx) error { return c.Status(fiber.StatusOK).JSON(editedPromoCode) } + +func (p *PromoCodeController) GetList(c *fiber.Ctx) error { + var req models.GetPromoCodesListReq + if err := c.BodyParser(&req); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"}) + } + + promoCodes, count, err := p.promoCodeService.GetPromoCodesList(c.Context(), &req) + if err != nil { + p.logger.Error("Failed to retrieve promocode list", zap.Error(err)) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) + } + + resp := models.GetPromoCodesListResp{ + Count: count, + Items: promoCodes, + } + return c.Status(fiber.StatusOK).JSON(resp) +} diff --git a/internal/models/bonus.go b/internal/models/bonus.go index 1f3158d..7a4dc9e 100644 --- a/internal/models/bonus.go +++ b/internal/models/bonus.go @@ -40,3 +40,19 @@ type ReqEditPromoCode struct { Delete *bool `json:"delete,omitempty" bson:"delete"` } + +type GetPromoCodesListReqFilter struct { + Text string `json:"text"` // полнотекстовый поиск пo Codeword, Decription, Greetings полям + Active bool `json:"active"` // если true, то выбирать deleted==false && outdated== false && offlimit == false +} + +type GetPromoCodesListReq struct { + Page int `json:"page"` //номер страницы выборки. начинается с 0. по сути, skip для выборки из mongodb + Limit int `json:"limit"` //размер страницы выборки. больше 10, меньше 250. отвечает за skip = page*limit, и за limit + Filter GetPromoCodesListReqFilter `json:"filter"` +} + +type GetPromoCodesListResp struct { + Count int64 `json:"count"` // количество в выборке всего + Items []PromoCode `json:"items"` // "страница" промокодов +} diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index 841537b..eecc193 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -4,6 +4,7 @@ import ( "codeword/internal/models" "context" "errors" + "github.com/pioz/faker" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" @@ -16,19 +17,39 @@ var ( ErrPromoCodeNotFound = errors.New("promo code not found") ) +// структура для горутины чтобы ошибки не пропускать +type countResult struct { + count int64 + err error +} + type PromoCodeRepository struct { mdb *mongo.Collection } func NewPromoCodeRepository(mdb *mongo.Collection) *PromoCodeRepository { - indexModel := mongo.IndexModel{ + // todo заменить паники вроде как в роде не круто их юзать + uniqueIndexModel := mongo.IndexModel{ Keys: bson.D{ - {"codeword", 1}, - {"delete", 1}, + {Key: "codeword", Value: 1}, + {Key: "delete", Value: 1}, }, Options: options.Index().SetUnique(true).SetPartialFilterExpression(bson.M{"delete": false}), } - _, err := mdb.Indexes().CreateOne(context.Background(), indexModel) + _, err := mdb.Indexes().CreateOne(context.Background(), uniqueIndexModel) + if err != nil { + panic(err) + } + + textIndexModel := mongo.IndexModel{ + Keys: bson.D{ + {Key: "codeword", Value: "text"}, + {Key: "description", Value: "text"}, + {Key: "greetings", Value: "text"}, + }, + Options: options.Index().SetName("TextIndex"), + } + _, err = mdb.Indexes().CreateOne(context.Background(), textIndexModel) if err != nil { panic(err) } @@ -36,11 +57,13 @@ func NewPromoCodeRepository(mdb *mongo.Collection) *PromoCodeRepository { return &PromoCodeRepository{mdb: mdb} } -func (r *PromoCodeRepository) CreatePromoCode(ctx context.Context, promoCode *models.PromoCode) (*models.PromoCode, error) { - promoCode.CreatedAt = time.Now() - promoCode.ID = primitive.NewObjectID() +func (r *PromoCodeRepository) CreatePromoCode(ctx context.Context, req *models.PromoCode) (*models.PromoCode, error) { - _, err := r.mdb.InsertOne(ctx, promoCode) + req.Codeword = req.Codeword + faker.String() + req.CreatedAt = time.Now() + req.ID = primitive.NewObjectID() + + _, err := r.mdb.InsertOne(ctx, req) if err != nil { if writeErr, ok := err.(mongo.WriteException); ok { for _, writeError := range writeErr.WriteErrors { @@ -53,30 +76,30 @@ func (r *PromoCodeRepository) CreatePromoCode(ctx context.Context, promoCode *mo return nil, err } - return promoCode, nil + return req, nil } -func (r *PromoCodeRepository) EditPromoCode(ctx context.Context, editPromoCode *models.ReqEditPromoCode) (*models.PromoCode, error) { - promoCodeID, err := primitive.ObjectIDFromHex(editPromoCode.ID) +func (r *PromoCodeRepository) EditPromoCode(ctx context.Context, req *models.ReqEditPromoCode) (*models.PromoCode, error) { + promoCodeID, err := primitive.ObjectIDFromHex(req.ID) if err != nil { return nil, err } updateFields := bson.M{} - if editPromoCode.Description != nil { - updateFields["description"] = *editPromoCode.Description + if req.Description != nil { + updateFields["description"] = *req.Description } - if editPromoCode.Greetings != nil { - updateFields["greetings"] = *editPromoCode.Greetings + if req.Greetings != nil { + updateFields["greetings"] = *req.Greetings } - if editPromoCode.DueTo != nil { - updateFields["dueTo"] = *editPromoCode.DueTo + if req.DueTo != nil { + updateFields["dueTo"] = *req.DueTo } - if editPromoCode.ActivationCount != nil { - updateFields["activationCount"] = *editPromoCode.ActivationCount + if req.ActivationCount != nil { + updateFields["activationCount"] = *req.ActivationCount } - if editPromoCode.Delete != nil { - updateFields["delete"] = *editPromoCode.Delete + if req.Delete != nil { + updateFields["delete"] = *req.Delete } if len(updateFields) == 0 { @@ -109,3 +132,59 @@ func (r *PromoCodeRepository) GetPromoCodeByID(ctx context.Context, promoCodeID return &promoCode, nil } + +func (r *PromoCodeRepository) GetPromoCodesList(ctx context.Context, req *models.GetPromoCodesListReq) ([]models.PromoCode, int64, error) { + filter := bson.M{} + + if req.Filter.Text != "" { + filter["$text"] = bson.M{"$search": req.Filter.Text} + } + + if req.Filter.Active { + filter["delete"] = false + filter["outdated"] = false + filter["offLimit"] = false + } else { + filter["$or"] = []interface{}{ + bson.M{"delete": true}, + bson.M{"outdated": true}, + bson.M{"offLimit": true}, + } + } + + opt := options.Find().SetSkip(int64(req.Page * req.Limit)).SetLimit(int64(req.Limit)) + + var countChan = make(chan countResult) + go func() { + defer close(countChan) + count, err := r.mdb.CountDocuments(ctx, filter) + countChan <- countResult{count, err} + }() + + cursor, err := r.mdb.Find(ctx, filter, opt) + if err != nil { + return nil, 0, err + } + defer cursor.Close(ctx) + + var promoCodes = make([]models.PromoCode, 0) + for cursor.Next(ctx) { + var p models.PromoCode + if err := cursor.Decode(&p); err != nil { + return nil, 0, err + } + promoCodes = append(promoCodes, p) + } + + if err := cursor.Err(); err != nil { + return nil, 0, err + } + + result := <-countChan + if result.err != nil { + return nil, 0, result.err + } + count := result.count + + return promoCodes, count, nil +} diff --git a/internal/server/http/http_server.go b/internal/server/http/http_server.go index 9c70564..43e75cd 100644 --- a/internal/server/http/http_server.go +++ b/internal/server/http/http_server.go @@ -59,6 +59,7 @@ 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) //... other } diff --git a/internal/services/promocode_service.go b/internal/services/promocode_service.go index e8f92cf..11cd344 100644 --- a/internal/services/promocode_service.go +++ b/internal/services/promocode_service.go @@ -7,8 +7,9 @@ import ( ) type PromoCodeRepository interface { - CreatePromoCode(ctx context.Context, promoCode *models.PromoCode) (*models.PromoCode, error) - EditPromoCode(ctx context.Context, reqEditPromoCode *models.ReqEditPromoCode) (*models.PromoCode, error) + 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) } type PromoDeps struct { @@ -47,3 +48,13 @@ func (s *PromoCodeService) EditPromoCode(ctx context.Context, req *models.ReqEdi return editedPromoCode, nil } + +func (s *PromoCodeService) GetPromoCodesList(ctx context.Context, req *models.GetPromoCodesListReq) ([]models.PromoCode, int64, error) { + promoCodes, count, err := s.promoCodeRepo.GetPromoCodesList(ctx, req) + if err != nil { + s.logger.Error("Failed to get list promocodes from database", zap.Error(err)) + return nil, 0, err + } + + return promoCodes, count, nil +} From b31c3a81c6118ef32d908742c788ebb2d511ca0e Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 12 Jan 2024 14:32:04 +0300 Subject: [PATCH 2/2] replace faker --- internal/repository/promocode_repository.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/repository/promocode_repository.go b/internal/repository/promocode_repository.go index eecc193..83944ef 100644 --- a/internal/repository/promocode_repository.go +++ b/internal/repository/promocode_repository.go @@ -4,7 +4,6 @@ import ( "codeword/internal/models" "context" "errors" - "github.com/pioz/faker" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" @@ -58,8 +57,6 @@ func NewPromoCodeRepository(mdb *mongo.Collection) *PromoCodeRepository { } func (r *PromoCodeRepository) CreatePromoCode(ctx context.Context, req *models.PromoCode) (*models.PromoCode, error) { - - req.Codeword = req.Codeword + faker.String() req.CreatedAt = time.Now() req.ID = primitive.NewObjectID()