From 2d2746acb93b50f238447c2ed17b24efc2555eb1 Mon Sep 17 00:00:00 2001 From: Andrew Besedin Date: Sat, 14 Feb 2026 03:42:12 +0300 Subject: [PATCH 1/8] update: add get-user-orders pagination --- src/controllers/orders.controller.ts | 138 ++++++++++++++++-- .../bodies/orders/GetUserOrdersBody.ts | 58 +++++++- .../responses/orders/GetUserOrdersRes.ts | 73 +++++++++ src/models/Orders.ts | 88 ++++++++++- src/routes/orders.router.ts | 7 +- src/schemes/Order.ts | 17 +++ 6 files changed, 363 insertions(+), 18 deletions(-) create mode 100644 src/interfaces/responses/orders/GetUserOrdersRes.ts diff --git a/src/controllers/orders.controller.ts b/src/controllers/orders.controller.ts index e3d6643..813dad3 100644 --- a/src/controllers/orders.controller.ts +++ b/src/controllers/orders.controller.ts @@ -2,11 +2,19 @@ import { Request, Response } from 'express'; import Decimal from 'decimal.js'; import CreateOrderRes, { CreateOrderErrorCode } from '@/interfaces/responses/orders/CreateOrderRes'; +import GetUserOrdersRes, { + GetUserOrdersErrorCode, + GetUserOrdersResCurrency, + GetUserOrdersResOrderData, +} from '@/interfaces/responses/orders/GetUserOrdersRes'; import candlesModel from '../models/Candles'; import ordersModel from '../models/Orders'; import CreateOrderBody from '../interfaces/bodies/orders/CreateOrderBody'; import GetUserOrdersPageBody from '../interfaces/bodies/orders/GetUserOrdersPageBody'; -import GetUserOrdersBody from '../interfaces/bodies/orders/GetUserOrdersBody'; +import GetUserOrdersBody, { + GetUserOrdersBodyStatus, + GetUserOrdersBodyType, +} from '../interfaces/bodies/orders/GetUserOrdersBody'; import CancelOrderBody from '../interfaces/bodies/orders/CancelOrderBody'; import GetCandlesBody from '../interfaces/bodies/orders/GetCandlesBody'; import GetChartOrdersBody from '../interfaces/bodies/orders/GetChartOrdersBody'; @@ -157,21 +165,131 @@ class OrdersController { } } - async getUserOrders(req: Request, res: Response) { + private fromGetUserOrdersServiceToResCurrencyMapper( + currency: Currency, + ): GetUserOrdersResCurrency { + return { + id: currency.id, + name: currency.name, + code: currency.code, + type: currency.type, + asset_id: currency.asset_id, + auto_parsed: currency.auto_parsed, + asset_info: currency.asset_info + ? { + asset_id: currency.asset_info.asset_id, + logo: currency.asset_info.logo, + price_url: currency.asset_info.price_url, + ticker: currency.asset_info.ticker, + full_name: currency.asset_info.full_name, + total_max_supply: currency.asset_info.total_max_supply, + current_supply: currency.asset_info.current_supply, + decimal_point: currency.asset_info.decimal_point, + meta_info: currency.asset_info.meta_info, + } + : undefined, + whitelisted: currency.whitelisted, + }; + } + getUserOrders = async (req: Request, res: Response) => { try { - await userModel.resetExchangeNotificationsAmount( - (req.body.userData as UserData).address, - ); - const result = await ordersModel.getUserOrders(req.body as GetUserOrdersBody); + const body = req.body as GetUserOrdersBody; + const { userData, offset, limit, filterInfo } = body; - if (result.data === 'Internal error') return res.status(500).send(result); + await userModel.resetExchangeNotificationsAmount(userData.address); - res.status(200).send(result); + const serviceOrderType: 'buy' | 'sell' | undefined = (() => { + if (filterInfo?.type === undefined) { + return undefined; + } + + return filterInfo.type === GetUserOrdersBodyType.BUY ? 'buy' : 'sell'; + })(); + + const serviceOrderStatus: 'active' | 'finished' | undefined = (() => { + if (filterInfo?.status === undefined) { + return undefined; + } + + return filterInfo.status === GetUserOrdersBodyStatus.ACTIVE ? 'active' : 'finished'; + })(); + + const result = await ordersModel.getUserOrders({ + address: userData.address, + offset, + limit, + filterInfo: { + type: serviceOrderType, + status: serviceOrderStatus, + date: + filterInfo.date !== undefined + ? { + from: filterInfo.date.from, + to: filterInfo.date.to, + } + : undefined, + }, + }); + + if (result.data === 'Internal error') { + throw new Error('ordersModel.getUserOrders returned Internal error'); + } + + const userOrders = result.data.map((order) => { + const mappedOrder: GetUserOrdersResOrderData = { + id: order.id, + type: order.type, + timestamp: order.timestamp, + side: order.side, + price: order.price, + amount: order.amount, + total: order.total, + pair_id: order.pair_id, + user_id: order.user_id, + status: order.status, + left: order.left, + hasNotification: order.hasNotification, + pair: { + id: order.pair.id, + first_currency_id: order.pair.first_currency_id, + second_currency_id: order.pair.second_currency_id, + rate: order.pair.rate, + coefficient: order.pair.coefficient, + high: order.pair.high, + low: order.pair.low, + volume: order.pair.volume, + featured: order.pair.featured, + first_currency: this.fromGetUserOrdersServiceToResCurrencyMapper( + order.pair.first_currency, + ), + second_currency: this.fromGetUserOrdersServiceToResCurrencyMapper( + order.pair.second_currency, + ), + }, + first_currency: this.fromGetUserOrdersServiceToResCurrencyMapper( + order.first_currency, + ), + second_currency: this.fromGetUserOrdersServiceToResCurrencyMapper( + order.second_currency, + ), + isInstant: order.isInstant, + }; + + return mappedOrder; + }); + + res.status(200).send({ + success: true, + data: userOrders, + }); } catch (err) { console.log(err); - res.status(500).send({ success: false, data: 'Unhandled error' }); + res.status(500).send({ + success: false, + data: GetUserOrdersErrorCode.UNHANDLED_ERROR, + }); } - } + }; async cancelOrder(req: Request, res: Response) { try { diff --git a/src/interfaces/bodies/orders/GetUserOrdersBody.ts b/src/interfaces/bodies/orders/GetUserOrdersBody.ts index 93e5015..d5d0c2a 100644 --- a/src/interfaces/bodies/orders/GetUserOrdersBody.ts +++ b/src/interfaces/bodies/orders/GetUserOrdersBody.ts @@ -1,7 +1,63 @@ -import UserData from '../../common/UserData'; +import UserData from '@/interfaces/common/UserData'; +import { body } from 'express-validator'; + +export enum GetUserOrdersBodyStatus { + // eslint-disable-next-line no-unused-vars + ACTIVE = 'active', + // eslint-disable-next-line no-unused-vars + FINISHED = 'finished', +} + +export enum GetUserOrdersBodyType { + // eslint-disable-next-line no-unused-vars + BUY = 'buy', + // eslint-disable-next-line no-unused-vars + SELL = 'sell', +} interface GetUserOrdersBody { userData: UserData; + + limit: number; + offset: number; + filterInfo: { + status?: GetUserOrdersBodyStatus; + type?: GetUserOrdersBodyType; + date?: { + // UNIX timestamps in milliseconds + from: number; + to: number; + }; + }; } +export const getUserOrdersValidator = [ + body('limit') + .isInt({ min: 1, max: 1000 }) + .withMessage('limit must be a positive integer within certain range'), + body('offset').isInt({ min: 0 }).withMessage('offset must be a non-negative integer'), + body('filterInfo').isObject().withMessage('filterInfo must be an object'), + body('filterInfo.status') + .optional() + .isIn(Object.values(GetUserOrdersBodyStatus)) + .withMessage(`Invalid filterInfo.status value`), + body('filterInfo.type') + .optional() + .isIn(Object.values(GetUserOrdersBodyType)) + .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 GetUserOrdersBody; diff --git a/src/interfaces/responses/orders/GetUserOrdersRes.ts b/src/interfaces/responses/orders/GetUserOrdersRes.ts new file mode 100644 index 0000000..98a6828 --- /dev/null +++ b/src/interfaces/responses/orders/GetUserOrdersRes.ts @@ -0,0 +1,73 @@ +export type GetUserOrdersResCurrency = { + id: number; + name: string; + code: string; + type: string; + asset_id: string; + auto_parsed: boolean; + asset_info?: { + asset_id: string; + logo: string; + price_url: string; + ticker: string; + full_name: string; + total_max_supply: string; + current_supply: string; + decimal_point: number; + meta_info: string; + }; + whitelisted: boolean; +}; + +export type GetUserOrdersResOrderData = { + id: number; + type: string; + timestamp: number; + side: string; + price: string; + amount: string; + total: string; + pair_id: number; + user_id: number; + status: string; + left: string; + hasNotification: boolean; + + pair: { + id: number; + first_currency_id: number; + second_currency_id: number; + rate?: number; + coefficient?: number; + high?: number; + low?: number; + volume: number; + featured: boolean; + + first_currency: GetUserOrdersResCurrency; + second_currency: GetUserOrdersResCurrency; + }; + + first_currency: GetUserOrdersResCurrency; + second_currency: GetUserOrdersResCurrency; + isInstant: boolean; +}; + +export type GetUserOrdersSuccessRes = { + success: true; + data: GetUserOrdersResOrderData[]; +}; + +export enum GetUserOrdersErrorCode { + // eslint-disable-next-line no-unused-vars + UNHANDLED_ERROR = 'Unhandled error', +} + +export type GetUserOrdersErrorRes = { + success: false; + data: GetUserOrdersErrorCode; +}; + +type GetUserOrdersRes = GetUserOrdersSuccessRes | GetUserOrdersErrorRes; + +export default GetUserOrdersRes; diff --git a/src/models/Orders.ts b/src/models/Orders.ts index 89320e7..11b9dc6 100644 --- a/src/models/Orders.ts +++ b/src/models/Orders.ts @@ -6,6 +6,7 @@ import { OrderWithAllTransactions, OrderWithPair, OrderWithPairAndCurrencies, + PairWithCurrencies, } from '@/interfaces/database/modifiedRequests.js'; import configModel from './Config.js'; import dexModel from './Dex.js'; @@ -25,7 +26,7 @@ import GetUserOrdersPageBody from '../interfaces/bodies/orders/GetUserOrdersPage import GetUserOrdersBody from '../interfaces/bodies/orders/GetUserOrdersBody.js'; import CancelOrderBody from '../interfaces/bodies/orders/CancelOrderBody.js'; import ApplyOrderBody from '../interfaces/bodies/orders/ApplyOrderBody.js'; -import Order from '../schemes/Order'; +import Order, { OrderStatus, OrderType } from '../schemes/Order'; import User from '../schemes/User'; import Transaction from '../schemes/Transaction'; import Pair from '../schemes/Pair'; @@ -387,17 +388,78 @@ class OrdersModel { } } - async getUserOrders(body: GetUserOrdersBody) { + async getUserOrders({ + address, + offset, + limit, + filterInfo: { status, type, date }, + }: { + address: string; + offset: number; + limit: number; + filterInfo: { + status?: 'active' | 'finished'; + type?: 'buy' | 'sell'; + date?: { + from: number; + to: number; + }; + }; + }): Promise< + | { + success: false; + data: 'Internal error'; + } + | { + success: true; + data: { + id: number; + type: string; + timestamp: number; + side: string; + price: string; + amount: string; + total: string; + pair_id: number; + user_id: number; + status: string; + left: string; + hasNotification: boolean; + + pair: PairWithCurrencies; + + first_currency: Currency; + second_currency: Currency; + isInstant: boolean; + }[]; + } + > { try { - const userRow = await userModel.getUserRow(body.userData.address); + const userRow = await userModel.getUserRow(address); if (!userRow) throw new Error('Invalid address from token.'); - const orders = (await Order.findAll({ + const ordersRows = (await Order.findAll({ where: { user_id: userRow.id, + ...(status !== undefined + ? { + status: + status === 'finished' + ? OrderStatus.FINISHED + : OrderStatus.ACTIVE, + } + : {}), + ...(type !== undefined + ? { type: type === 'buy' ? OrderType.BUY : OrderType.SELL } + : {}), + ...(date !== undefined + ? { timestamp: { [Op.between]: [date.from, date.to] } } + : {}), }, order: [['timestamp', 'DESC']], + limit, + offset, include: [ { model: Pair, @@ -407,8 +469,22 @@ class OrdersModel { ], })) as OrderWithPairAndCurrencies[]; - const result = orders.map((e) => ({ - ...e.toJSON(), + const result = ordersRows.map((e) => ({ + id: e.id, + type: e.type, + timestamp: e.timestamp, + side: e.side, + price: e.price, + amount: e.amount, + total: e.total, + pair_id: e.pair_id, + user_id: e.user_id, + status: e.status, + left: e.left, + hasNotification: e.hasNotification, + + pair: e.pair, + first_currency: e.pair.first_currency, second_currency: e.pair.second_currency, isInstant: dexModel.isBotActive(e.id), diff --git a/src/routes/orders.router.ts b/src/routes/orders.router.ts index 712b106..28fc64d 100644 --- a/src/routes/orders.router.ts +++ b/src/routes/orders.router.ts @@ -1,5 +1,6 @@ import express from 'express'; import { createOrderValidator } from '@/interfaces/bodies/orders/CreateOrderBody.js'; +import { getUserOrdersValidator } from '@/interfaces/bodies/orders/GetUserOrdersBody.js'; import middleware from '../middleware/middleware.js'; import ordersController from '../controllers/orders.controller.js'; @@ -23,7 +24,11 @@ ordersRouter.post( ); ordersRouter.post('/orders/get-page', ordersController.getOrdersPage); ordersRouter.post('/orders/get-user-page', ordersController.getUserOrdersPage); -ordersRouter.post('/orders/get', ordersController.getUserOrders); +ordersRouter.patch( + '/orders/get', + middleware.expressValidator(getUserOrdersValidator), + ordersController.getUserOrders.bind(ordersController), +); ordersRouter.post('/orders/cancel', ordersController.cancelOrder); ordersRouter.post('/orders/get-candles', ordersController.getCandles); ordersRouter.post('/orders/get-chart-orders', ordersController.getChartOrders); diff --git a/src/schemes/Order.ts b/src/schemes/Order.ts index 08b7b6e..00de457 100644 --- a/src/schemes/Order.ts +++ b/src/schemes/Order.ts @@ -1,6 +1,22 @@ import { Model, DataTypes } from 'sequelize'; import sequelize from '../sequelize'; +export enum OrderType { + // eslint-disable-next-line no-unused-vars + BUY = 'buy', + // eslint-disable-next-line no-unused-vars + SELL = 'sell', +} + +export enum OrderStatus { + // eslint-disable-next-line no-unused-vars + ACTIVE = 'active', + // eslint-disable-next-line no-unused-vars + ZERO = 'zero', + // eslint-disable-next-line no-unused-vars + FINISHED = 'finished', +} + class Order extends Model { declare readonly id: number; @@ -8,6 +24,7 @@ class Order extends Model { declare timestamp: number; + // Currently not used declare side: string; declare price: string; From 5aa1da59e1f44c1d40e9a04e56ecca4938d3be1c Mon Sep 17 00:00:00 2001 From: Andrew Besedin Date: Mon, 16 Feb 2026 15:52:41 +0300 Subject: [PATCH 2/8] add: add get-user-orders-all-pairs endpoint --- src/controllers/orders.controller.ts | 41 ++++++++++++ .../orders/GetUserOrdersAllPairsBody.ts | 9 +++ src/interfaces/database/modifiedRequests.ts | 5 ++ .../orders/GetUserOrdersAllPairsRes.ts | 30 +++++++++ src/models/Orders.ts | 65 +++++++++++++++++-- src/routes/orders.router.ts | 7 ++ 6 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 src/interfaces/bodies/orders/GetUserOrdersAllPairsBody.ts create mode 100644 src/interfaces/responses/orders/GetUserOrdersAllPairsRes.ts diff --git a/src/controllers/orders.controller.ts b/src/controllers/orders.controller.ts index 813dad3..2914175 100644 --- a/src/controllers/orders.controller.ts +++ b/src/controllers/orders.controller.ts @@ -7,6 +7,11 @@ import GetUserOrdersRes, { GetUserOrdersResCurrency, GetUserOrdersResOrderData, } from '@/interfaces/responses/orders/GetUserOrdersRes'; +import GetUserOrdersAllPairsBody from '@/interfaces/bodies/orders/GetUserOrdersAllPairsBody'; +import GetUserOrdersAllPairsRes, { + GetUserOrdersAllPairsErrorCode, + GetUserOrdersAllPairsResPair, +} from '@/interfaces/responses/orders/GetUserOrdersAllPairsRes'; import candlesModel from '../models/Candles'; import ordersModel from '../models/Orders'; import CreateOrderBody from '../interfaces/bodies/orders/CreateOrderBody'; @@ -291,6 +296,42 @@ class OrdersController { } }; + getUserOrdersAllPairs = async (req: Request, res: Response) => { + try { + const body = req.body as GetUserOrdersAllPairsBody; + const { userData } = body; + + const getUserOrdersAllPairsResult = await ordersModel.getUserOrdersAllPairs( + userData.address, + ); + + const pairs = getUserOrdersAllPairsResult.data; + + const responsePairs: GetUserOrdersAllPairsResPair[] = pairs.map((pair) => ({ + id: pair.id, + firstCurrency: { + id: pair.firstCurrency.id, + ticker: pair.firstCurrency.ticker, + }, + secondCurrency: { + id: pair.secondCurrency.id, + ticker: pair.secondCurrency.ticker, + }, + })); + + res.status(200).send({ + success: true, + data: responsePairs, + }); + } catch (err) { + console.log(err); + res.status(500).send({ + success: false, + data: GetUserOrdersAllPairsErrorCode.UNHANDLED_ERROR, + }); + } + }; + async cancelOrder(req: Request, res: Response) { try { if (!(req.body as CancelOrderBody).orderId) diff --git a/src/interfaces/bodies/orders/GetUserOrdersAllPairsBody.ts b/src/interfaces/bodies/orders/GetUserOrdersAllPairsBody.ts new file mode 100644 index 0000000..06b6de2 --- /dev/null +++ b/src/interfaces/bodies/orders/GetUserOrdersAllPairsBody.ts @@ -0,0 +1,9 @@ +import UserData from '@/interfaces/common/UserData'; + +interface GetUserOrdersAllPairsBody { + userData: UserData; +} + +export const getUserOrdersAllPairsValidator = []; + +export default GetUserOrdersAllPairsBody; diff --git a/src/interfaces/database/modifiedRequests.ts b/src/interfaces/database/modifiedRequests.ts index 9a0f026..a55aa3d 100644 --- a/src/interfaces/database/modifiedRequests.ts +++ b/src/interfaces/database/modifiedRequests.ts @@ -32,3 +32,8 @@ export interface PairWithCurrencies extends Pair { export interface OrderWithPairAndCurrencies extends Order { pair: PairWithCurrencies; } + +export interface GroupByIdPair { + pair_id: number; + pair: PairWithCurrencies; +} diff --git a/src/interfaces/responses/orders/GetUserOrdersAllPairsRes.ts b/src/interfaces/responses/orders/GetUserOrdersAllPairsRes.ts new file mode 100644 index 0000000..8dd7210 --- /dev/null +++ b/src/interfaces/responses/orders/GetUserOrdersAllPairsRes.ts @@ -0,0 +1,30 @@ +export type GetUserOrdersAllPairsResPair = { + id: number; + firstCurrency: { + id: number; + ticker: string | null; + }; + secondCurrency: { + id: number; + ticker: string | null; + }; +}; + +export type GetUserOrdersAllPairsSuccessRes = { + success: true; + data: GetUserOrdersAllPairsResPair[]; +}; + +export enum GetUserOrdersAllPairsErrorCode { + // eslint-disable-next-line no-unused-vars + UNHANDLED_ERROR = 'Unhandled error', +} + +export type GetUserOrdersAllPairsErrorRes = { + success: false; + data: GetUserOrdersAllPairsErrorCode; +}; + +type GetUserOrdersAllPairsRes = GetUserOrdersAllPairsSuccessRes | GetUserOrdersAllPairsErrorRes; + +export default GetUserOrdersAllPairsRes; diff --git a/src/models/Orders.ts b/src/models/Orders.ts index 11b9dc6..ff335c9 100644 --- a/src/models/Orders.ts +++ b/src/models/Orders.ts @@ -3,12 +3,10 @@ import Decimal from 'decimal.js'; import TransactionWithOrders from '@/interfaces/common/Transaction.js'; import Currency from '@/schemes/Currency.js'; import { - OrderWithAllTransactions, - OrderWithPair, + GroupByIdPair, OrderWithPairAndCurrencies, PairWithCurrencies, } from '@/interfaces/database/modifiedRequests.js'; -import configModel from './Config.js'; import dexModel from './Dex.js'; import userModel from './User.js'; import exchangeModel from './ExchangeTransactions.js'; @@ -23,7 +21,6 @@ import io from '../server.js'; import ApplyTip from '../interfaces/responses/orders/ApplyTip.js'; import CreateOrderBody from '../interfaces/bodies/orders/CreateOrderBody.js'; import GetUserOrdersPageBody from '../interfaces/bodies/orders/GetUserOrdersPageBody.js'; -import GetUserOrdersBody from '../interfaces/bodies/orders/GetUserOrdersBody.js'; import CancelOrderBody from '../interfaces/bodies/orders/CancelOrderBody.js'; import ApplyOrderBody from '../interfaces/bodies/orders/ApplyOrderBody.js'; import Order, { OrderStatus, OrderType } from '../schemes/Order'; @@ -804,6 +801,66 @@ class OrdersModel { return { success: false, data: 'Internal error' }; } } + + static GET_USER_ORDERS_ALL_PAIRS_USER_NOT_FOUND = 'No user found'; + getUserOrdersAllPairs = async ( + address: string, + ): Promise<{ + success: true; + data: { + id: number; + firstCurrency: { + id: number; + ticker: string | null; + }; + secondCurrency: { + id: number; + ticker: string | null; + }; + }[]; + }> => { + const userRow = await userModel.getUserRow(address); + + if (!userRow) { + throw new Error(OrdersModel.GET_USER_ORDERS_ALL_PAIRS_USER_NOT_FOUND); + } + + const pairsGroupedSelection = (await Order.findAll({ + where: { + user_id: userRow.id, + }, + group: 'pair_id', + include: [ + { + model: Pair, + as: 'pair', + include: ['first_currency', 'second_currency'], + }, + ], + })) as unknown as GroupByIdPair[]; + + const pairs = pairsGroupedSelection.map((e) => { + const firstCurrencyTicker = e.pair.first_currency.asset_info?.ticker; + const secondCurrencyTicker = e.pair.second_currency.asset_info?.ticker; + + return { + id: e.pair.id, + firstCurrency: { + id: e.pair.first_currency.id, + ticker: firstCurrencyTicker ?? null, + }, + secondCurrency: { + id: e.pair.second_currency.id, + ticker: secondCurrencyTicker ?? null, + }, + }; + }); + + return { + success: true, + data: pairs, + }; + }; } const ordersModel = new OrdersModel(); diff --git a/src/routes/orders.router.ts b/src/routes/orders.router.ts index 28fc64d..f9d2c6e 100644 --- a/src/routes/orders.router.ts +++ b/src/routes/orders.router.ts @@ -1,6 +1,8 @@ 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 middleware from '../middleware/middleware.js'; import ordersController from '../controllers/orders.controller.js'; @@ -35,5 +37,10 @@ ordersRouter.post('/orders/get-chart-orders', ordersController.getChartOrders); ordersRouter.post('/orders/get-pair-stats', ordersController.getPairStats); ordersRouter.post('/orders/apply-order', ordersController.applyOrder); ordersRouter.post('/orders/get-trades', ordersController.getTrades); +ordersRouter.get( + '/orders/get-user-orders-pairs', + middleware.expressValidator(getUserOrdersAllPairsValidator), + ordersController.getUserOrdersAllPairs.bind(ordersController), +); export default ordersRouter; From 186e8019ccfe2d803927aa34d0fbabdfa942961f Mon Sep 17 00:00:00 2001 From: Andrew Besedin Date: Mon, 16 Feb 2026 22:17:37 +0300 Subject: [PATCH 3/8] update: add totalItemsCount in get-user-orders response --- src/controllers/orders.controller.ts | 3 ++ .../responses/orders/GetUserOrdersRes.ts | 1 + src/models/Orders.ts | 47 +++++++++++-------- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/controllers/orders.controller.ts b/src/controllers/orders.controller.ts index 2914175..2df33fa 100644 --- a/src/controllers/orders.controller.ts +++ b/src/controllers/orders.controller.ts @@ -240,6 +240,8 @@ class OrdersController { throw new Error('ordersModel.getUserOrders returned Internal error'); } + const { totalItemsCount } = result; + const userOrders = result.data.map((order) => { const mappedOrder: GetUserOrdersResOrderData = { id: order.id, @@ -285,6 +287,7 @@ class OrdersController { res.status(200).send({ success: true, + totalItemsCount, data: userOrders, }); } catch (err) { diff --git a/src/interfaces/responses/orders/GetUserOrdersRes.ts b/src/interfaces/responses/orders/GetUserOrdersRes.ts index 98a6828..960513f 100644 --- a/src/interfaces/responses/orders/GetUserOrdersRes.ts +++ b/src/interfaces/responses/orders/GetUserOrdersRes.ts @@ -55,6 +55,7 @@ export type GetUserOrdersResOrderData = { export type GetUserOrdersSuccessRes = { success: true; + totalItemsCount: number; data: GetUserOrdersResOrderData[]; }; diff --git a/src/models/Orders.ts b/src/models/Orders.ts index ff335c9..f510f38 100644 --- a/src/models/Orders.ts +++ b/src/models/Orders.ts @@ -1,4 +1,4 @@ -import { Op } from 'sequelize'; +import { Op, WhereOptions } from 'sequelize'; import Decimal from 'decimal.js'; import TransactionWithOrders from '@/interfaces/common/Transaction.js'; import Currency from '@/schemes/Currency.js'; @@ -409,6 +409,7 @@ class OrdersModel { } | { success: true; + totalItemsCount: number; data: { id: number; type: string; @@ -436,24 +437,28 @@ class OrdersModel { if (!userRow) throw new Error('Invalid address from token.'); + const ordersSelectWhereClause: WhereOptions = { + user_id: userRow.id, + ...(status !== undefined + ? { + status: + status === 'finished' ? OrderStatus.FINISHED : OrderStatus.ACTIVE, + } + : {}), + ...(type !== undefined + ? { type: type === 'buy' ? OrderType.BUY : OrderType.SELL } + : {}), + ...(date !== undefined + ? { timestamp: { [Op.between]: [date.from, date.to] } } + : {}), + }; + + const totalItemsCount = await Order.count({ + where: ordersSelectWhereClause, + }); + const ordersRows = (await Order.findAll({ - where: { - user_id: userRow.id, - ...(status !== undefined - ? { - status: - status === 'finished' - ? OrderStatus.FINISHED - : OrderStatus.ACTIVE, - } - : {}), - ...(type !== undefined - ? { type: type === 'buy' ? OrderType.BUY : OrderType.SELL } - : {}), - ...(date !== undefined - ? { timestamp: { [Op.between]: [date.from, date.to] } } - : {}), - }, + where: ordersSelectWhereClause, order: [['timestamp', 'DESC']], limit, offset, @@ -487,7 +492,11 @@ class OrdersModel { isInstant: dexModel.isBotActive(e.id), })); - return { success: true, data: result }; + return { + success: true, + totalItemsCount, + data: result, + }; } catch (err) { console.log(err); return { success: false, data: 'Internal error' }; From 4bf4978f5716577c2ac1253f029b5f5bfdc635b2 Mon Sep 17 00:00:00 2001 From: Andrew Besedin Date: Tue, 17 Feb 2026 00:22:16 +0300 Subject: [PATCH 4/8] update: replace get method with patch in get-user-orders-pairs endpoint --- src/routes/orders.router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/orders.router.ts b/src/routes/orders.router.ts index f9d2c6e..ad4260f 100644 --- a/src/routes/orders.router.ts +++ b/src/routes/orders.router.ts @@ -37,7 +37,7 @@ ordersRouter.post('/orders/get-chart-orders', ordersController.getChartOrders); ordersRouter.post('/orders/get-pair-stats', ordersController.getPairStats); ordersRouter.post('/orders/apply-order', ordersController.applyOrder); ordersRouter.post('/orders/get-trades', ordersController.getTrades); -ordersRouter.get( +ordersRouter.patch( '/orders/get-user-orders-pairs', middleware.expressValidator(getUserOrdersAllPairsValidator), ordersController.getUserOrdersAllPairs.bind(ordersController), From a4e0406426675d1aacf964b0a167662d5168fe97 Mon Sep 17 00:00:00 2001 From: Andrew Besedin Date: Tue, 17 Feb 2026 02:26:31 +0300 Subject: [PATCH 5/8] fix: fix get-user-orders-all-pairs issues --- src/interfaces/database/modifiedRequests.ts | 5 +- .../orders/GetUserOrdersAllPairsRes.ts | 4 +- src/models/Orders.ts | 52 ++++++++++--------- src/routes/orders.router.ts | 1 + 4 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/interfaces/database/modifiedRequests.ts b/src/interfaces/database/modifiedRequests.ts index a55aa3d..3e60529 100644 --- a/src/interfaces/database/modifiedRequests.ts +++ b/src/interfaces/database/modifiedRequests.ts @@ -33,7 +33,6 @@ export interface OrderWithPairAndCurrencies extends Order { pair: PairWithCurrencies; } -export interface GroupByIdPair { - pair_id: number; - pair: PairWithCurrencies; +export interface PairWithIdAndCurrencies extends PairWithCurrencies { + id: number; } diff --git a/src/interfaces/responses/orders/GetUserOrdersAllPairsRes.ts b/src/interfaces/responses/orders/GetUserOrdersAllPairsRes.ts index 8dd7210..0523c71 100644 --- a/src/interfaces/responses/orders/GetUserOrdersAllPairsRes.ts +++ b/src/interfaces/responses/orders/GetUserOrdersAllPairsRes.ts @@ -2,11 +2,11 @@ export type GetUserOrdersAllPairsResPair = { id: number; firstCurrency: { id: number; - ticker: string | null; + ticker: string; }; secondCurrency: { id: number; - ticker: string | null; + ticker: string; }; }; diff --git a/src/models/Orders.ts b/src/models/Orders.ts index f510f38..262efa4 100644 --- a/src/models/Orders.ts +++ b/src/models/Orders.ts @@ -3,9 +3,9 @@ import Decimal from 'decimal.js'; import TransactionWithOrders from '@/interfaces/common/Transaction.js'; import Currency from '@/schemes/Currency.js'; import { - GroupByIdPair, OrderWithPairAndCurrencies, PairWithCurrencies, + PairWithIdAndCurrencies, } from '@/interfaces/database/modifiedRequests.js'; import dexModel from './Dex.js'; import userModel from './User.js'; @@ -820,11 +820,11 @@ class OrdersModel { id: number; firstCurrency: { id: number; - ticker: string | null; + ticker: string; }; secondCurrency: { id: number; - ticker: string | null; + ticker: string; }; }[]; }> => { @@ -834,33 +834,37 @@ class OrdersModel { throw new Error(OrdersModel.GET_USER_ORDERS_ALL_PAIRS_USER_NOT_FOUND); } - const pairsGroupedSelection = (await Order.findAll({ - where: { - user_id: userRow.id, - }, - group: 'pair_id', - include: [ - { - model: Pair, - as: 'pair', - include: ['first_currency', 'second_currency'], - }, - ], - })) as unknown as GroupByIdPair[]; + // Select distinct pair IDs for the user's orders, then fetch pairs + const distinctPairIdRows = (await Order.findAll({ + attributes: [[sequelize.fn('DISTINCT', sequelize.col('pair_id')), 'pair_id']], + where: { user_id: userRow.id }, + raw: true, + })) as { pair_id: number }[]; - const pairs = pairsGroupedSelection.map((e) => { - const firstCurrencyTicker = e.pair.first_currency.asset_info?.ticker; - const secondCurrencyTicker = e.pair.second_currency.asset_info?.ticker; + const pairIds = distinctPairIdRows.map((row) => row.pair_id); + + const pairsSelection = (await Pair.findAll({ + where: { id: pairIds }, + include: [ + { model: Currency, as: 'first_currency' }, + { model: Currency, as: 'second_currency' }, + ], + attributes: ['id'], + })) as PairWithIdAndCurrencies[]; + + const pairs = pairsSelection.map((e) => { + const firstCurrencyTicker = e.first_currency.name; + const secondCurrencyTicker = e.second_currency.name; return { - id: e.pair.id, + id: e.id, firstCurrency: { - id: e.pair.first_currency.id, - ticker: firstCurrencyTicker ?? null, + id: e.first_currency.id, + ticker: firstCurrencyTicker, }, secondCurrency: { - id: e.pair.second_currency.id, - ticker: secondCurrencyTicker ?? null, + id: e.second_currency.id, + ticker: secondCurrencyTicker, }, }; }); diff --git a/src/routes/orders.router.ts b/src/routes/orders.router.ts index ad4260f..fdf1896 100644 --- a/src/routes/orders.router.ts +++ b/src/routes/orders.router.ts @@ -15,6 +15,7 @@ ordersRouter.use( '/orders/get', '/orders/cancel', '/orders/apply-order', + '/orders/get-user-orders-pairs', ], middleware.verifyToken, ); From ac430e0d172d1d22e64424b98aaa5de55b130a7a Mon Sep 17 00:00:00 2001 From: Andrew Besedin Date: Tue, 17 Feb 2026 10:39:08 +0300 Subject: [PATCH 6/8] update: add get-user-orders pairId filter --- src/controllers/orders.controller.ts | 1 + src/interfaces/bodies/orders/GetUserOrdersBody.ts | 5 +++++ src/models/Orders.ts | 4 +++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/controllers/orders.controller.ts b/src/controllers/orders.controller.ts index 2df33fa..08c3adf 100644 --- a/src/controllers/orders.controller.ts +++ b/src/controllers/orders.controller.ts @@ -224,6 +224,7 @@ class OrdersController { offset, limit, filterInfo: { + pairId: filterInfo.pairId, type: serviceOrderType, status: serviceOrderStatus, date: diff --git a/src/interfaces/bodies/orders/GetUserOrdersBody.ts b/src/interfaces/bodies/orders/GetUserOrdersBody.ts index d5d0c2a..800ec9a 100644 --- a/src/interfaces/bodies/orders/GetUserOrdersBody.ts +++ b/src/interfaces/bodies/orders/GetUserOrdersBody.ts @@ -21,6 +21,7 @@ interface GetUserOrdersBody { limit: number; offset: number; filterInfo: { + pairId?: number; status?: GetUserOrdersBodyStatus; type?: GetUserOrdersBodyType; date?: { @@ -37,6 +38,10 @@ export const getUserOrdersValidator = [ .withMessage('limit must be a positive integer within certain range'), body('offset').isInt({ min: 0 }).withMessage('offset must be a non-negative integer'), 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.status') .optional() .isIn(Object.values(GetUserOrdersBodyStatus)) diff --git a/src/models/Orders.ts b/src/models/Orders.ts index 262efa4..16fede8 100644 --- a/src/models/Orders.ts +++ b/src/models/Orders.ts @@ -389,12 +389,13 @@ class OrdersModel { address, offset, limit, - filterInfo: { status, type, date }, + filterInfo: { pairId, status, type, date }, }: { address: string; offset: number; limit: number; filterInfo: { + pairId?: number; status?: 'active' | 'finished'; type?: 'buy' | 'sell'; date?: { @@ -439,6 +440,7 @@ class OrdersModel { const ordersSelectWhereClause: WhereOptions = { user_id: userRow.id, + ...(pairId !== undefined ? { pair_id: pairId } : {}), ...(status !== undefined ? { status: From afe5ce2fa3c2ce0c1284ae21221ea405241dc062 Mon Sep 17 00:00:00 2001 From: Andrew Besedin Date: Wed, 18 Feb 2026 02:37:27 +0300 Subject: [PATCH 7/8] add: add orders cancel-all endpoint --- src/controllers/orders.controller.ts | 48 ++++++++ src/interfaces/bodies/orders/CancelAllBody.ts | 50 ++++++++ .../responses/orders/CancelAllRes.ts | 17 +++ src/models/Orders.ts | 110 +++++++++++++++++- src/routes/orders.router.ts | 6 + 5 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 src/interfaces/bodies/orders/CancelAllBody.ts create mode 100644 src/interfaces/responses/orders/CancelAllRes.ts diff --git a/src/controllers/orders.controller.ts b/src/controllers/orders.controller.ts index 08c3adf..518318d 100644 --- a/src/controllers/orders.controller.ts +++ b/src/controllers/orders.controller.ts @@ -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) { + 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(); diff --git a/src/interfaces/bodies/orders/CancelAllBody.ts b/src/interfaces/bodies/orders/CancelAllBody.ts new file mode 100644 index 0000000..31438fa --- /dev/null +++ b/src/interfaces/bodies/orders/CancelAllBody.ts @@ -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; diff --git a/src/interfaces/responses/orders/CancelAllRes.ts b/src/interfaces/responses/orders/CancelAllRes.ts new file mode 100644 index 0000000..1fe1096 --- /dev/null +++ b/src/interfaces/responses/orders/CancelAllRes.ts @@ -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; diff --git a/src/models/Orders.ts b/src/models/Orders.ts index 16fede8..c2aa8a7 100644 --- a/src/models/Orders.ts +++ b/src/models/Orders.ts @@ -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(); diff --git a/src/routes/orders.router.ts b/src/routes/orders.router.ts index fdf1896..71a6ec4 100644 --- a/src/routes/orders.router.ts +++ b/src/routes/orders.router.ts @@ -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; From 5eaa10bdfcfbec2e8f637b68f985ad3ab5ce599a Mon Sep 17 00:00:00 2001 From: Andrew Besedin Date: Wed, 18 Feb 2026 17:01:29 +0300 Subject: [PATCH 8/8] update: fix cancel-all endpoint --- src/models/Orders.ts | 44 +++++++++++++++++++++---------------- src/routes/orders.router.ts | 1 + 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/models/Orders.ts b/src/models/Orders.ts index c2aa8a7..2840c49 100644 --- a/src/models/Orders.ts +++ b/src/models/Orders.ts @@ -878,7 +878,6 @@ class OrdersModel { }; 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, @@ -916,25 +915,30 @@ class OrdersModel { ...(date !== undefined ? { timestamp: { [Op.between]: [date.from, date.to] } } : {}), }; - const ordersToCancelCount = await Order.count({ - where: ordersToCancelWhereClause, - }); + const ORDERS_PER_CANCEL_CHUNK = 10_000; + let lastOrderTimestamp: number | undefined; + let finished = false; - const cancelPromises = []; + while (!finished) { + const orderRows = await Order.findAll({ + where: { + ...ordersToCancelWhereClause, + ...(lastOrderTimestamp !== undefined + ? { timestamp: { [Op.gt]: lastOrderTimestamp } } + : {}), + }, + order: [['timestamp', 'ASC']], + limit: ORDERS_PER_CANCEL_CHUNK, + }); - for (let offset = 0; offset < ordersToCancelCount; offset += 1) { - cancelPromises.push(async () => { - const orderRow = await Order.findOne({ - where: ordersToCancelWhereClause, - order: [['timestamp', 'ASC']], - offset, - limit: 1, - }); + const lastOrderRow = orderRows.at(-1); - if (!orderRow) { - throw new Error(OrdersModel.CANCEL_ALL_ORDER_NOT_FOUND); - } + // if there are no more orders to cancel, finish the process + if (!lastOrderRow) { + finished = true; + } + for (const orderRow of orderRows) { await this.cancelOrderNotifications(orderRow, userRow); const eps = new Decimal(1e-8); @@ -977,10 +981,12 @@ class OrdersModel { transaction.afterCommit(() => { sendDeleteOrderMessage(io, orderRow.pair_id.toString(), orderRow.id.toString()); }); - }); - } + } - await Promise.all(cancelPromises); + if (lastOrderRow) { + lastOrderTimestamp = lastOrderRow.timestamp; + } + } return { success: true }; }; diff --git a/src/routes/orders.router.ts b/src/routes/orders.router.ts index 71a6ec4..5e36de9 100644 --- a/src/routes/orders.router.ts +++ b/src/routes/orders.router.ts @@ -17,6 +17,7 @@ ordersRouter.use( '/orders/cancel', '/orders/apply-order', '/orders/get-user-orders-pairs', + '/orders/cancel-all', ], middleware.verifyToken, );