package store import ( "context" "errors" "github.com/tech/sendico/fx/storage" "github.com/tech/sendico/fx/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/mongo" "go.uber.org/zap" ) type pairStore struct { logger mlogger.Logger repo repository.Repository } func NewPair(logger mlogger.Logger, db *mongo.Database) (storage.PairStore, error) { repo := repository.CreateMongoRepository(db, model.PairsCollection) index := &ri.Definition{ Keys: []ri.Key{ {Field: "pair.base", Sort: ri.Asc}, {Field: "pair.quote", Sort: ri.Asc}, }, Unique: true, } if err := repo.CreateIndex(index); err != nil { logger.Error("Failed to ensure pairs index", zap.Error(err)) return nil, err } logger.Debug("Pair store initialised", zap.String("collection", model.PairsCollection)) return &pairStore{ logger: logger.Named(model.PairsCollection), repo: repo, }, nil } func (p *pairStore) ListEnabled(ctx context.Context) ([]*model.Pair, error) { filter := repository.Query().Filter(repository.Field("isEnabled"), true) pairs := make([]*model.Pair, 0) err := p.repo.FindManyByFilter(ctx, filter, func(cur *mongo.Cursor) error { doc := &model.Pair{} if err := cur.Decode(doc); err != nil { return err } pairs = append(pairs, doc) return nil }) if err != nil { p.logger.Warn("Failed to list enabled pairs", zap.Error(err)) return nil, err } p.logger.Debug("Listed enabled pairs", zap.Int("count", len(pairs))) return pairs, nil } func (p *pairStore) Get(ctx context.Context, pair model.CurrencyPair) (*model.Pair, error) { if pair.Base == "" || pair.Quote == "" { p.logger.Warn("Attempt to fetch pair with empty currency", zap.String("base", pair.Base), zap.String("quote", pair.Quote)) return nil, merrors.InvalidArgument("pairStore: incomplete pair") } result := &model.Pair{} query := repository.Query(). Filter(repository.Field("pair").Dot("base"), pair.Base). Filter(repository.Field("pair").Dot("quote"), pair.Quote) if err := p.repo.FindOneByFilter(ctx, query, result); err != nil { if errors.Is(err, merrors.ErrNoData) { p.logger.Debug("Pair not found", zap.String("base", pair.Base), zap.String("quote", pair.Quote)) } return nil, err } p.logger.Debug("Pair loaded", zap.String("base", pair.Base), zap.String("quote", pair.Quote)) return result, nil } func (p *pairStore) Upsert(ctx context.Context, pair *model.Pair) error { if pair == nil { p.logger.Warn("Attempt to upsert nil pair") return merrors.InvalidArgument("pairStore: nil pair") } if pair.Pair.Base == "" || pair.Pair.Quote == "" { p.logger.Warn("Attempt to upsert pair with empty currency", zap.String("base", pair.Pair.Base), zap.String("quote", pair.Pair.Quote)) return merrors.InvalidArgument("pairStore: incomplete pair") } existing := &model.Pair{} query := repository.Query(). Filter(repository.Field("pair").Dot("base"), pair.Pair.Base). Filter(repository.Field("pair").Dot("quote"), pair.Pair.Quote) err := p.repo.FindOneByFilter(ctx, query, existing) if err != nil { if errors.Is(err, merrors.ErrNoData) { p.logger.Debug("Inserting new pair", zap.String("base", pair.Pair.Base), zap.String("quote", pair.Pair.Quote)) return p.repo.Insert(ctx, pair, query) } p.logger.Warn("Failed to fetch pair", zap.Error(err), zap.String("base", pair.Pair.Base), zap.String("quote", pair.Pair.Quote)) return err } if existing.GetID() != nil { pair.SetID(*existing.GetID()) } p.logger.Debug("Updating pair", zap.String("base", pair.Pair.Base), zap.String("quote", pair.Pair.Quote)) return p.repo.Update(ctx, pair) }