package repository import ( "codeword/internal/models" "context" "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/options" "time" ) // структура для горутины чтобы ошибки не пропускать type countResult struct { count int64 err error } type PromoCodeRepository struct { mdb *mongo.Collection } func NewPromoCodeRepository(mdb *mongo.Collection) *PromoCodeRepository { return &PromoCodeRepository{mdb: mdb} } func InitPromoCodeIndexes(ctx context.Context, mdb *mongo.Collection) error { uniqueIndexModel := mongo.IndexModel{ Keys: bson.D{ {Key: "codeword", Value: 1}, {Key: "delete", Value: 1}, }, Options: options.Index().SetUnique(true).SetPartialFilterExpression(bson.M{"delete": false}), } _, err := mdb.Indexes().CreateOne(ctx, uniqueIndexModel) if err != nil { return 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(ctx, textIndexModel) if err != nil { return err } return nil } func (r *PromoCodeRepository) CreatePromoCode(ctx context.Context, req *models.PromoCode) (*models.PromoCode, error) { req.CreatedAt = time.Now() req.ID = primitive.NewObjectID() if req.FastLinks == nil { req.FastLinks = []string{} } _, err := r.mdb.InsertOne(ctx, req) if err != nil { if writeErr, ok := err.(mongo.WriteException); ok { for _, writeError := range writeErr.WriteErrors { if writeError.Code == 11000 { return nil, ErrDuplicateCodeword } } } return nil, err } return req, nil } 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 req.Description != nil { updateFields["description"] = *req.Description } if req.Greetings != nil { updateFields["greetings"] = *req.Greetings } if req.DueTo != nil { updateFields["dueTo"] = *req.DueTo } if req.ActivationCount != nil { updateFields["activationCount"] = *req.ActivationCount } if req.Delete != nil { updateFields["delete"] = *req.Delete } if req.Bonus != nil { if req.Bonus.Privilege != nil { if req.Bonus.Privilege.PrivilegeID != "" { updateFields["bonus.privilege.privilegeID"] = req.Bonus.Privilege.PrivilegeID } if req.Bonus.Privilege.Amount != 0 { updateFields["bonus.privilege.amount"] = req.Bonus.Privilege.Amount } } if req.Bonus.Discount != nil { if req.Bonus.Discount.Layer != 0 { updateFields["bonus.discount.layer"] = req.Bonus.Discount.Layer } if req.Bonus.Discount.Factor != 0.0 { updateFields["bonus.discount.factor"] = req.Bonus.Discount.Factor } if req.Bonus.Discount.Target != "" { updateFields["bonus.discount.target"] = req.Bonus.Discount.Target } if req.Bonus.Discount.Threshold != 0 { updateFields["bonus.discount.threshold"] = req.Bonus.Discount.Threshold } } } if len(updateFields) == 0 { return r.GetPromoCodeByID(ctx, promoCodeID) } update := bson.M{"$set": updateFields} options := options.FindOneAndUpdate().SetReturnDocument(options.After) result := r.mdb.FindOneAndUpdate(ctx, bson.M{"_id": promoCodeID}, update, options) if result.Err() != nil { return nil, result.Err() } var updatedPromoCode models.PromoCode err = result.Decode(&updatedPromoCode) if err != nil { return nil, err } return &updatedPromoCode, nil } func (r *PromoCodeRepository) GetPromoCodeByID(ctx context.Context, promoCodeID primitive.ObjectID) (*models.PromoCode, error) { var promoCode models.PromoCode err := r.mdb.FindOne(ctx, bson.M{"_id": promoCodeID}).Decode(&promoCode) if err != nil { if err == mongo.ErrNoDocuments { return nil, ErrPromoCodeNotFound } return nil, err } 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, 10) 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 } func (r *PromoCodeRepository) ActivatePromo(ctx context.Context, req *models.ActivateReq) (*models.PromoCode, error) { var promoCode models.PromoCode var filter bson.M if req.Codeword != "" { filter = bson.M{ "codeword": req.Codeword, } } else if req.FastLink != "" { filter = bson.M{ "fastLinks": req.FastLink, } } 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 } 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 { 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 } func (r *PromoCodeRepository) AddFastLink(ctx context.Context, promoCodeID primitive.ObjectID, xid string) error { filter := bson.M{"_id": promoCodeID, "delete": false} update := bson.M{"$push": bson.M{"fastLinks": xid}} result, err := r.mdb.UpdateOne(ctx, filter, update) if err != nil { return err } if result.MatchedCount == 0 { return ErrPromoCodeNotFound } return nil }