diff --git a/server/api/controllers/trade.js b/server/api/controllers/trade.js index 1faad76857..03c1f0738b 100644 --- a/server/api/controllers/trade.js +++ b/server/api/controllers/trade.js @@ -12,20 +12,62 @@ const getUserTrades = (req, res) => { ); const user_id = req.auth.sub.id; - const { limit, page, order_by, order, start_date, end_date, format } = req.swagger.params; + const { + limit, + page, + order_by, + order, + start_date, + end_date, + format + } = req.swagger.params; const symbol = req.swagger.params.symbol.value; + loggerTrades.info( + req.uuid, + 'controllers/trade/getUserTrades params', + 'user_id', + user_id, + 'symbol', + symbol, + 'limit', + limit.value, + 'page', + page.value, + 'order_by', + order_by.value, + 'order', + order.value, + 'start_date', + start_date.value, + 'end_date', + end_date.value, + 'format', + format.value + ); + if (symbol && !toolsLib.subscribedToPair(symbol)) { loggerTrades.error(req.uuid, 'controllers/trade/getUserTrades', 'Invalid symbol'); return res.status(400).json({ message: 'Invalid symbol' }); } - toolsLib.order.getAllUserTradesByKitId(user_id, symbol, limit.value, page.value, order_by.value, order.value, start_date.value, end_date.value, format.value) + toolsLib.order.getAllUserTradesByKitId( + user_id, + symbol, + limit.value, + page.value, + order_by.value, + order.value, + start_date.value, + end_date.value, + format.value + ) .then((data) => { - if (format.value) { + if (format.value == 'csv') { res.setHeader('Content-disposition', `attachment; filename=${toolsLib.getKitConfig().api_name}-trades.csv`); res.set('Content-Type', 'text/csv'); - return res.status(202).send(data); + res.status(202); + return res.send(data); } else { return res.json(data); } @@ -41,20 +83,68 @@ const getAdminTrades = (req, res) => { const { user_id, symbol, limit, page, order_by, order, start_date, end_date, format } = req.swagger.params; + loggerTrades.info( + req.uuid, + 'controllers/trade/getAdminTrades params', + 'user_id', + user_id.value, + 'symbol', + symbol.value, + 'limit', + limit.value, + 'page', + page.value, + 'order_by', + order_by.value, + 'order', + order.value, + 'start_date', + start_date.value, + 'end_date', + end_date.value, + 'format', + format.value + ); + + if (symbol.value && !toolsLib.subscribedToPair(symbol.value)) { + loggerTrades.error(req.uuid, 'controllers/trade/getUserTrades', 'Invalid symbol'); + return res.status(400).json({ message: 'Invalid symbol' }); + } + let promiseQuery; if (user_id.value) { - promiseQuery = toolsLib.order.getAllUserTradesByKitId(user_id.value, symbol.value, limit.value, page.value, order_by.value, order.value, start_date.value, end_date.value, format.value); + promiseQuery = toolsLib.order.getAllUserTradesByKitId( + user_id.value, + symbol.value, + limit.value, + page.value, + order_by.value, + order.value, + start_date.value, + end_date.value, + format.value + ); } else { - promiseQuery = toolsLib.order.getAllTradesNetwork(symbol.value, limit.value, page.value, order_by.value, order.value, start_date.value, end_date.value, format.value); + promiseQuery = toolsLib.order.getAllTradesNetwork( + symbol.value, + limit.value, + page.value, + order_by.value, + order.value, + start_date.value, + end_date.value, + format.value + ); } promiseQuery .then((data) => { - if (format.value) { - res.setHeader('Content-disposition', `attachment; filename=${toolsLib.getKitConfig().api_name}-users-trades.csv`); + if (format.value === 'csv') { + res.setHeader('Content-disposition', `attachment; filename=${user_id.value ? `user-${user_id.value}-` : ''}trades.csv`); res.set('Content-Type', 'text/csv'); - return res.status(202).send(data); + res.status(202); + return res.send(data); } else { return res.json(data); } diff --git a/server/api/swagger/swagger.yaml b/server/api/swagger/swagger.yaml index 65ee84349e..9ff305edb1 100644 --- a/server/api/swagger/swagger.yaml +++ b/server/api/swagger/swagger.yaml @@ -12,6 +12,7 @@ consumes: produces: - application/json - text/csv + - text/plain securityDefinitions: Bearer: @@ -1572,14 +1573,12 @@ paths: required: false type: number format: int32 - default: 50 - in: query name: page description: Page of data to retrieve required: false type: number format: int32 - default: 1 - in: query name: order_by description: Field to order data @@ -1607,7 +1606,7 @@ paths: name: format description: Specify data format required: false - enum: ['csv'] + enum: ['csv', 'all'] type: string responses: 200: @@ -2988,7 +2987,7 @@ paths: name: format description: Specify data format required: false - enum: ['csv'] + enum: ['csv', 'all'] type: string tags: - Admin diff --git a/server/package.json b/server/package.json index 61b2db7526..8ddc7d9782 100644 --- a/server/package.json +++ b/server/package.json @@ -15,6 +15,7 @@ "license": "bitHolla Inc.", "main": "app.js", "dependencies": { + "JSONStream": "1.3.5", "bcryptjs": "2.4.3", "bluebird": "3.5.3", "body-parser": "1.19.0", @@ -28,8 +29,8 @@ "flat": "5.0.0", "geoip-lite": "1.4.1", "helmet": "3.12.0", - "hollaex-node-lib": "github:bitholla/hollaex-node-lib#2.5", - "hollaex-tools-lib": "github:bitholla/hollaex-tools-lib#2.8", + "hollaex-node-lib": "github:bitholla/hollaex-node-lib#2.6", + "hollaex-tools-lib": "github:bitholla/hollaex-tools-lib#2.9", "http": "0.0.0", "install": "0.10.4", "json2csv": "4.5.4", @@ -48,6 +49,7 @@ "otp": "0.1.3", "pg": "6.4.2", "pg-hstore": "2.3.2", + "pg-query-stream": "4.1.0", "pm2": "2.10.1", "pmx": "1.6.4", "random-string": "0.2.0", diff --git a/server/plugins.js b/server/plugins.js index 5c24093caa..60339f3735 100644 --- a/server/plugins.js +++ b/server/plugins.js @@ -125,18 +125,6 @@ checkStatus() }, optional: true }, - limit: { - in: ['query'], - errorMessage: 'must be an integer', - isInt: true, - optional: true - }, - page: { - in: ['query'], - errorMessage: 'must be an integer', - isInt: true, - optional: true - }, search: { in: ['query'], errorMessage: 'must be a string', @@ -154,48 +142,71 @@ checkStatus() return res.status(400).json({ errors: errors.array() }); } - const { limit, page, name, search } = req.query; + const { name, search } = req.query; - let promiseQuery = toolsLib.plugin.getPaginatedPlugins(limit, page, search); + let promiseQuery = null; if (name) { promiseQuery = toolsLib.plugin.getPlugin( name, { raw: true, - attributes: [ - 'name', - 'version', - 'enabled', - 'author', - 'description', - 'bio', - 'url', - 'logo', - 'icon', - 'documentation', - 'web_view', - 'public_meta', - 'type', - 'admin_view', - 'created_at', - 'updated_at' - ] + attributes: { + exclude: [ + 'id', + 'script', + 'meta', + 'prescript', + 'postscript' + ] + } } - ); - } - - promiseQuery - .then((plugins) => { - if (name) { - if (!plugins) { + ) + .then((data) => { + if (!data) { throw new Error('Plugin not found'); } else { - plugins.enabled_admin_view = !!plugins.admin_view; - delete plugins.admin_view; + data.enabled_admin_view = !!data.admin_view; + return lodash.omit(data, [ 'admin_view' ]); } - } - return res.json(plugins); + }); + } else { + const options = { + where: {}, + raw: true, + attributes: { + exclude: [ + 'id', + 'script', + 'meta', + 'prescript', + 'postscript' + ] + }, + order: [[ 'id', 'asc' ]] + }; + + if (search) { + options.where = { + name: { [sequelize.Op.like]: `%${search}%` } + }; + } + + promiseQuery = Plugin.findAndCountAll(options) + .then((data) => { + return { + count: data.count, + data: data.rows.map((plugin) => { + plugin.enabled_admin_view = !!plugin.admin_view; + return lodash.omit(plugin, [ 'admin_view' ]); + }) + }; + }); + } + + promiseQuery + .then((data) => { + return res.json(data); }) .catch((err) => { loggerPlugin.error(req.uuid, 'GET /plugins err', err.message); @@ -442,27 +453,34 @@ checkStatus() loggerPlugin.info(req.uuid, 'PUT /plugins name', name, 'version', version); - let sameTypePlugins = []; + let sameTypePlugin = null; if (type) { - sameTypePlugins = Plugin.findAll({ - where: { type } + sameTypePlugin = Plugin.findOne({ + where: { + type, + name: { + [sequelize.Op.not]: name + } + }, + raw: true, + attributes: ['id', 'name', 'type'] }); } bluebird.all([ toolsLib.plugin.getPlugin(name), - sameTypePlugins + sameTypePlugin ]) - .then(([ plugin, sameType ]) => { + .then(([ plugin, sameTypePlugin ]) => { if (!plugin) { throw new Error('Plugin not installed'); } if (plugin.version === version) { throw new Error('Version is already installed'); } - if (sameType.length > 0 && type && plugin.type !== type) { - throw new Error(`Plugin with type ${type} already installed`); + if (sameTypePlugin) { + throw new Error(`${name} version ${version} cannot be ran in parallel with an installed plugin (${sameTypePlugin.name}). Uninstall the plugin ${sameTypePlugin.name} before updating this plugin.`); } const updatedPlugin = { @@ -811,24 +829,31 @@ checkStatus() loggerPlugin.info(req.uuid, 'POST /plugins name', name, 'version', version); - const whereArray = [ - { name } - ]; + let sameTypePlugin = null; if (type) { - whereArray.push( - { type } - ); + sameTypePlugin = Plugin.findOne({ + where: { type }, + raw: true, + attributes: ['id', 'name', 'type'] + }); } - Plugin.findAll({ - where: { - [sequelize.Op.or]: whereArray - } - }) - .then((plugins) => { - if (plugins.length > 0) { - throw new Error('Plugin with same name or type is already installed'); + bluebird.all([ + Plugin.findOne({ + where: { name }, + raw: true, + attributes: ['id', 'name'] + }), + sameTypePlugin + ]) + .then(([ sameNamePlugin, sameTypePlugin ]) => { + if (sameNamePlugin) { + throw new Error(`Plugin ${name} is already installed`); + } + + if (sameTypePlugin) { + throw new Error(`${name} cannot be ran in parallel with an installed plugin (${sameTypePlugin.name}). Uninstall the plugin ${sameTypePlugin.name} before installing this plugin.`); } const newPlugin = {