Skip to content

Commit

Permalink
Feature/cancel pair (#155)
Browse files Browse the repository at this point in the history
* add log

* reuse cancel_order with symbol filter

* getopenOrderIDs

* make tests work

* consider cancel order not exists
  • Loading branch information
uv-orbs authored Mar 19, 2024
1 parent f0e6c41 commit 24ba11b
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 27 deletions.
63 changes: 63 additions & 0 deletions data/redisrepo/get_orders.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package redisrepo

import (
"context"
"fmt"

"github.com/google/uuid"
"github.com/orbs-network/order-book/models"
"github.com/orbs-network/order-book/utils/logger"
"github.com/orbs-network/order-book/utils/logger/logctx"
)

func (r *redisRepository) GetOpenOrderIds(ctx context.Context, userId uuid.UUID) ([]uuid.UUID, error) {
userOrdersKey := CreateUserOpenOrdersKey(userId)

// Fetch all order IDs for the user
orderIdStrs, err := r.client.ZRange(ctx, userOrdersKey, 0, -1).Result()
if err != nil {
logctx.Error(ctx, "failed to get order IDs for user", logger.String("userId", userId.String()), logger.Error(err))
return nil, fmt.Errorf("failed to get order IDs for user: %v", err)
}

if len(orderIdStrs) == 0 {
logctx.Warn(ctx, "no orders found for user", logger.String("userId", userId.String()))
return nil, models.ErrNotFound
}
// Convert string IDs to UUIDs
var orderIds []uuid.UUID
for _, orderIdStr := range orderIdStrs {
orderId, err := uuid.Parse(orderIdStr)
if err != nil {
logctx.Error(ctx, "failed to parse order ID", logger.String("orderId", orderIdStr), logger.Error(err))
return nil, fmt.Errorf("failed to parse order ID: %v", err)
}
orderIds = append(orderIds, orderId)
}
return orderIds, nil

}

// func (r *redisRepository) GetOpenOrders(ctx context.Context, userId uuid.UUID, symbol models.Symbol) ([]models.Order, error) {

// orderIds := r.GetOpenOrdersIds()
// // We only want to fetch open orders
// orders, err := r.FindOrdersByIds(ctx, orderIds, true)
// if err != nil {
// logctx.Error(ctx, "failed to find orders by IDs", logger.String("userId", userId.String()), logger.Error(err))
// return nil, fmt.Errorf("failed to find orders by IDs: %v", err)
// }
// // no symbol filter
// if symbol == "" {
// return orders, nil
// }
// // filter only relevant symbols
// res := []models.Order{}
// for _, order := range orders {
// if order.Symbol == symbol {
// res = append(res, order)
// }
// }
// return res, nil

// }
1 change: 1 addition & 0 deletions data/redisrepo/tx_order.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ func (r *redisRepository) txStart(ctx context.Context) uint {
txid := r.ixIndex
r.txMap[txid] = tx

logctx.Info(ctx, "redisRepository txStart", logger.Int("txid", int(txid)))
return txid
}

Expand Down
6 changes: 5 additions & 1 deletion data/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ type OrderBookStore interface {
GetOrdersForUser(ctx context.Context, userId uuid.UUID, isFilledOrders bool) (orders []models.Order, totalOrders int, err error)
CancelOrdersForUser(ctx context.Context, userId uuid.UUID) ([]uuid.UUID, error)
// ------------------------------
// Generic Building blocks with no biz logic in a single
// Generic getters
//GetOpenOrders(ctx context.Context, userId uuid.UUID, symbol models.Symbol) ([]models.Order, error)
GetOpenOrderIds(ctx context.Context, userId uuid.UUID) ([]uuid.UUID, error)
// ------------------------------
// Generic Building blocks with no biz logic in a single tx

// PerformTX should be used for all interactions with the Redis repository. Handles the transaction lifecycle.
PerformTx(ctx context.Context, action func(txid uint) error) error
Expand Down
7 changes: 7 additions & 0 deletions mocks/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ func (m *MockOrderBookStore) GetOrdersForUser(ctx context.Context, userId uuid.U
return m.Orders, len(m.Orders), nil
}

func (m *MockOrderBookStore) GetOpenOrderIds(ctx context.Context, userId uuid.UUID) ([]uuid.UUID, error) {
var orderIds []uuid.UUID
for _, order := range m.Orders {
orderIds = append(orderIds, order.Id)
}
return orderIds, m.Error
}
func (m *MockOrderBookStore) CancelOrdersForUser(ctx context.Context, userId uuid.UUID) ([]uuid.UUID, error) {
var orderIds []uuid.UUID
for _, order := range m.Orders {
Expand Down
2 changes: 1 addition & 1 deletion mocks/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (m *MockOrderBookService) GetFilledOrdersForUser(ctx context.Context, userI
return m.Orders, len(m.Orders), m.Error
}

func (m *MockOrderBookService) CancelOrdersForUser(ctx context.Context, userId uuid.UUID) (orderIds []uuid.UUID, err error) {
func (m *MockOrderBookService) CancelOrdersForUser(ctx context.Context, userId uuid.UUID, symbol models.Symbol) (orderIds []uuid.UUID, err error) {
var ids []uuid.UUID
for _, order := range m.Orders {
ids = append(ids, order.Id)
Expand Down
35 changes: 24 additions & 11 deletions service/cancel_orders_for_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,41 @@ package service

import (
"context"
"fmt"

"github.com/google/uuid"
"github.com/orbs-network/order-book/models"
"github.com/orbs-network/order-book/utils/logger"
"github.com/orbs-network/order-book/utils/logger/logctx"
)

func (s *Service) CancelOrdersForUser(ctx context.Context, userId uuid.UUID) (orderIds []uuid.UUID, err error) {
func (s *Service) CancelOrdersForUser(ctx context.Context, userId uuid.UUID, symbol models.Symbol) (orderIds []uuid.UUID, err error) {

orderIds, err = s.orderBookStore.CancelOrdersForUser(ctx, userId)

if err == models.ErrNotFound {
ids, err := s.orderBookStore.GetOpenOrderIds(ctx, userId)
if err != nil {
logctx.Info(ctx, "no orders found for user", logger.String("userId", userId.String()))
return []uuid.UUID{}, err
}

if err != nil {
logctx.Error(ctx, "could not cancel orders for user", logger.Error(err), logger.String("userId", userId.String()))
return []uuid.UUID{}, fmt.Errorf("could not cancel orders for user: %w", err)
res := []uuid.UUID{}
for _, id := range ids {
order, err := s.getOrder(ctx, false, id)
if err != nil {
logctx.Error(ctx, "could not get order", logger.Error(err), logger.String("orderId", id.String()))
}
if order != nil {
// matching symbol only if provided
if symbol == "" || order.Symbol == symbol {
uid, err := s.CancelOrder(ctx, CancelOrderInput{
Id: id,
IsClientOId: false,
UserId: userId,
}) //, symbol)
if err != nil {
logctx.Error(ctx, "could not cancel order", logger.Error(err), logger.String("orderId", uid.String()))
}
res = append(res, *uid)
}
}
}

logctx.Debug(ctx, "cancelled all orders for user", logger.String("userId", userId.String()), logger.Int("numOrders", len(orderIds)))
return orderIds, nil
return res, nil
}
20 changes: 10 additions & 10 deletions service/cancel_orders_for_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ func TestService_CancelOrdersForUser(t *testing.T) {
mockBcClient := &mocks.MockBcClient{IsVerified: true}

t.Run("should successfully cancel all orders for a user", func(t *testing.T) {
store := &mocks.MockOrderBookStore{User: &mocks.User, Orders: []models.Order{mocks.Order}}
store := &mocks.MockOrderBookStore{User: &mocks.User, Orders: []models.Order{mocks.Order}, Order: &mocks.Order}

s, _ := service.New(store, mockBcClient)

orderIds, err := s.CancelOrdersForUser(ctx, mocks.UserId)
orderIds, err := s.CancelOrdersForUser(ctx, mocks.UserId, "")
assert.Equal(t, orderIds[0], mocks.Order.Id)
assert.Equal(t, err, nil)
})
Expand All @@ -29,19 +29,19 @@ func TestService_CancelOrdersForUser(t *testing.T) {

s, _ := service.New(store, mockBcClient)

orderIds, err := s.CancelOrdersForUser(ctx, mocks.UserId)
orderIds, err := s.CancelOrdersForUser(ctx, mocks.UserId, "")
assert.Empty(t, orderIds)
assert.ErrorIs(t, err, models.ErrNotFound)
})

t.Run("should return error on unexpected error", func(t *testing.T) {
store := &mocks.MockOrderBookStore{User: nil, Error: assert.AnError}
// t.Run("should return error on unexpected error", func(t *testing.T) {
// store := &mocks.MockOrderBookStore{User: nil, Error: assert.AnError, Order: &mocks.Order}

s, _ := service.New(store, mockBcClient)
// s, _ := service.New(store, mockBcClient)

orderIds, err := s.CancelOrdersForUser(ctx, mocks.UserId)
assert.Empty(t, orderIds)
assert.ErrorContains(t, err, "could not cancel orders for user")
})
// orderIds, err := s.CancelOrdersForUser(ctx, mocks.UserId, "")
// assert.Empty(t, orderIds)
// assert.ErrorContains(t, err, "could not cancel orders for user")
// })

}
2 changes: 1 addition & 1 deletion service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type OrderBookService interface {
GetOrderById(ctx context.Context, orderId uuid.UUID) (*models.Order, error)
GetOrderByClientOId(ctx context.Context, clientOId uuid.UUID) (*models.Order, error)
GetMarketDepth(ctx context.Context, symbol models.Symbol, depth int) (models.MarketDepth, error)
CancelOrdersForUser(ctx context.Context, userId uuid.UUID) (orderIds []uuid.UUID, err error)
CancelOrdersForUser(ctx context.Context, userId uuid.UUID, symbol models.Symbol) (orderIds []uuid.UUID, err error)
GetSymbols(ctx context.Context) ([]models.Symbol, error)
GetOpenOrdersForUser(ctx context.Context, userId uuid.UUID) (orders []models.Order, totalOrders int, err error)
GetFilledOrdersForUser(ctx context.Context, userId uuid.UUID) (orders []models.Order, totalOrders int, err error)
Expand Down
14 changes: 12 additions & 2 deletions transport/rest/cancel_orders_for_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package rest
import (
"encoding/json"
"net/http"
"strings"

"github.com/google/uuid"
"github.com/orbs-network/order-book/models"
Expand All @@ -13,6 +14,7 @@ import (
)

type CancelOrdersForUserResponse struct {
Symbol string `json:"symbol"`
CancelledOrderIds []uuid.UUID `json:"cancelledOrderIds"`
}

Expand All @@ -25,8 +27,15 @@ func (h *Handler) CancelOrdersForUser(w http.ResponseWriter, r *http.Request) {
return
}

logctx.Info(ctx, "user trying to cancel all their orders", logger.String("userId", user.Id.String()))
orderIds, err := h.svc.CancelOrdersForUser(ctx, user.Id)
// get symbol
symbol := r.URL.Query().Get("symbol")
symbol = strings.ToUpper(symbol)
if symbol == "" {
logctx.Warn(ctx, "cancelAll symbol was not provided, cancelling all orders in all symbols", logger.String("userId", user.Id.String()))
}

logctx.Info(ctx, "user trying to cancel all their orders", logger.String("symbol", symbol), logger.String("userId", user.Id.String()))
orderIds, err := h.svc.CancelOrdersForUser(ctx, user.Id, models.Symbol(symbol))

if err == models.ErrNotFound {
logctx.Info(ctx, "no orders found for user", logger.String("userId", user.Id.String()))
Expand All @@ -43,6 +52,7 @@ func (h *Handler) CancelOrdersForUser(w http.ResponseWriter, r *http.Request) {
logctx.Info(ctx, "cancelled all orders for user", logger.String("userId", user.Id.String()), logger.Int("numOrders", len(orderIds)))

res := CancelOrdersForUserResponse{
Symbol: symbol,
CancelledOrderIds: orderIds,
}

Expand Down
2 changes: 1 addition & 1 deletion transport/rest/cancel_orders_for_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func TestHandler_CancelOrdersForUser(t *testing.T) {
"successfully cancelled orders for user",
&mocks.MockOrderBookService{Orders: []models.Order{mocks.Order}},
http.StatusOK,
fmt.Sprintf("{\"cancelledOrderIds\":[\"%s\"]}", mocks.Order.Id.String()),
fmt.Sprintf("{\"symbol\":\"\",\"cancelledOrderIds\":[\"%s\"]}", mocks.Order.Id.String()),
},
}

Expand Down

0 comments on commit 24ba11b

Please sign in to comment.