Merge pull request #23 from hyle-team/dev
This commit is contained in:
commit
e4bd5e65df
31 changed files with 1481 additions and 170 deletions
14
package-lock.json
generated
14
package-lock.json
generated
|
|
@ -15,6 +15,7 @@
|
|||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^8.2.1",
|
||||
"express-validator": "^7.3.1",
|
||||
"jimp": "^0.22.8",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"nanoid": "^5.1.5",
|
||||
|
|
@ -4280,6 +4281,19 @@
|
|||
"express": ">= 4.11"
|
||||
}
|
||||
},
|
||||
"node_modules/express-validator": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.3.1.tgz",
|
||||
"integrity": "sha512-IGenaSf+DnWc69lKuqlRE9/i/2t5/16VpH5bXoqdxWz1aCpRvEdrBuu1y95i/iL5QP8ZYVATiwLFhwk3EDl5vg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21",
|
||||
"validator": "~13.15.23"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express/node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^8.2.1",
|
||||
"express-validator": "^7.3.1",
|
||||
"jimp": "^0.22.8",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"nanoid": "^5.1.5",
|
||||
|
|
|
|||
3
shared/constants.ts
Normal file
3
shared/constants.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const NON_NEGATIVE_REAL_NUMBER_REGEX = /^\d+(\.\d+)?$/;
|
||||
|
||||
export const AUTH_MESSAGE_EXPIRATION_TIME_MS = 5 * 60 * 1000; // 5 minutes
|
||||
|
|
@ -1,13 +1,39 @@
|
|||
import jwt from 'jsonwebtoken';
|
||||
import dotenv from 'dotenv';
|
||||
import { Request, Response } from 'express';
|
||||
import crypto from 'crypto';
|
||||
|
||||
import AuthData from '@/interfaces/bodies/user/AuthData.js';
|
||||
import RequestAuthBody from '@/interfaces/bodies/auth/RequestAuthBody.js';
|
||||
import authMessagesModel from '@/models/AuthMessages.js';
|
||||
import { AUTH_MESSAGE_EXPIRATION_TIME_MS } from 'shared/constants.js';
|
||||
import RequestAuthRes from '@/interfaces/responses/auth/RequestAuthRes.js';
|
||||
import sequelize from '@/sequelize.js';
|
||||
import validateWallet from '../methods/validateWallet.js';
|
||||
import userModel from '../models/User.js';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
class AuthController {
|
||||
requestAuth = async (req: Request, res: Response<RequestAuthRes>) => {
|
||||
const { address, alias } = req.body as RequestAuthBody;
|
||||
|
||||
const message = crypto.randomUUID();
|
||||
const expiresAt = new Date(Date.now() + AUTH_MESSAGE_EXPIRATION_TIME_MS);
|
||||
|
||||
const authMessageRow = await authMessagesModel.create({
|
||||
address,
|
||||
alias,
|
||||
message,
|
||||
expiresAt,
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
success: true,
|
||||
data: authMessageRow.message,
|
||||
});
|
||||
};
|
||||
|
||||
async auth(req: Request, res: Response) {
|
||||
try {
|
||||
const userData: AuthData = req.body.data;
|
||||
|
|
@ -18,6 +44,19 @@ class AuthController {
|
|||
return res.status(400).send({ success: false, data: 'Invalid auth data' });
|
||||
}
|
||||
|
||||
const authMessageRow = await authMessagesModel.findOne({
|
||||
address,
|
||||
alias,
|
||||
message,
|
||||
});
|
||||
|
||||
const isAuthMessageValid =
|
||||
!!authMessageRow && authMessageRow.expires_at.getTime() > Date.now();
|
||||
|
||||
if (!isAuthMessageValid) {
|
||||
return res.status(400).send({ success: false, data: 'Invalid auth message' });
|
||||
}
|
||||
|
||||
const dataValid = !!(
|
||||
userData &&
|
||||
userData.address &&
|
||||
|
|
@ -28,17 +67,33 @@ class AuthController {
|
|||
return res.status(400).send({ success: false, data: 'Invalid auth data' });
|
||||
}
|
||||
|
||||
const success = await userModel.add(userData);
|
||||
let token: string | undefined;
|
||||
|
||||
await sequelize.transaction(async (transaction) => {
|
||||
const success = await userModel.add(userData, { transaction });
|
||||
|
||||
if (success) {
|
||||
const token = jwt.sign(
|
||||
await authMessagesModel.deleteOne(
|
||||
{
|
||||
address,
|
||||
alias,
|
||||
message,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
token = jwt.sign(
|
||||
{ ...userData },
|
||||
process.env.JWT_SECRET || '',
|
||||
neverExpires ? undefined : { expiresIn: '24h' },
|
||||
);
|
||||
res.status(200).send({ success, data: token });
|
||||
}
|
||||
});
|
||||
|
||||
if (token !== undefined) {
|
||||
res.status(200).send({ success: true, data: token });
|
||||
} else {
|
||||
res.status(500).send({ success, data: 'Internal error' });
|
||||
res.status(500).send({ success: false, data: 'Internal error' });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ import UserData from '@/interfaces/common/UserData.js';
|
|||
import Currency from '@/schemes/Currency.js';
|
||||
import Pair from '@/schemes/Pair.js';
|
||||
import { Op } from 'sequelize';
|
||||
import GetAssetsPriceRatesBody from '@/interfaces/bodies/dex/GetAssetsPriceRatesBody.js';
|
||||
import GetAssetsPriceRatesRes, {
|
||||
GetAssetsPriceRatesResPriceRate,
|
||||
} from '@/interfaces/responses/dex/GetAssetsPriceRatesRes.js';
|
||||
import User from '../schemes/User.js';
|
||||
import ordersModel from '../models/Orders.js';
|
||||
import dexModel from '../models/Dex.js';
|
||||
|
|
@ -104,10 +108,10 @@ class DexController {
|
|||
return res.status(200).send(result);
|
||||
}
|
||||
|
||||
async getAssetsPriceRates(req: Request, res: Response) {
|
||||
const { assetsIds } = req.body;
|
||||
getAssetsPriceRates = async (req: Request, res: Response<GetAssetsPriceRatesRes>) => {
|
||||
const { assetsIds } = req.body as GetAssetsPriceRatesBody;
|
||||
|
||||
const currencysRows = await Currency.findAll({
|
||||
const currenciesRows = await Currency.findAll({
|
||||
where: {
|
||||
asset_id: {
|
||||
[Op.in]: assetsIds,
|
||||
|
|
@ -115,17 +119,9 @@ class DexController {
|
|||
},
|
||||
});
|
||||
|
||||
if (!currencysRows) {
|
||||
return res.status(200).send({
|
||||
success: false,
|
||||
data: 'Assets with this id doesn`t exists',
|
||||
});
|
||||
}
|
||||
const currencyIds = currenciesRows.map((currency) => currency.id);
|
||||
|
||||
const currencyIds = currencysRows.map((currency) => currency.id);
|
||||
|
||||
const pairsRows = (
|
||||
(await Pair.findAll({
|
||||
const pairsRows = (await Pair.findAll({
|
||||
where: {
|
||||
first_currency_id: {
|
||||
[Op.in]: currencyIds,
|
||||
|
|
@ -139,37 +135,26 @@ class DexController {
|
|||
attributes: ['asset_id'],
|
||||
},
|
||||
],
|
||||
})) || []
|
||||
).map((pair) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
asset_id: pair?.first_currency?.asset_id,
|
||||
rate: pair.rate,
|
||||
}));
|
||||
})) as (Pair & { first_currency: Currency })[];
|
||||
|
||||
if (!pairsRows || pairsRows.length === 0) {
|
||||
return res.status(200).send({
|
||||
success: false,
|
||||
data: 'Assets with this id doesn`t exists',
|
||||
const priceRates: GetAssetsPriceRatesResPriceRate[] = pairsRows.map((pairRow) => {
|
||||
const assetId = pairRow.first_currency.asset_id;
|
||||
|
||||
return {
|
||||
asset_id: assetId,
|
||||
rate: pairRow?.rate ?? null,
|
||||
day_change: pairRow?.coefficient ?? null,
|
||||
day_volume: pairRow?.volume ?? null,
|
||||
day_high: pairRow?.high ?? null,
|
||||
day_low: pairRow?.low ?? null,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// const priceRates = await Promise.all(pairsRows.map(async (pair) => {
|
||||
// const currency = await Currency.findOne({ where: {
|
||||
// id: pair.first_currency_id
|
||||
// }})
|
||||
|
||||
// return {
|
||||
// asset_id: currency?.asset_id,
|
||||
// rate: pair.rate
|
||||
// }
|
||||
// }))
|
||||
|
||||
return res.status(200).send({
|
||||
success: true,
|
||||
priceRates: pairsRows,
|
||||
priceRates,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
async findPairID(req: Request, res: Response) {
|
||||
const { first, second } = req.body;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,28 @@
|
|||
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 GetUserOrdersAllPairsBody from '@/interfaces/bodies/orders/GetUserOrdersAllPairsBody';
|
||||
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';
|
||||
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';
|
||||
|
|
@ -16,70 +34,97 @@ import Currency from '../schemes/Currency';
|
|||
import { validateTokensInput } from '../../shared/utils';
|
||||
|
||||
class OrdersController {
|
||||
async createOrder(req: Request, res: Response) {
|
||||
static CURRENCY_DECIMAL_POINT_NOT_FOUND_ERROR_MSG = 'CURRENCY_DECIMAL_POINT_MISSING';
|
||||
async createOrder(req: Request, res: Response<CreateOrderRes>) {
|
||||
try {
|
||||
const { orderData } = req.body as CreateOrderBody;
|
||||
const body = req.body as CreateOrderBody;
|
||||
const { orderData } = body;
|
||||
const { price, amount, pairId } = orderData;
|
||||
|
||||
const isFull =
|
||||
orderData &&
|
||||
orderData?.type &&
|
||||
orderData?.side &&
|
||||
orderData?.price &&
|
||||
orderData?.amount &&
|
||||
orderData?.pairId;
|
||||
const priceDecimal = new Decimal(price);
|
||||
const amountDecimal = new Decimal(amount);
|
||||
|
||||
const priceDecimal = new Decimal(orderData?.price || 0);
|
||||
const amountDecimal = new Decimal(orderData?.amount || 0);
|
||||
|
||||
const pair = await Pair.findByPk(orderData?.pairId);
|
||||
const pair = await Pair.findByPk(pairId);
|
||||
|
||||
const firstCurrency = await Currency.findByPk(pair?.first_currency_id);
|
||||
|
||||
const secondCurrency = await Currency.findByPk(pair?.second_currency_id);
|
||||
|
||||
if (!pair || !firstCurrency || !secondCurrency) {
|
||||
return res.status(400).send({ success: false, data: 'Invalid pair data' });
|
||||
return res.status(400).send({
|
||||
success: false,
|
||||
data: CreateOrderErrorCode.INVALID_ORDER_DATA,
|
||||
});
|
||||
}
|
||||
|
||||
const firstCurrencyDecimalPoint = firstCurrency?.asset_info?.decimal_point || 12;
|
||||
const secondCurrencyDecimalPoint = secondCurrency?.asset_info?.decimal_point || 12;
|
||||
const firstCurrencyDP = firstCurrency.asset_info?.decimal_point;
|
||||
const secondCurrencyDP = secondCurrency.asset_info?.decimal_point;
|
||||
|
||||
const rangeCorrect = (() => {
|
||||
const priceCorrect = validateTokensInput(
|
||||
orderData?.price,
|
||||
secondCurrencyDecimalPoint,
|
||||
).valid;
|
||||
const amountCorrect = validateTokensInput(
|
||||
orderData?.amount,
|
||||
firstCurrencyDecimalPoint,
|
||||
).valid;
|
||||
|
||||
return priceCorrect && amountCorrect;
|
||||
})();
|
||||
|
||||
const priceDecimalPointCorrect = priceDecimal.toString().replace('.', '').length <= 20;
|
||||
const amountDecimalPointCorrect =
|
||||
amountDecimal.toString().replace('.', '').length <= 18;
|
||||
|
||||
if (!priceDecimalPointCorrect || !amountDecimalPointCorrect) {
|
||||
return res.status(400).send({ success: false, data: 'Invalid pair data' });
|
||||
if (firstCurrencyDP === undefined || secondCurrencyDP === undefined) {
|
||||
throw new Error(OrdersController.CURRENCY_DECIMAL_POINT_NOT_FOUND_ERROR_MSG);
|
||||
}
|
||||
|
||||
if (!isFull || !rangeCorrect)
|
||||
return res.status(400).send({ success: false, data: 'Invalid order data' });
|
||||
const totalDecimal = priceDecimal.mul(amountDecimal);
|
||||
const total = totalDecimal.toFixed();
|
||||
|
||||
const isPriceValid = validateTokensInput(price, secondCurrencyDP).valid;
|
||||
const isAmountValid = validateTokensInput(amount, firstCurrencyDP).valid;
|
||||
const isTotalValid = validateTokensInput(total, secondCurrencyDP).valid;
|
||||
|
||||
const areAmountsValid = isPriceValid && isAmountValid && isTotalValid;
|
||||
|
||||
if (!areAmountsValid) {
|
||||
return res.status(400).send({
|
||||
success: false,
|
||||
data: CreateOrderErrorCode.INVALID_ORDER_DATA,
|
||||
});
|
||||
}
|
||||
|
||||
const result = await ordersModel.createOrder(req.body);
|
||||
|
||||
if (result.data === 'Invalid order data') return res.status(400).send(result);
|
||||
if (result.data === 'Invalid order data')
|
||||
return res.status(400).send({
|
||||
success: false,
|
||||
data: CreateOrderErrorCode.INVALID_ORDER_DATA,
|
||||
});
|
||||
|
||||
if (result.data === 'Same order') return res.status(400).send(result);
|
||||
if (result.data === 'Same order')
|
||||
return res.status(400).send({
|
||||
success: false,
|
||||
data: CreateOrderErrorCode.SAME_ORDER,
|
||||
});
|
||||
|
||||
if (result.data === 'Internal error') return res.status(500).send(result);
|
||||
if (result.data === 'Internal error') {
|
||||
throw new Error('orderModel.createOrder returned Internal error');
|
||||
}
|
||||
|
||||
res.status(200).send(result);
|
||||
if (typeof result.data === 'string') {
|
||||
throw new Error('Invalid orderModel.createOrder result');
|
||||
}
|
||||
|
||||
const createdOrder = result.data;
|
||||
|
||||
res.status(200).send({
|
||||
success: true,
|
||||
data: {
|
||||
id: createdOrder.id,
|
||||
type: createdOrder.type,
|
||||
timestamp: createdOrder.timestamp,
|
||||
side: createdOrder.side,
|
||||
price: createdOrder.price,
|
||||
amount: createdOrder.amount,
|
||||
total: createdOrder.total,
|
||||
pair_id: createdOrder.pairId,
|
||||
user_id: createdOrder.userId,
|
||||
status: createdOrder.status,
|
||||
left: createdOrder.left,
|
||||
hasNotification: createdOrder.hasNotification,
|
||||
immediateMatch: createdOrder.immediateMatch,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
res.status(500).send({ success: false, data: 'Unhandled error' });
|
||||
res.status(500).send({ success: false, data: CreateOrderErrorCode.UNHANDLED_ERROR });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -128,21 +173,171 @@ 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<GetUserOrdersRes>) => {
|
||||
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: {
|
||||
pairId: filterInfo.pairId,
|
||||
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 { totalItemsCount } = result;
|
||||
|
||||
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,
|
||||
totalItemsCount,
|
||||
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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
getUserOrdersAllPairs = async (req: Request, res: Response<GetUserOrdersAllPairsRes>) => {
|
||||
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 {
|
||||
|
|
@ -260,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();
|
||||
|
|
|
|||
13
src/interfaces/bodies/auth/RequestAuthBody.ts
Normal file
13
src/interfaces/bodies/auth/RequestAuthBody.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { body } from 'express-validator';
|
||||
|
||||
interface RequestAuthBody {
|
||||
address: string;
|
||||
alias: string;
|
||||
}
|
||||
|
||||
export const requestAuthBodyValidator = [
|
||||
body('address').isString().notEmpty(),
|
||||
body('alias').isString().notEmpty(),
|
||||
];
|
||||
|
||||
export default RequestAuthBody;
|
||||
14
src/interfaces/bodies/dex/GetAssetsPriceRatesBody.ts
Normal file
14
src/interfaces/bodies/dex/GetAssetsPriceRatesBody.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { body } from 'express-validator';
|
||||
|
||||
interface GetAssetsPriceRatesBody {
|
||||
assetsIds: string[];
|
||||
}
|
||||
|
||||
export const getAssetsPriceRatesValidator = [
|
||||
body('assetsIds')
|
||||
.isArray({ min: 1 })
|
||||
.withMessage('assetsIds must be a non-empty array of strings'),
|
||||
body('assetsIds.*').isString().withMessage('Each assetId must be a string'),
|
||||
];
|
||||
|
||||
export default GetAssetsPriceRatesBody;
|
||||
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;
|
||||
|
|
@ -1,10 +1,24 @@
|
|||
import OfferType from '../../common/OfferType';
|
||||
import Side from '../../common/Side';
|
||||
import { body } from 'express-validator';
|
||||
import { NON_NEGATIVE_REAL_NUMBER_REGEX } from 'shared/constants';
|
||||
import UserData from '../../common/UserData';
|
||||
|
||||
interface OrderData {
|
||||
type: OfferType;
|
||||
side: Side;
|
||||
export enum CreateOrderType {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
BUY = 'buy',
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
SELL = 'sell',
|
||||
}
|
||||
|
||||
export enum CreateOrderSide {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
LIMIT = 'limit',
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
MARKET = 'market',
|
||||
}
|
||||
|
||||
interface CreateOrderData {
|
||||
type: CreateOrderType;
|
||||
side: CreateOrderSide;
|
||||
price: string;
|
||||
amount: string;
|
||||
pairId: string;
|
||||
|
|
@ -12,7 +26,26 @@ interface OrderData {
|
|||
|
||||
interface CreateOrderBody {
|
||||
userData: UserData;
|
||||
orderData: OrderData;
|
||||
orderData: CreateOrderData;
|
||||
}
|
||||
|
||||
export const createOrderValidator = [
|
||||
body('orderData').isObject().withMessage('orderData must be an object'),
|
||||
body('orderData.type')
|
||||
.isIn(Object.values(CreateOrderType))
|
||||
.withMessage(`Invalid orderData.type value`),
|
||||
body('orderData.side')
|
||||
.isIn(Object.values(CreateOrderSide))
|
||||
.withMessage(`Invalid orderData.side value`),
|
||||
body('orderData.price')
|
||||
.isString()
|
||||
.matches(NON_NEGATIVE_REAL_NUMBER_REGEX)
|
||||
.withMessage('orderData.price must be a positive decimal string'),
|
||||
body('orderData.amount')
|
||||
.isString()
|
||||
.matches(NON_NEGATIVE_REAL_NUMBER_REGEX)
|
||||
.withMessage('orderData.amount must be a positive decimal string'),
|
||||
body('orderData.pairId').isString().withMessage('orderData.pairId must be a string'),
|
||||
];
|
||||
|
||||
export default CreateOrderBody;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
import UserData from '@/interfaces/common/UserData';
|
||||
|
||||
interface GetUserOrdersAllPairsBody {
|
||||
userData: UserData;
|
||||
}
|
||||
|
||||
export const getUserOrdersAllPairsValidator = [];
|
||||
|
||||
export default GetUserOrdersAllPairsBody;
|
||||
|
|
@ -1,7 +1,68 @@
|
|||
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: {
|
||||
pairId?: number;
|
||||
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.pairId')
|
||||
.optional()
|
||||
.isInt({ min: 0 })
|
||||
.withMessage('filterInfo.pairId must be a non-negative integer'),
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -32,3 +32,7 @@ export interface PairWithCurrencies extends Pair {
|
|||
export interface OrderWithPairAndCurrencies extends Order {
|
||||
pair: PairWithCurrencies;
|
||||
}
|
||||
|
||||
export interface PairWithIdAndCurrencies extends PairWithCurrencies {
|
||||
id: number;
|
||||
}
|
||||
|
|
|
|||
7
src/interfaces/responses/auth/RequestAuthRes.ts
Normal file
7
src/interfaces/responses/auth/RequestAuthRes.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
interface RequestAuthRes {
|
||||
success: true;
|
||||
// Auth message
|
||||
data: string;
|
||||
}
|
||||
|
||||
export default RequestAuthRes;
|
||||
24
src/interfaces/responses/dex/GetAssetsPriceRatesRes.ts
Normal file
24
src/interfaces/responses/dex/GetAssetsPriceRatesRes.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
export type GetAssetsPriceRatesResPriceRate = {
|
||||
asset_id: string;
|
||||
rate: number | null;
|
||||
day_change: number | null;
|
||||
day_volume: number | null;
|
||||
day_high: number | null;
|
||||
day_low: number | null;
|
||||
};
|
||||
|
||||
export type GetAssetsPriceRatesSuccessRes = {
|
||||
success: true;
|
||||
priceRates: GetAssetsPriceRatesResPriceRate[];
|
||||
};
|
||||
|
||||
export enum GetAssetsPriceRatesErrorCode {}
|
||||
|
||||
export type GetAssetsPriceRatesErrorRes = {
|
||||
success: false;
|
||||
data: GetAssetsPriceRatesErrorCode;
|
||||
};
|
||||
|
||||
type GetAssetsPriceRatesRes = GetAssetsPriceRatesSuccessRes | GetAssetsPriceRatesErrorRes;
|
||||
|
||||
export default GetAssetsPriceRatesRes;
|
||||
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;
|
||||
36
src/interfaces/responses/orders/CreateOrderRes.ts
Normal file
36
src/interfaces/responses/orders/CreateOrderRes.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
export type CreateOrderSuccessRes = {
|
||||
success: true;
|
||||
data: {
|
||||
hasNotification: boolean;
|
||||
id: number;
|
||||
type: string;
|
||||
timestamp: number;
|
||||
side: string;
|
||||
price: string;
|
||||
amount: string;
|
||||
total: string;
|
||||
pair_id: number;
|
||||
user_id: number;
|
||||
status: string;
|
||||
left: string;
|
||||
immediateMatch?: true;
|
||||
};
|
||||
};
|
||||
|
||||
export enum CreateOrderErrorCode {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
INVALID_ORDER_DATA = 'Invalid order data',
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
SAME_ORDER = 'Same order',
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
UNHANDLED_ERROR = 'Unhandled error',
|
||||
}
|
||||
|
||||
export type CreateOrderErrorRes = {
|
||||
success: false;
|
||||
data: CreateOrderErrorCode;
|
||||
};
|
||||
|
||||
type CreateOrderRes = CreateOrderSuccessRes | CreateOrderErrorRes;
|
||||
|
||||
export default CreateOrderRes;
|
||||
30
src/interfaces/responses/orders/GetUserOrdersAllPairsRes.ts
Normal file
30
src/interfaces/responses/orders/GetUserOrdersAllPairsRes.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
export type GetUserOrdersAllPairsResPair = {
|
||||
id: number;
|
||||
firstCurrency: {
|
||||
id: number;
|
||||
ticker: string;
|
||||
};
|
||||
secondCurrency: {
|
||||
id: number;
|
||||
ticker: string;
|
||||
};
|
||||
};
|
||||
|
||||
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;
|
||||
74
src/interfaces/responses/orders/GetUserOrdersRes.ts
Normal file
74
src/interfaces/responses/orders/GetUserOrdersRes.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
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;
|
||||
totalItemsCount: number;
|
||||
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;
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { NextFunction, Request, Response } from 'express';
|
||||
import { ValidationChain, validationResult } from 'express-validator';
|
||||
import { rateLimit } from 'express-rate-limit';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import User from '@/schemes/User';
|
||||
|
|
@ -49,6 +50,49 @@ class Middleware {
|
|||
|
||||
defaultRateLimit = async (req: Request, res: Response, next: NextFunction) =>
|
||||
defaultRateLimitMiddleware(req, res, next);
|
||||
expressValidator(validators: ValidationChain[]) {
|
||||
return [
|
||||
...validators,
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const errors = validationResult(req);
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
res.status(500).send({
|
||||
success: false,
|
||||
data: 'Internal error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
expressJSONErrorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => {
|
||||
const isExpressJSONError =
|
||||
err instanceof SyntaxError && 'status' in err && err.status === 400 && 'body' in err;
|
||||
|
||||
if (isExpressJSONError) {
|
||||
res.status(500).send({
|
||||
success: false,
|
||||
data: 'Internal error',
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
globalErrorHandler = (err: Error, req: Request, res: Response, _next: NextFunction) => {
|
||||
console.error('Global error handler:');
|
||||
console.error(err);
|
||||
res.status(500).send({
|
||||
success: false,
|
||||
data: 'Internal error',
|
||||
});
|
||||
};
|
||||
|
||||
resultGlobalErrorHandler = [this.expressJSONErrorHandler, this.globalErrorHandler];
|
||||
}
|
||||
|
||||
const middleware = new Middleware();
|
||||
|
|
|
|||
89
src/models/AuthMessages.ts
Normal file
89
src/models/AuthMessages.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import { Op, Transaction } from 'sequelize';
|
||||
|
||||
import AuthMessage from '@/schemes/AuthMessage';
|
||||
|
||||
class AuthMessagesModel {
|
||||
create = async (
|
||||
{
|
||||
address,
|
||||
alias,
|
||||
message,
|
||||
expiresAt,
|
||||
}: {
|
||||
address: string;
|
||||
alias: string;
|
||||
message: string;
|
||||
expiresAt: Date;
|
||||
},
|
||||
{ transaction }: { transaction?: Transaction } = {},
|
||||
): Promise<AuthMessage> => {
|
||||
const authMessage = await AuthMessage.create(
|
||||
{
|
||||
address,
|
||||
alias,
|
||||
message,
|
||||
expires_at: expiresAt,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
return authMessage;
|
||||
};
|
||||
|
||||
findOne = async ({
|
||||
address,
|
||||
alias,
|
||||
message,
|
||||
}: {
|
||||
address: string;
|
||||
alias: string;
|
||||
message: string;
|
||||
}): Promise<AuthMessage | null> =>
|
||||
AuthMessage.findOne({
|
||||
where: {
|
||||
address,
|
||||
alias,
|
||||
message,
|
||||
},
|
||||
});
|
||||
|
||||
deleteOne = async (
|
||||
{
|
||||
address,
|
||||
alias,
|
||||
message,
|
||||
}: {
|
||||
address: string;
|
||||
alias: string;
|
||||
message: string;
|
||||
},
|
||||
{ transaction }: { transaction?: Transaction } = {},
|
||||
): Promise<void> => {
|
||||
await AuthMessage.destroy({
|
||||
where: {
|
||||
address,
|
||||
alias,
|
||||
message,
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
};
|
||||
|
||||
deleteAllExpired = async (
|
||||
{ now }: { now: Date },
|
||||
{ transaction }: { transaction?: Transaction } = {},
|
||||
): Promise<void> => {
|
||||
await AuthMessage.destroy({
|
||||
where: {
|
||||
expires_at: {
|
||||
[Op.lt]: now,
|
||||
},
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const authMessagesModel = new AuthMessagesModel();
|
||||
|
||||
export default authMessagesModel;
|
||||
|
|
@ -38,8 +38,58 @@ class DexModel {
|
|||
}
|
||||
|
||||
private getPairsSearchCondition(searchText: string, whitelistedOnly: boolean) {
|
||||
const tickerRegexp = /^[A-Za-z0-9]{1,14}$/;
|
||||
const fullNameRegexp = /^[A-Za-z0-9.,:!?\-() ]*$/;
|
||||
|
||||
const firstFullNameExpr = Sequelize.literal(`"first_currency"."asset_info"->>'full_name'`);
|
||||
const secondFullNameExpr = Sequelize.literal(
|
||||
`"second_currency"."asset_info"->>'full_name'`,
|
||||
);
|
||||
|
||||
const searchCondition: WhereOptions = {
|
||||
[Op.and]: [
|
||||
Sequelize.where(Sequelize.col('first_currency.name'), {
|
||||
[Op.regexp]: tickerRegexp.source,
|
||||
}),
|
||||
{
|
||||
[Op.or]: [
|
||||
Sequelize.where(Sequelize.col('first_currency.asset_info'), {
|
||||
[Op.is]: null,
|
||||
}),
|
||||
Sequelize.where(firstFullNameExpr, { [Op.is]: null }),
|
||||
{
|
||||
[Op.and]: [
|
||||
Sequelize.where(firstFullNameExpr, {
|
||||
[Op.regexp]: fullNameRegexp.source,
|
||||
}),
|
||||
Sequelize.where(Sequelize.fn('char_length', firstFullNameExpr), {
|
||||
[Op.lte]: 400,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
Sequelize.where(Sequelize.col('second_currency.name'), {
|
||||
[Op.regexp]: tickerRegexp.source,
|
||||
}),
|
||||
{
|
||||
[Op.or]: [
|
||||
Sequelize.where(Sequelize.col('second_currency.asset_info'), {
|
||||
[Op.is]: null,
|
||||
}),
|
||||
Sequelize.where(secondFullNameExpr, { [Op.is]: null }),
|
||||
{
|
||||
[Op.and]: [
|
||||
Sequelize.where(secondFullNameExpr, {
|
||||
[Op.regexp]: fullNameRegexp.source,
|
||||
}),
|
||||
Sequelize.where(Sequelize.fn('char_length', secondFullNameExpr), {
|
||||
[Op.lte]: 400,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
[Op.or]: [
|
||||
Sequelize.where(
|
||||
|
|
@ -70,7 +120,20 @@ class DexModel {
|
|||
],
|
||||
};
|
||||
|
||||
return searchCondition;
|
||||
const includeCondition = [
|
||||
{
|
||||
model: Currency,
|
||||
as: 'first_currency',
|
||||
attributes: ['asset_id', 'code', 'id', 'name', 'type', 'whitelisted'],
|
||||
},
|
||||
{
|
||||
model: Currency,
|
||||
as: 'second_currency',
|
||||
attributes: ['asset_id', 'code', 'id', 'name', 'type', 'whitelisted'],
|
||||
},
|
||||
];
|
||||
|
||||
return { searchCondition, includeCondition };
|
||||
}
|
||||
|
||||
async getPairRow(id: number) {
|
||||
|
|
@ -88,7 +151,10 @@ class DexModel {
|
|||
sortOption: PairSortOption,
|
||||
) {
|
||||
try {
|
||||
const searchCondition = this.getPairsSearchCondition(searchText, whitelistedOnly);
|
||||
const { searchCondition, includeCondition } = this.getPairsSearchCondition(
|
||||
searchText,
|
||||
whitelistedOnly,
|
||||
);
|
||||
|
||||
const volumeSortDirection =
|
||||
sortOption === PairSortOption.VOLUME_LOW_TO_HIGH ? 'ASC' : 'DESC';
|
||||
|
|
@ -105,18 +171,7 @@ class DexModel {
|
|||
'volume',
|
||||
'featured',
|
||||
],
|
||||
include: [
|
||||
{
|
||||
model: Currency,
|
||||
as: 'first_currency',
|
||||
attributes: ['asset_id', 'code', 'id', 'name', 'type', 'whitelisted'],
|
||||
},
|
||||
{
|
||||
model: Currency,
|
||||
as: 'second_currency',
|
||||
attributes: ['asset_id', 'code', 'id', 'name', 'type', 'whitelisted'],
|
||||
},
|
||||
],
|
||||
include: includeCondition,
|
||||
where: searchCondition,
|
||||
order: [
|
||||
['volume', volumeSortDirection],
|
||||
|
|
@ -152,21 +207,13 @@ class DexModel {
|
|||
|
||||
async getPairsPagesAmount(searchText: string, whitelistedOnly: boolean) {
|
||||
try {
|
||||
const searchCondition = this.getPairsSearchCondition(searchText, whitelistedOnly);
|
||||
const { searchCondition, includeCondition } = this.getPairsSearchCondition(
|
||||
searchText,
|
||||
whitelistedOnly,
|
||||
);
|
||||
|
||||
const count = await Pair.count({
|
||||
include: [
|
||||
{
|
||||
model: Currency,
|
||||
as: 'first_currency',
|
||||
attributes: ['asset_id', 'code', 'id', 'name', 'type'],
|
||||
},
|
||||
{
|
||||
model: Currency,
|
||||
as: 'second_currency',
|
||||
attributes: ['asset_id', 'code', 'id', 'name', 'type'],
|
||||
},
|
||||
],
|
||||
include: includeCondition,
|
||||
where: searchCondition,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import { Op } 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';
|
||||
import {
|
||||
OrderWithAllTransactions,
|
||||
OrderWithPair,
|
||||
OrderWithPairAndCurrencies,
|
||||
PairWithCurrencies,
|
||||
PairWithIdAndCurrencies,
|
||||
} 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';
|
||||
|
|
@ -22,10 +21,9 @@ 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 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';
|
||||
|
|
@ -68,7 +66,30 @@ class OrdersModel {
|
|||
return matchedOrders;
|
||||
}
|
||||
|
||||
async createOrder(body: CreateOrderBody) {
|
||||
async createOrder(body: CreateOrderBody): Promise<
|
||||
| {
|
||||
success: false;
|
||||
data: string;
|
||||
}
|
||||
| {
|
||||
success: true;
|
||||
data: {
|
||||
id: number;
|
||||
type: string;
|
||||
timestamp: number;
|
||||
side: string;
|
||||
price: string;
|
||||
amount: string;
|
||||
total: string;
|
||||
pairId: number;
|
||||
userId: number;
|
||||
status: string;
|
||||
left: string;
|
||||
hasNotification: boolean;
|
||||
immediateMatch?: true;
|
||||
};
|
||||
}
|
||||
> {
|
||||
try {
|
||||
const { orderData } = body;
|
||||
const { userData } = body;
|
||||
|
|
@ -173,13 +194,41 @@ class OrdersModel {
|
|||
return {
|
||||
success: true,
|
||||
data: {
|
||||
...newOrder.toJSON(),
|
||||
id: newOrder.id,
|
||||
type: newOrder.type,
|
||||
timestamp: newOrder.timestamp,
|
||||
side: newOrder.side,
|
||||
price: newOrder.price,
|
||||
amount: newOrder.amount,
|
||||
total: newOrder.total,
|
||||
pairId: newOrder.pair_id,
|
||||
userId: newOrder.user_id,
|
||||
status: newOrder.status,
|
||||
left: newOrder.left,
|
||||
hasNotification: newOrder.hasNotification,
|
||||
|
||||
immediateMatch: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return { success: true, data: newOrder.toJSON() };
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
id: newOrder.id,
|
||||
type: newOrder.type,
|
||||
timestamp: newOrder.timestamp,
|
||||
side: newOrder.side,
|
||||
price: newOrder.price,
|
||||
amount: newOrder.amount,
|
||||
total: newOrder.total,
|
||||
pairId: newOrder.pair_id,
|
||||
userId: newOrder.user_id,
|
||||
status: newOrder.status,
|
||||
left: newOrder.left,
|
||||
hasNotification: newOrder.hasNotification,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return { success: false, data: 'Internal error' };
|
||||
|
|
@ -336,17 +385,85 @@ class OrdersModel {
|
|||
}
|
||||
}
|
||||
|
||||
async getUserOrders(body: GetUserOrdersBody) {
|
||||
async getUserOrders({
|
||||
address,
|
||||
offset,
|
||||
limit,
|
||||
filterInfo: { pairId, status, type, date },
|
||||
}: {
|
||||
address: string;
|
||||
offset: number;
|
||||
limit: number;
|
||||
filterInfo: {
|
||||
pairId?: number;
|
||||
status?: 'active' | 'finished';
|
||||
type?: 'buy' | 'sell';
|
||||
date?: {
|
||||
from: number;
|
||||
to: number;
|
||||
};
|
||||
};
|
||||
}): Promise<
|
||||
| {
|
||||
success: false;
|
||||
data: 'Internal error';
|
||||
}
|
||||
| {
|
||||
success: true;
|
||||
totalItemsCount: number;
|
||||
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({
|
||||
where: {
|
||||
const ordersSelectWhereClause: WhereOptions = {
|
||||
user_id: userRow.id,
|
||||
},
|
||||
...(pairId !== undefined ? { pair_id: pairId } : {}),
|
||||
...(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: ordersSelectWhereClause,
|
||||
order: [['timestamp', 'DESC']],
|
||||
limit,
|
||||
offset,
|
||||
include: [
|
||||
{
|
||||
model: Pair,
|
||||
|
|
@ -356,14 +473,32 @@ 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),
|
||||
}));
|
||||
|
||||
return { success: true, data: result };
|
||||
return {
|
||||
success: true,
|
||||
totalItemsCount,
|
||||
data: result,
|
||||
};
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return { success: false, data: 'Internal error' };
|
||||
|
|
@ -677,6 +812,184 @@ 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;
|
||||
};
|
||||
secondCurrency: {
|
||||
id: number;
|
||||
ticker: string;
|
||||
};
|
||||
}[];
|
||||
}> => {
|
||||
const userRow = await userModel.getUserRow(address);
|
||||
|
||||
if (!userRow) {
|
||||
throw new Error(OrdersModel.GET_USER_ORDERS_ALL_PAIRS_USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 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 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.id,
|
||||
firstCurrency: {
|
||||
id: e.first_currency.id,
|
||||
ticker: firstCurrencyTicker,
|
||||
},
|
||||
secondCurrency: {
|
||||
id: e.second_currency.id,
|
||||
ticker: secondCurrencyTicker,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: pairs,
|
||||
};
|
||||
};
|
||||
|
||||
static CANCEL_ALL_USER_NOT_FOUND = 'No user found';
|
||||
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 ORDERS_PER_CANCEL_CHUNK = 10_000;
|
||||
let lastOrderTimestamp: number | undefined;
|
||||
let finished = false;
|
||||
|
||||
while (!finished) {
|
||||
const orderRows = await Order.findAll({
|
||||
where: {
|
||||
...ordersToCancelWhereClause,
|
||||
...(lastOrderTimestamp !== undefined
|
||||
? { timestamp: { [Op.gt]: lastOrderTimestamp } }
|
||||
: {}),
|
||||
},
|
||||
order: [['timestamp', 'ASC']],
|
||||
limit: ORDERS_PER_CANCEL_CHUNK,
|
||||
});
|
||||
|
||||
const lastOrderRow = orderRows.at(-1);
|
||||
|
||||
// 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);
|
||||
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());
|
||||
});
|
||||
}
|
||||
|
||||
if (lastOrderRow) {
|
||||
lastOrderTimestamp = lastOrderRow.timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
};
|
||||
}
|
||||
|
||||
const ordersModel = new OrdersModel();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Op } from 'sequelize';
|
||||
import { Op, Transaction } from 'sequelize';
|
||||
import Offer from '../schemes/Offer';
|
||||
import GetUserBody from '../interfaces/bodies/user/GetUserBody';
|
||||
import SetFavouriteCurrsBody from '../interfaces/bodies/user/SetFavouriteCurrsBody';
|
||||
|
|
@ -15,7 +15,7 @@ class UserModel {
|
|||
return selected;
|
||||
}
|
||||
|
||||
async add(userData: UserData) {
|
||||
async add(userData: UserData, { transaction }: { transaction?: Transaction } = {}) {
|
||||
try {
|
||||
const userRow = await this.getUserRow(userData.address);
|
||||
if (userRow) return true;
|
||||
|
|
@ -27,12 +27,15 @@ class UserModel {
|
|||
if (oldAddressOfCurrentAlias) {
|
||||
await User.update(
|
||||
{ address: userData.address },
|
||||
{ where: { alias: userData.alias } },
|
||||
{ where: { alias: userData.alias }, transaction },
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
await User.create({ alias: userData.alias, address: userData.address });
|
||||
await User.create(
|
||||
{ alias: userData.alias, address: userData.address },
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,16 @@
|
|||
import express from 'express';
|
||||
|
||||
import middleware from '@/middleware/middleware.js';
|
||||
import { requestAuthBodyValidator } from '@/interfaces/bodies/auth/RequestAuthBody.js';
|
||||
import authController from '../controllers/auth.controller.js';
|
||||
|
||||
const authRouter = express.Router();
|
||||
|
||||
authRouter.post(
|
||||
'/auth/request-auth',
|
||||
middleware.expressValidator(requestAuthBodyValidator),
|
||||
authController.requestAuth.bind(authController),
|
||||
);
|
||||
authRouter.post('/auth', authController.auth);
|
||||
|
||||
export default authRouter;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import express from 'express';
|
||||
import { getAssetsPriceRatesValidator } from '@/interfaces/bodies/dex/GetAssetsPriceRatesBody.js';
|
||||
import dexController from '../controllers/dex.controller.js';
|
||||
import middleware from '../middleware/middleware.js';
|
||||
|
||||
|
|
@ -9,7 +10,11 @@ dexRouter.post('/dex/get-pairs-pages-amount', dexController.getPairsPagesAmount)
|
|||
dexRouter.post('/dex/get-pair', dexController.getPair);
|
||||
dexRouter.post('/dex/renew-bot', middleware.verifyToken, dexController.registerBot);
|
||||
dexRouter.post('/dex/volume-stats', dexController.volumeStats);
|
||||
dexRouter.post('/dex/get-assets-price-rates', dexController.getAssetsPriceRates);
|
||||
dexRouter.post(
|
||||
'/dex/get-assets-price-rates',
|
||||
middleware.expressValidator(getAssetsPriceRatesValidator),
|
||||
dexController.getAssetsPriceRates,
|
||||
);
|
||||
dexRouter.post('/dex/find-pair', dexController.findPairID);
|
||||
|
||||
export default dexRouter;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
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';
|
||||
|
||||
|
|
@ -11,19 +16,39 @@ ordersRouter.use(
|
|||
'/orders/get',
|
||||
'/orders/cancel',
|
||||
'/orders/apply-order',
|
||||
'/orders/get-user-orders-pairs',
|
||||
'/orders/cancel-all',
|
||||
],
|
||||
middleware.verifyToken,
|
||||
);
|
||||
|
||||
ordersRouter.post('/orders/create', ordersController.createOrder);
|
||||
ordersRouter.post(
|
||||
'/orders/create',
|
||||
middleware.expressValidator(createOrderValidator),
|
||||
ordersController.createOrder,
|
||||
);
|
||||
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);
|
||||
ordersRouter.post('/orders/get-pair-stats', ordersController.getPairStats);
|
||||
ordersRouter.post('/orders/apply-order', ordersController.applyOrder);
|
||||
ordersRouter.post('/orders/get-trades', ordersController.getTrades);
|
||||
ordersRouter.patch(
|
||||
'/orders/get-user-orders-pairs',
|
||||
middleware.expressValidator(getUserOrdersAllPairsValidator),
|
||||
ordersController.getUserOrdersAllPairs.bind(ordersController),
|
||||
);
|
||||
ordersRouter.patch(
|
||||
'/orders/cancel-all',
|
||||
middleware.expressValidator(cancelAllValidator),
|
||||
ordersController.cancelAll.bind(ordersController),
|
||||
);
|
||||
|
||||
export default ordersRouter;
|
||||
|
|
|
|||
45
src/schemes/AuthMessage.ts
Normal file
45
src/schemes/AuthMessage.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import sequelize from '@/sequelize';
|
||||
import { DataTypes, Model } from 'sequelize';
|
||||
|
||||
class AuthMessage extends Model {
|
||||
declare readonly id: number;
|
||||
declare address: string;
|
||||
declare alias: string;
|
||||
declare message: string;
|
||||
declare expires_at: Date;
|
||||
|
||||
declare readonly createdAt: Date;
|
||||
declare readonly updatedAt: Date;
|
||||
}
|
||||
|
||||
AuthMessage.init(
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
address: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
alias: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
message: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
expires_at: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
modelName: 'AuthMessage',
|
||||
},
|
||||
);
|
||||
|
||||
export default AuthMessage;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import express from 'express';
|
|||
import http from 'http';
|
||||
import { Server } from 'socket.io';
|
||||
|
||||
import authMessagesCleanService from '@/workers/authMessagesCleanService';
|
||||
import authRouter from './routes/auth.router';
|
||||
import offersRouter from './routes/offers.router';
|
||||
import userRouter from './routes/user.router';
|
||||
|
|
@ -73,6 +74,7 @@ process.on('unhandledRejection', (reason, promise) => {
|
|||
|
||||
assetsUpdateChecker.run();
|
||||
ordersModerationService.run();
|
||||
authMessagesCleanService.run();
|
||||
exchangeModel.runPairStatsDaemon();
|
||||
statsModel.init();
|
||||
|
||||
|
|
@ -101,6 +103,8 @@ process.on('unhandledRejection', (reason, promise) => {
|
|||
res.send({ success: true, userData: req.body.userData }),
|
||||
);
|
||||
|
||||
app.use(middleware.resultGlobalErrorHandler);
|
||||
|
||||
server.listen(PORT, () => console.log(`Server is running on port ${PORT}`));
|
||||
})();
|
||||
|
||||
|
|
|
|||
41
src/workers/authMessagesCleanService.ts
Normal file
41
src/workers/authMessagesCleanService.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import authMessagesModel from '@/models/AuthMessages';
|
||||
|
||||
const CLEAN_INTERVAL = 60 * 60 * 1000; // 1 hour
|
||||
|
||||
class AuthMessagesCleanService {
|
||||
run = async () => {
|
||||
console.log(
|
||||
`Auth messages clean service is running. Cleaning interval: ${CLEAN_INTERVAL / 1000} sec.`,
|
||||
);
|
||||
|
||||
async function clean() {
|
||||
console.log(`[${new Date()}] Cleaning auth messages...`);
|
||||
|
||||
await authMessagesModel.deleteAllExpired({ now: new Date() });
|
||||
|
||||
console.log(`[${new Date()}] Auth messages cleaned.`);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
try {
|
||||
await clean();
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`[${new Date()}] Error while cleaning auth messages. Continuing on next iteration. Error:`,
|
||||
);
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[${new Date()}] Auth messages cleaned. Next cleaning in ${CLEAN_INTERVAL / 1000} sec.`,
|
||||
);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, CLEAN_INTERVAL));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const authMessagesCleanService = new AuthMessagesCleanService();
|
||||
|
||||
export default authMessagesCleanService;
|
||||
Loading…
Add table
Reference in a new issue