add: add orders cancel-all endpoint
This commit is contained in:
parent
ac430e0d17
commit
afe5ce2fa3
5 changed files with 230 additions and 1 deletions
|
|
@ -12,6 +12,9 @@ import GetUserOrdersAllPairsRes, {
|
|||
GetUserOrdersAllPairsErrorCode,
|
||||
GetUserOrdersAllPairsResPair,
|
||||
} from '@/interfaces/responses/orders/GetUserOrdersAllPairsRes';
|
||||
import CancelAllBody, { CancelAllBodyOrderType } from '@/interfaces/bodies/orders/CancelAllBody';
|
||||
import sequelize from '@/sequelize';
|
||||
import CancelAllRes, { CancelAllErrorCode } from '@/interfaces/responses/orders/CancelAllRes';
|
||||
import candlesModel from '../models/Candles';
|
||||
import ordersModel from '../models/Orders';
|
||||
import CreateOrderBody from '../interfaces/bodies/orders/CreateOrderBody';
|
||||
|
|
@ -452,6 +455,51 @@ class OrdersController {
|
|||
res.status(500).send({ success: false, data: 'Unhandled error' });
|
||||
}
|
||||
}
|
||||
|
||||
async cancelAll(req: Request, res: Response<CancelAllRes>) {
|
||||
try {
|
||||
const body = req.body as CancelAllBody;
|
||||
const { userData, filterInfo } = body;
|
||||
|
||||
const filterType = (() => {
|
||||
if (filterInfo.type === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return filterInfo.type === CancelAllBodyOrderType.BUY
|
||||
? GetUserOrdersBodyType.BUY
|
||||
: GetUserOrdersBodyType.SELL;
|
||||
})();
|
||||
|
||||
await sequelize.transaction(async (transaction) => {
|
||||
await ordersModel.cancelAll(
|
||||
{
|
||||
address: userData.address,
|
||||
filterInfo: {
|
||||
pairId: filterInfo.pairId,
|
||||
type: filterType,
|
||||
date:
|
||||
filterInfo.date !== undefined
|
||||
? {
|
||||
from: filterInfo.date.from,
|
||||
to: filterInfo.date.to,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
});
|
||||
|
||||
res.status(200).send({ success: true });
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
res.status(500).send({
|
||||
success: false,
|
||||
data: CancelAllErrorCode.UNHANDLED_ERROR,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ordersController = new OrdersController();
|
||||
|
|
|
|||
50
src/interfaces/bodies/orders/CancelAllBody.ts
Normal file
50
src/interfaces/bodies/orders/CancelAllBody.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import UserData from '@/interfaces/common/UserData';
|
||||
import { body } from 'express-validator';
|
||||
|
||||
export enum CancelAllBodyOrderType {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
BUY = 'buy',
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
SELL = 'sell',
|
||||
}
|
||||
|
||||
interface CancelAllBody {
|
||||
userData: UserData;
|
||||
|
||||
filterInfo: {
|
||||
pairId?: number;
|
||||
type?: CancelAllBodyOrderType;
|
||||
date?: {
|
||||
// UNIX timestamps in milliseconds
|
||||
from: number;
|
||||
to: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const cancelAllValidator = [
|
||||
body('filterInfo').isObject().withMessage('filterInfo must be an object'),
|
||||
body('filterInfo.pairId')
|
||||
.optional()
|
||||
.isInt({ min: 0 })
|
||||
.withMessage('filterInfo.pairId must be a non-negative integer'),
|
||||
body('filterInfo.type')
|
||||
.optional()
|
||||
.isIn(Object.values(CancelAllBodyOrderType))
|
||||
.withMessage(`Invalid filterInfo.type value`),
|
||||
body('filterInfo.date').optional().isObject().withMessage('filterInfo.date must be an object'),
|
||||
body('filterInfo.date.from')
|
||||
.if(body('filterInfo.date').isObject())
|
||||
.isInt({ min: 0 })
|
||||
.withMessage(
|
||||
'filterInfo.date.from must be a non-negative integer representing a UNIX timestamp in milliseconds',
|
||||
),
|
||||
body('filterInfo.date.to')
|
||||
.if(body('filterInfo.date').isObject())
|
||||
.isInt({ min: 0 })
|
||||
.withMessage(
|
||||
'filterInfo.date.to must be a non-negative integer representing a UNIX timestamp in milliseconds',
|
||||
),
|
||||
];
|
||||
|
||||
export default CancelAllBody;
|
||||
17
src/interfaces/responses/orders/CancelAllRes.ts
Normal file
17
src/interfaces/responses/orders/CancelAllRes.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
export type CancelAllSuccessRes = {
|
||||
success: true;
|
||||
};
|
||||
|
||||
export enum CancelAllErrorCode {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
UNHANDLED_ERROR = 'Unhandled error',
|
||||
}
|
||||
|
||||
export type CancelAllErrorRes = {
|
||||
success: false;
|
||||
data: CancelAllErrorCode;
|
||||
};
|
||||
|
||||
type CancelAllRes = CancelAllSuccessRes | CancelAllErrorRes;
|
||||
|
||||
export default CancelAllRes;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Op, WhereOptions } from 'sequelize';
|
||||
import { Op, Transaction as SequelizeTransaction, WhereOptions } from 'sequelize';
|
||||
import Decimal from 'decimal.js';
|
||||
import TransactionWithOrders from '@/interfaces/common/Transaction.js';
|
||||
import Currency from '@/schemes/Currency.js';
|
||||
|
|
@ -876,6 +876,114 @@ class OrdersModel {
|
|||
data: pairs,
|
||||
};
|
||||
};
|
||||
|
||||
static CANCEL_ALL_USER_NOT_FOUND = 'No user found';
|
||||
static CANCEL_ALL_ORDER_NOT_FOUND = 'Order not found during cancel all process';
|
||||
cancelAll = async (
|
||||
{
|
||||
address,
|
||||
filterInfo: { pairId, type, date },
|
||||
}: {
|
||||
address: string;
|
||||
filterInfo: {
|
||||
pairId?: number;
|
||||
type?: 'buy' | 'sell';
|
||||
date?: {
|
||||
from: number;
|
||||
to: number;
|
||||
};
|
||||
};
|
||||
},
|
||||
{ transaction }: { transaction: SequelizeTransaction },
|
||||
): Promise<{
|
||||
success: true;
|
||||
}> => {
|
||||
const userRow = await userModel.getUserRow(address);
|
||||
|
||||
if (!userRow) {
|
||||
throw new Error(OrdersModel.CANCEL_ALL_USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
const ordersToCancelWhereClause: WhereOptions = {
|
||||
user_id: userRow.id,
|
||||
status: {
|
||||
[Op.ne]: OrderStatus.FINISHED,
|
||||
},
|
||||
...(pairId !== undefined ? { pair_id: pairId } : {}),
|
||||
...(type !== undefined
|
||||
? { type: type === 'buy' ? OrderType.BUY : OrderType.SELL }
|
||||
: {}),
|
||||
...(date !== undefined ? { timestamp: { [Op.between]: [date.from, date.to] } } : {}),
|
||||
};
|
||||
|
||||
const ordersToCancelCount = await Order.count({
|
||||
where: ordersToCancelWhereClause,
|
||||
});
|
||||
|
||||
const cancelPromises = [];
|
||||
|
||||
for (let offset = 0; offset < ordersToCancelCount; offset += 1) {
|
||||
cancelPromises.push(async () => {
|
||||
const orderRow = await Order.findOne({
|
||||
where: ordersToCancelWhereClause,
|
||||
order: [['timestamp', 'ASC']],
|
||||
offset,
|
||||
limit: 1,
|
||||
});
|
||||
|
||||
if (!orderRow) {
|
||||
throw new Error(OrdersModel.CANCEL_ALL_ORDER_NOT_FOUND);
|
||||
}
|
||||
|
||||
await this.cancelOrderNotifications(orderRow, userRow);
|
||||
|
||||
const eps = new Decimal(1e-8);
|
||||
const leftDecimal = new Decimal(orderRow.left);
|
||||
const amountDecimal = new Decimal(orderRow.amount);
|
||||
|
||||
// if order was partially filled
|
||||
if (leftDecimal.minus(amountDecimal).abs().greaterThan(eps)) {
|
||||
const connectedTransactions = await Transaction.findAll({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ buy_order_id: orderRow.id },
|
||||
{ sell_order_id: orderRow.id },
|
||||
],
|
||||
status: 'pending',
|
||||
},
|
||||
});
|
||||
|
||||
for (const tx of connectedTransactions) {
|
||||
await exchangeModel.returnTransactionAmount(tx.id, transaction);
|
||||
}
|
||||
|
||||
await Order.update(
|
||||
{ status: OrderStatus.FINISHED },
|
||||
{
|
||||
where: { id: orderRow.id, user_id: userRow.id },
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
await Order.destroy({
|
||||
where: {
|
||||
id: orderRow.id,
|
||||
user_id: userRow.id,
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
|
||||
transaction.afterCommit(() => {
|
||||
sendDeleteOrderMessage(io, orderRow.pair_id.toString(), orderRow.id.toString());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
await Promise.all(cancelPromises);
|
||||
|
||||
return { success: true };
|
||||
};
|
||||
}
|
||||
|
||||
const ordersModel = new OrdersModel();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import express from 'express';
|
|||
import { createOrderValidator } from '@/interfaces/bodies/orders/CreateOrderBody.js';
|
||||
import { getUserOrdersValidator } from '@/interfaces/bodies/orders/GetUserOrdersBody.js';
|
||||
import { getUserOrdersAllPairsValidator } from '@/interfaces/bodies/orders/GetUserOrdersAllPairsBody.js';
|
||||
import { cancelAllValidator } from '@/interfaces/bodies/orders/CancelAllBody.js';
|
||||
import middleware from '../middleware/middleware.js';
|
||||
import ordersController from '../controllers/orders.controller.js';
|
||||
|
||||
|
|
@ -43,5 +44,10 @@ ordersRouter.patch(
|
|||
middleware.expressValidator(getUserOrdersAllPairsValidator),
|
||||
ordersController.getUserOrdersAllPairs.bind(ordersController),
|
||||
);
|
||||
ordersRouter.patch(
|
||||
'/orders/cancel-all',
|
||||
middleware.expressValidator(cancelAllValidator),
|
||||
ordersController.cancelAll.bind(ordersController),
|
||||
);
|
||||
|
||||
export default ordersRouter;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue