package store import ( "context" "errors" "strings" "github.com/tech/sendico/payments/storage" "github.com/tech/sendico/payments/storage/model" "github.com/tech/sendico/pkg/db/repository" ri "github.com/tech/sendico/pkg/db/repository/index" "github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/mlogger" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "go.uber.org/zap" ) type PlanTemplates struct { logger mlogger.Logger repo repository.Repository } // NewPlanTemplates constructs a Mongo-backed plan template store. func NewPlanTemplates(logger mlogger.Logger, repo repository.Repository) (*PlanTemplates, error) { if repo == nil { return nil, merrors.InvalidArgument("planTemplatesStore: repository is nil") } indexes := []*ri.Definition{ { Keys: []ri.Key{ {Field: "fromRail", Sort: ri.Asc}, {Field: "toRail", Sort: ri.Asc}, {Field: "network", Sort: ri.Asc}, }, Unique: true, }, { Keys: []ri.Key{{Field: "fromRail", Sort: ri.Asc}}, }, { Keys: []ri.Key{{Field: "toRail", Sort: ri.Asc}}, }, { Keys: []ri.Key{{Field: "isEnabled", Sort: ri.Asc}}, }, } for _, def := range indexes { if err := repo.CreateIndex(def); err != nil { logger.Error("Failed to ensure plan templates index", zap.Error(err), zap.String("collection", repo.Collection())) return nil, err } } return &PlanTemplates{ logger: logger.Named("plan_templates"), repo: repo, }, nil } func (p *PlanTemplates) Create(ctx context.Context, template *model.PaymentPlanTemplate) error { if template == nil { return merrors.InvalidArgument("planTemplatesStore: nil template") } template.Normalize() if template.FromRail == "" || template.FromRail == model.RailUnspecified { return merrors.InvalidArgument("planTemplatesStore: from_rail is required") } if template.ToRail == "" || template.ToRail == model.RailUnspecified { return merrors.InvalidArgument("planTemplatesStore: to_rail is required") } if len(template.Steps) == 0 { return merrors.InvalidArgument("planTemplatesStore: steps are required") } if template.ID.IsZero() { template.SetID(bson.NewObjectID()) } else { template.Update() } filter := repository.Filter("fromRail", template.FromRail).And( repository.Filter("toRail", template.ToRail), repository.Filter("network", template.Network), ) if err := p.repo.Insert(ctx, template, filter); err != nil { if errors.Is(err, merrors.ErrDataConflict) { return storage.ErrDuplicatePlanTemplate } return err } return nil } func (p *PlanTemplates) Update(ctx context.Context, template *model.PaymentPlanTemplate) error { if template == nil { return merrors.InvalidArgument("planTemplatesStore: nil template") } if template.ID.IsZero() { return merrors.InvalidArgument("planTemplatesStore: missing template id") } template.Normalize() template.Update() if err := p.repo.Update(ctx, template); err != nil { if errors.Is(err, merrors.ErrNoData) { return storage.ErrPlanTemplateNotFound } return err } return nil } func (p *PlanTemplates) GetByID(ctx context.Context, id bson.ObjectID) (*model.PaymentPlanTemplate, error) { if id == bson.NilObjectID { return nil, merrors.InvalidArgument("planTemplatesStore: template id is required") } entity := &model.PaymentPlanTemplate{} if err := p.repo.Get(ctx, id, entity); err != nil { if errors.Is(err, merrors.ErrNoData) { return nil, storage.ErrPlanTemplateNotFound } return nil, err } entity.Normalize() return entity, nil } func (p *PlanTemplates) List(ctx context.Context, filter *model.PaymentPlanTemplateFilter) (*model.PaymentPlanTemplateList, error) { if filter == nil { filter = &model.PaymentPlanTemplateFilter{} } query := repository.Query() if from := normalizedRailFilterValues(filter.FromRail); len(from) == 1 { query = query.Filter(repository.Field("fromRail"), from[0]) } else if len(from) > 1 { query = query.In(repository.Field("fromRail"), stringSliceToAny(from)...) } if to := normalizedRailFilterValues(filter.ToRail); len(to) == 1 { query = query.Filter(repository.Field("toRail"), to[0]) } else if len(to) > 1 { query = query.In(repository.Field("toRail"), stringSliceToAny(to)...) } if network := strings.ToUpper(strings.TrimSpace(filter.Network)); network != "" { query = query.Filter(repository.Field("network"), network) } if filter.IsEnabled != nil { query = query.Filter(repository.Field("isEnabled"), *filter.IsEnabled) } templates := make([]*model.PaymentPlanTemplate, 0) decoder := func(cur *mongo.Cursor) error { item := &model.PaymentPlanTemplate{} if err := cur.Decode(item); err != nil { return err } item.Normalize() templates = append(templates, item) return nil } if err := p.repo.FindManyByFilter(ctx, query, decoder); err != nil && !errors.Is(err, merrors.ErrNoData) { return nil, err } return &model.PaymentPlanTemplateList{ Items: templates, }, nil } var _ storage.PlanTemplatesStore = (*PlanTemplates)(nil)