diff --git a/api/http/chatrooms.js b/api/http/chatrooms.js new file mode 100644 index 00000000..a216a331 --- /dev/null +++ b/api/http/chatrooms.js @@ -0,0 +1,34 @@ +'use strict'; + +const Router = require('../../helpers/router'); +const httpApi = require('../../helpers/httpApi'); + +/** + * Binds api with modules and creates common url. + * - End point: `/api/chatrooms` + * + * - Sanitized + * - get /:ID + * - get /:ID/:ID + * @memberof module:chatrooms + * @requires helpers/Router + * @requires helpers/httpApi + * @constructor + * @param {Object} chatroomsModule - Module chats instance. + * @param {scope} app - Network app. + */ +// Constructor +function ChatroomsHttpApi (chatroomsModule, app) { + + const router = new Router(); + + router.map(chatroomsModule.internal, { + 'get /U*/U*': 'getMessages', + 'get /U*': 'getChats', + }); + + + httpApi.registerEndpoint('/api/chatrooms', app, router, chatroomsModule.isLoaded); +} + +module.exports = ChatroomsHttpApi; \ No newline at end of file diff --git a/api/http/node.js b/api/http/node.js new file mode 100644 index 00000000..0bcb8f13 --- /dev/null +++ b/api/http/node.js @@ -0,0 +1,32 @@ +'use strict'; + +var Router = require('../../helpers/router'); +var httpApi = require('../../helpers/httpApi'); +var schema = require('../../schema/node.js'); + +/** + * Binds api with modules and creates common url. + * - End point: `/api/node` + * - Public API: + - get /status + * @memberof module:node + * @requires helpers/Router + * @requires helpers/httpApi + * @constructor + * @param {Object} nodeModule - Module node instance. + * @param {scope} app - Network app. + */ + +function NodeHttpApi (nodeModule, app) { + + var router = new Router(); + + router.map(nodeModule.shared, { + 'get /status': 'getStatus', + }); + + + httpApi.registerEndpoint('/api/node', app, router, nodeModule.isLoaded); +} + +module.exports = NodeHttpApi; diff --git a/app.js b/app.js index 32c4588b..98f4b069 100644 --- a/app.js +++ b/app.js @@ -39,7 +39,6 @@ var httpApi = require('./helpers/httpApi.js'); var Sequence = require('./helpers/sequence.js'); var util = require('util'); var z_schema = require('./helpers/z_schema.js'); - process.stdin.resume(); var versionBuild = fs.readFileSync(path.join(__dirname, 'build'), 'utf8'); @@ -51,7 +50,7 @@ var lastCommit = ''; if (typeof gc !== 'undefined') { setInterval(function () { -// eslint-disable-next-line no-undef + // eslint-disable-next-line no-undef gc(); }, 60000); } @@ -138,7 +137,10 @@ var config = { multisignatures: './modules/multisignatures.js', dapps: './modules/dapps.js', chats: './modules/chats.js', + chatrooms: './modules/chatrooms.js', states: './modules/states.js', + node: './modules/node.js', + chats: './modules/chats.js', crypto: './modules/crypto.js', sql: './modules/sql.js', cache: './modules/cache.js' @@ -148,7 +150,9 @@ var config = { blocks: { http: './api/http/blocks.js' }, dapps: { http: './api/http/dapps.js' }, chats: { http: './api/http/chats.js' }, + chatrooms: { http: './api/http/chatrooms.js' }, states: { http: './api/http/states.js' }, + node: { http: './api/http/node.js' }, delegates: { http: './api/http/delegates.js' }, loader: { http: './api/http/loader.js' }, multisignatures: { http: './api/http/multisignatures.js' }, @@ -165,8 +169,11 @@ var config = { * The Object is initialized here and pass to others as parameter. * @property {object} - Logger instance. */ -var logger = new Logger({ echo: appConfig.consoleLogLevel, errorLevel: appConfig.fileLogLevel, - filename: appConfig.logFileName }); +var logger = new Logger({ + echo: appConfig.consoleLogLevel, + errorLevel: appConfig.fileLogLevel, + filename: appConfig.logFileName +}); // Trying to get last git commit try { @@ -182,7 +189,10 @@ try { var d = require('domain').create(); d.on('error', function (err) { - logger.fatal('Domain master', { message: err.message, stack: err.stack }); + logger.fatal('Domain master', { + message: err.message, + stack: err.stack + }); process.exit(0); }); @@ -219,13 +229,11 @@ d.run(function () { } fs.writeFileSync('./config.json', JSON.stringify(appConfig, null, 4)); - cb(null, appConfig); } else { cb(null, appConfig); } }, - logger: function (cb) { cb(null, logger); }, @@ -248,8 +256,10 @@ d.run(function () { block: genesisblock }); }, - - public: function (cb) { + packageJson: function (cb) { + cb(null, packageJson); + }, + public: function (cb) { cb(null, path.join(__dirname, 'public')); }, @@ -257,6 +267,17 @@ d.run(function () { cb(null, new z_schema()); }, + /** + * ws client PWA, + * @method clientWs + * @param {object} wsconfig - config from ws client PWA, + * @param {nodeStyleCallback} cb - Callback function with created Method: + * `emit`. + */ + clientWs: ['config', function (scope, cb) { + var ClientWs = require('./modules/clientWs'); + cb(null, new ClientWs(scope.config.wsClient, logger)); + }], /** * Once config is completed, creates app, http & https servers & sockets with express. * @method network @@ -280,7 +301,9 @@ d.run(function () { require('./helpers/request-limiter')(app, appConfig); - app.use(compression({ level: 9 })); + app.use(compression({ + level: 9 + })); app.use(cors()); app.options('*', cors()); @@ -360,9 +383,17 @@ d.run(function () { scope.network.app.set('view engine', 'ejs'); scope.network.app.set('views', path.join(__dirname, 'public')); scope.network.app.use(scope.network.express.static(path.join(__dirname, 'public'))); - scope.network.app.use(bodyParser.raw({limit: '2mb'})); - scope.network.app.use(bodyParser.urlencoded({extended: true, limit: '2mb', parameterLimit: 5000})); - scope.network.app.use(bodyParser.json({limit: '2mb'})); + scope.network.app.use(bodyParser.raw({ + limit: '2mb' + })); + scope.network.app.use(bodyParser.urlencoded({ + extended: true, + limit: '2mb', + parameterLimit: 5000 + })); + scope.network.app.use(bodyParser.json({ + limit: '2mb' + })); scope.network.app.use(methodOverride()); var ignore = ['id', 'name', 'lastBlockId', 'blockId', 'transactionId', 'address', 'recipientId', 'senderId', 'previousBlock']; @@ -412,9 +443,9 @@ d.run(function () { ed: function (cb) { cb(null, require('./helpers/ed.js')); }, - accounts: function (cb) { - cb(null, require('./helpers/accounts.js')); - }, + accounts: function (cb) { + cb(null, require('./helpers/accounts.js')); + }, bus: ['ed', function (scope, cb) { var changeCase = require('change-case'); var bus = function () { @@ -426,12 +457,12 @@ d.run(function () { // executes the each module onBind function modules.forEach(function (module) { - if (typeof(module[eventName]) === 'function') { + if (typeof (module[eventName]) === 'function') { module[eventName].apply(module[eventName], args); } if (module.submodules) { async.each(module.submodules, function (submodule) { - if (submodule && typeof(submodule[eventName]) === 'function') { + if (submodule && typeof (submodule[eventName]) === 'function') { submodule[eventName].apply(submodule[eventName], args); } }); @@ -460,11 +491,11 @@ d.run(function () { * @param {object} scope - The results from current execution, * at leats will contain the required elements. * @param {function} cb - Callback function. - */ + */ logic: ['db', 'bus', 'schema', 'genesisblock', function (scope, cb) { var Transaction = require('./logic/transaction.js'); - var Chat = require('./logic/chat.js'); - var State = require('./logic/state.js'); + var Chat = require('./logic/chat.js'); + var State = require('./logic/state.js'); var Block = require('./logic/block.js'); var Account = require('./logic/account.js'); var Peers = require('./logic/peers.js'); @@ -490,18 +521,21 @@ d.run(function () { block: genesisblock }); }, + clientWs: function (cb) { + cb(null, scope.clientWs); + }, account: ['db', 'bus', 'ed', 'schema', 'genesisblock', 'logger', function (scope, cb) { new Account(scope.db, scope.schema, scope.logger, cb); }], - transaction: ['db', 'bus', 'ed', 'schema', 'genesisblock', 'account', 'logger', function (scope, cb) { - new Transaction(scope.db, scope.ed, scope.schema, scope.genesisblock, scope.account, scope.logger, cb); + transaction: ['db', 'bus', 'ed', 'schema', 'genesisblock', 'account', 'logger', 'clientWs', function (scope, cb) { + new Transaction(scope.db, scope.ed, scope.schema, scope.genesisblock, scope.account, scope.logger, scope.clientWs, cb); + }], + chat: ['db', 'bus', 'ed', 'schema', 'account', 'logger', function (scope, cb) { + new Chat(scope.db, scope.ed, scope.schema, scope.account, scope.logger, cb); + }], + state: ['db', 'bus', 'ed', 'schema', 'account', 'logger', function (scope, cb) { + new State(scope.db, scope.ed, scope.schema, scope.account, scope.logger, cb); }], - chat: ['db', 'bus', 'ed', 'schema', 'account', 'logger', function (scope, cb) { - new Chat(scope.db, scope.ed, scope.schema, scope.account, scope.logger, cb); - }], - state: ['db', 'bus', 'ed', 'schema', 'account', 'logger', function (scope, cb) { - new State(scope.db, scope.ed, scope.schema, scope.account, scope.logger, cb); - }], block: ['db', 'bus', 'ed', 'schema', 'genesisblock', 'account', 'transaction', function (scope, cb) { new Block(scope.ed, scope.schema, scope.transaction, cb); }], @@ -528,7 +562,10 @@ d.run(function () { var d = require('domain').create(); d.on('error', function (err) { - scope.logger.fatal('Domain ' + name, {message: err.message, stack: err.stack}); + scope.logger.fatal('Domain ' + name, { + message: err.message, + stack: err.stack + }); }); d.run(function () { @@ -552,7 +589,7 @@ d.run(function () { * @param {object} scope - The results from current execution, * at leats will contain the required elements. * @param {function} cb - Callback function. - */ + */ api: ['modules', 'logger', 'network', function (scope, cb) { Object.keys(config.api).forEach(function (moduleName) { Object.keys(config.api[moduleName]).forEach(function (protocol) { @@ -648,7 +685,7 @@ d.run(function () { process.once('cleanup', function () { scope.logger.info('Cleaning up...'); async.eachSeries(modules, function (module, cb) { - if (typeof(module.cleanup) === 'function') { + if (typeof (module.cleanup) === 'function') { module.cleanup(cb); } else { setImmediate(cb); @@ -724,10 +761,13 @@ d.run(function () { */ process.on('uncaughtException', function (err) { // Handle error safely - logger.fatal('System error', { message: err.message, stack: err.stack }); + logger.fatal('System error', { + message: err.message, + stack: err.stack + }); /** * emits cleanup once 'uncaughtException'. * @emits cleanup */ process.emit('cleanup'); -}); +}); \ No newline at end of file diff --git a/config.json b/config.json index 405fbf57..02bc7bab 100644 --- a/config.json +++ b/config.json @@ -1,8 +1,6 @@ { "port": 36666, "address": "0.0.0.0", - "version": "0.4.2", - "minVersion": ">=0.4.0", "fileLogLevel": "info", "logFileName": "logs/adamant.log", "consoleLogLevel": "none", @@ -32,7 +30,9 @@ "enabled": true, "access": { "public": false, - "whiteList": ["127.0.0.1"] + "whiteList": [ + "127.0.0.1" + ] }, "options": { "limits": { @@ -106,8 +106,12 @@ }, "dapp": { "masterrequired": true, - "masterpassword": "", + "masterpassword": "rJ1T2sZH3cLU", "autoexec": [] }, + "wsClient": { + "portWS": 36668, + "enabled": true + }, "nethash": "bd330166898377fb28743ceef5e43a5d9d0a3efd9b3451fb7bc53530bb0a6d64" -} +} \ No newline at end of file diff --git a/logic/transaction.js b/logic/transaction.js index 00e4c9a2..06267237 100644 --- a/logic/transaction.js +++ b/logic/transaction.js @@ -9,7 +9,6 @@ var exceptions = require('../helpers/exceptions.js'); var extend = require('extend'); var slots = require('../helpers/slots.js'); var sql = require('../sql/transactions.js'); - // Private fields var self, modules, __private = {}; @@ -41,7 +40,7 @@ __private.types = {}; * @return {setImmediateCallback} With `this` as data. */ // Constructor -function Transaction (db, ed, schema, genesisblock, account, logger, cb) { +function Transaction (db, ed, schema, genesisblock, account, logger, clientWs, cb) { this.scope = { db: db, ed: ed, @@ -49,6 +48,7 @@ function Transaction (db, ed, schema, genesisblock, account, logger, cb) { genesisblock: genesisblock, account: account, logger: logger, + clientWs: clientWs }; self = this; if (cb) { @@ -105,49 +105,49 @@ Transaction.prototype.create = function (data) { return trs; }; Transaction.prototype.publish = function (data) { - if (!__private.types[data.type]) { - throw 'Unknown transaction type ' + data.type; - } + if (!__private.types[data.type]) { + throw 'Unknown transaction type ' + data.type; + } - if (!data.senderId) { - throw 'Invalid sender'; - } + if (!data.senderId) { + throw 'Invalid sender'; + } - if (!data.signature) { - throw 'Invalid signature'; - } + if (!data.signature) { + throw 'Invalid signature'; + } - var trs = data; + var trs = data; - trs.id = this.getId(trs); + trs.id = this.getId(trs); - trs.fee = __private.types[trs.type].calculateFee.call(this, trs, data.senderId) || false; + trs.fee = __private.types[trs.type].calculateFee.call(this, trs, data.senderId) || false; - return trs; + return trs; }; Transaction.prototype.normalize = function (data) { - if (!__private.types[data.type]) { - throw 'Unknown transaction type ' + data.type; - } - - if (!data.sender) { - throw 'Invalid sender'; - } - - var trs = { - type: data.type, - amount: 0, - senderPublicKey: data.sender.publicKey, - requesterPublicKey: data.requester ? data.requester.publicKey.toString('hex') : null, - timestamp: slots.getTime(), - asset: {} - }; - - trs = __private.types[trs.type].create.call(this, data, trs); - - return trs; + if (!__private.types[data.type]) { + throw 'Unknown transaction type ' + data.type; + } + + if (!data.sender) { + throw 'Invalid sender'; + } + + var trs = { + type: data.type, + amount: 0, + senderPublicKey: data.sender.publicKey, + requesterPublicKey: data.requester ? data.requester.publicKey.toString('hex') : null, + timestamp: slots.getTime(), + asset: {} + }; + + trs = __private.types[trs.type].create.call(this, data, trs); + + return trs; }; /** * Sets private type based on type id after instance object validation. @@ -268,7 +268,9 @@ Transaction.prototype.getBytes = function (trs, skipSignature, skipSecondSignatu if (trs.recipientId) { var recipient = trs.recipientId.slice(1); - recipient = new bignum(recipient).toBuffer({size: 8}); + recipient = new bignum(recipient).toBuffer({ + size: 8 + }); for (i = 0; i < 8; i++) { bb.writeByte(recipient[i] || 0); @@ -335,7 +337,9 @@ Transaction.prototype.ready = function (trs, sender) { * @return {setImmediateCallback} error | row.count */ Transaction.prototype.countById = function (trs, cb) { - this.scope.db.one(sql.countById, { id: trs.id }).then(function (row) { + this.scope.db.one(sql.countById, { + id: trs.id + }).then(function (row) { return setImmediate(cb, null, row.count); }).catch(function (err) { this.scope.logger.error(err.stack); @@ -378,7 +382,7 @@ Transaction.prototype.checkBalance = function (amount, balance, trs, sender) { exceeded: exceeded, error: exceeded ? [ 'Account does not have enough ADM:', sender.address, - 'balance:', new bignum(sender[balance].toString() || '0').div(Math.pow(10,8)) + 'balance:', new bignum(sender[balance].toString() || '0').div(Math.pow(10, 8)) ].join(' ') : null }; }; @@ -581,7 +585,9 @@ Transaction.prototype.verify = function (trs, sender, requester, cb) { // Check that signatures are unique if (trs.signatures && trs.signatures.length) { var signatures = trs.signatures.reduce(function (p, c) { - if (p.indexOf(c) < 0) { p.push(c); } + if (p.indexOf(c) < 0) { + p.push(c); + } return p; }, []); @@ -614,12 +620,12 @@ Transaction.prototype.verify = function (trs, sender, requester, cb) { // Calculate fee var fee = __private.types[trs.type].calculateFee.call(this, trs, sender) || false; if (!fee || trs.fee !== fee) { - if (exceptions.fee.indexOf(trs.id) > -1) { - this.scope.logger.debug('Invalid transaction fee'); - this.scope.logger.debug(JSON.stringify(trs)); - } else { - return setImmediate(cb, 'Invalid transaction fee'); - } + if (exceptions.fee.indexOf(trs.id) > -1) { + this.scope.logger.debug('Invalid transaction fee'); + this.scope.logger.debug(JSON.stringify(trs)); + } else { + return setImmediate(cb, 'Invalid transaction fee'); + } } // Check amount @@ -669,7 +675,9 @@ Transaction.prototype.verifySignature = function (trs, publicKey, signature) { throw 'Unknown transaction type ' + trs.type; } - if (!signature) { return false; } + if (!signature) { + return false; + } var res; @@ -698,7 +706,9 @@ Transaction.prototype.verifySecondSignature = function (trs, publicKey, signatur throw 'Unknown transaction type ' + trs.type; } - if (!signature) { return false; } + if (!signature) { + return false; + } var res; @@ -771,7 +781,12 @@ Transaction.prototype.apply = function (trs, block, sender, cb) { amount = amount.toNumber(); - this.scope.logger.trace('Logic/Transaction->apply', {sender: sender.address, balance: -amount, blockId: block.id, round: modules.rounds.calc(block.height)}); + this.scope.logger.trace('Logic/Transaction->apply', { + sender: sender.address, + balance: -amount, + blockId: block.id, + round: modules.rounds.calc(block.height) + }); this.scope.account.merge(sender.address, { balance: -amount, blockId: block.id, @@ -814,9 +829,14 @@ Transaction.prototype.apply = function (trs, block, sender, cb) { */ Transaction.prototype.undo = function (trs, block, sender, cb) { var amount = new bignum(trs.amount.toString()); - amount = amount.plus(trs.fee.toString()).toNumber(); + amount = amount.plus(trs.fee.toString()).toNumber(); - this.scope.logger.trace('Logic/Transaction->undo', {sender: sender.address, balance: amount, blockId: block.id, round: modules.rounds.calc(block.height)}); + this.scope.logger.trace('Logic/Transaction->undo', { + sender: sender.address, + balance: amount, + blockId: block.id, + round: modules.rounds.calc(block.height) + }); this.scope.account.merge(sender.address, { balance: amount, blockId: block.id, @@ -858,6 +878,7 @@ Transaction.prototype.undo = function (trs, block, sender, cb) { * @return {setImmediateCallback} for errors | cb */ Transaction.prototype.applyUnconfirmed = function (trs, sender, requester, cb) { + if (typeof requester === 'function') { cb = requester; } @@ -872,14 +893,22 @@ Transaction.prototype.applyUnconfirmed = function (trs, sender, requester, cb) { amount = amount.toNumber(); - this.scope.account.merge(sender.address, {u_balance: -amount}, function (err, sender) { + if (this.scope.clientWs) { + this.scope.clientWs.emit(trs); + } + + this.scope.account.merge(sender.address, { + u_balance: -amount + }, function (err, sender) { if (err) { return setImmediate(cb, err); } __private.types[trs.type].applyUnconfirmed.call(this, trs, sender, function (err) { if (err) { - this.scope.account.merge(sender.address, {u_balance: amount}, function (err2) { + this.scope.account.merge(sender.address, { + u_balance: amount + }, function (err2) { return setImmediate(cb, err2 || err); }); } else { @@ -903,16 +932,20 @@ Transaction.prototype.applyUnconfirmed = function (trs, sender, requester, cb) { */ Transaction.prototype.undoUnconfirmed = function (trs, sender, cb) { var amount = new bignum(trs.amount.toString()); - amount = amount.plus(trs.fee.toString()).toNumber(); + amount = amount.plus(trs.fee.toString()).toNumber(); - this.scope.account.merge(sender.address, {u_balance: amount}, function (err, sender) { + this.scope.account.merge(sender.address, { + u_balance: amount + }, function (err, sender) { if (err) { return setImmediate(cb, err); } __private.types[trs.type].undoUnconfirmed.call(this, trs, sender, function (err) { if (err) { - this.scope.account.merge(sender.address, {u_balance: -amount}, function (err2) { + this.scope.account.merge(sender.address, { + u_balance: -amount + }, function (err2) { return setImmediate(cb, err2 || err); }); } else { @@ -963,27 +996,25 @@ Transaction.prototype.dbSave = function (trs) { throw e; } - var promises = [ - { - table: this.dbTable, - fields: this.dbFields, - values: { - id: trs.id, - blockId: trs.blockId, - type: trs.type, - timestamp: trs.timestamp, - senderPublicKey: senderPublicKey, - requesterPublicKey: requesterPublicKey, - senderId: trs.senderId, - recipientId: trs.recipientId || null, - amount: trs.amount, - fee: trs.fee, - signature: signature, - signSignature: signSignature, - signatures: trs.signatures ? trs.signatures.join(',') : null, - } + var promises = [{ + table: this.dbTable, + fields: this.dbFields, + values: { + id: trs.id, + blockId: trs.blockId, + type: trs.type, + timestamp: trs.timestamp, + senderPublicKey: senderPublicKey, + requesterPublicKey: requesterPublicKey, + senderId: trs.senderId, + recipientId: trs.recipientId || null, + amount: trs.amount, + fee: trs.fee, + signature: signature, + signSignature: signSignature, + signatures: trs.signatures ? trs.signatures.join(',') : null, } - ]; + }]; var promise = __private.types[trs.type].dbSave(trs); @@ -1203,4 +1234,4 @@ Transaction.prototype.bindModules = function (__modules) { }; // Export -module.exports = Transaction; +module.exports = Transaction; \ No newline at end of file diff --git a/modules/chatrooms.js b/modules/chatrooms.js new file mode 100644 index 00000000..4ec096fb --- /dev/null +++ b/modules/chatrooms.js @@ -0,0 +1,339 @@ +'use strict'; + +const _ = require('lodash'); +const async = require('async'); +const Chat = require('../logic/chat.js'); +const sql = require('../sql/chats.js'); +const transactionTypes = require('../helpers/transactionTypes.js'); +const schema = require('../schema/chatrooms.js'); +const Transfer = require('../logic/transfer.js'); +const OrderBy = require('../helpers/orderBy.js'); + +// Private fields +let modules, library, self, __private = {}, shared = {}; + +__private.assetTypes = {}; + +/** + * Initializes library with scope content and generates instances for: + * - Chatrooms + * Calls logic.transaction.attachAssetType(). + * + * Listens `exit` signal. + * Checks 'public/chat' folder and created it if doesn't exists. + * @memberof module:chatrooms + * @class + * @classdesc Main chatrooms methods. + * @param {function} cb - Callback function. + * @param {scope} scope - App instance. + * @return {setImmediateCallback} Callback function with `self` as data. + */ +// Constructor +function Chatrooms (cb, scope) { + library = { + logger: scope.logger, + db: scope.db, + public: scope.public, + network: scope.network, + schema: scope.schema, + ed: scope.ed, + balancesSequence: scope.balancesSequence, + logic: { + transaction: scope.logic.transaction, + chat: scope.logic.chat + } + }; + self = this; + + __private.assetTypes[transactionTypes.CHAT_MESSAGE] = library.logic.transaction.attachAssetType( + transactionTypes.CHAT_MESSAGE, + new Chat( + scope.db, + scope.logger, + scope.schema, + scope.network + ) + ); + __private.assetTypes[transactionTypes.SEND] = library.logic.transaction.attachAssetType( + transactionTypes.SEND, new Transfer() + ); + setImmediate(cb, null, self); +} + +__private.listChats = function (filter, cb) { + let params = {}, where = [], whereOr = []; + + if (filter.type >= 0) { + where.push('"c_type" = ${type}'); + params.type = filter.type; + } + if (filter.withPayments) { + where.push(`("t_type" = ${transactionTypes.CHAT_MESSAGE} OR "t_type" = ${transactionTypes.SEND})`); + } else { + where.push('"t_type" = '+ transactionTypes.CHAT_MESSAGE); + } + + if (filter.senderId) { + where.push('"t_senderId" = ${name}'); + params.name = filter.senderId; + } + + if (filter.recipientId) { + where.push('"t_recipientId" = ${name}'); + params.name = filter.recipientId; + } + + if (filter.userId) { + whereOr.push('"t_senderId" = ${name}'); + whereOr.push('"t_recipientId" = ${name}'); + params.name = filter.userId; + } + + if (!filter.limit) { + params.limit = 100; + } else { + params.limit = Math.abs(filter.limit); + } + + if (!filter.offset) { + params.offset = 0; + } else { + params.offset = Math.abs(filter.offset); + } + + if (params.limit > 100) { + return setImmediate(cb, 'Invalid limit. Maximum is 100'); + } + + const orderBy = OrderBy( + filter.orderBy, { + sortFields: sql.sortFields + } + ); + + if (orderBy.error) { + return setImmediate(cb, orderBy.error); + } + library.db.query(sql.countChats({ + where: where, + whereOr: whereOr + }), params).then(function (rows) { + const count = rows.length ? rows[0].count : 0; + library.db.query(sql.listChats({ + where: where, + whereOr: whereOr, + sortField: orderBy.sortField, + sortMethod: orderBy.sortMethod + }), params).then(function (rows) { + let transactions = [], chats = {}; + for (let i = 0; i < rows.length; i++) { + const trs = library.logic.transaction.dbRead(rows[i]); + trs.participants = [ + {address: trs.senderId, publicKey: trs.senderPublicKey}, + {address: trs.recipientId, publicKey: trs.recipientPublicKey} + ]; + const uid = trs.senderId !== filter.userId ? trs.senderId : trs.recipientId; + if (!chats[uid]) { + chats[uid] = []; + } + chats[uid].push(trs); + } + for (const uid in chats) { + transactions.push(chats[uid].sort((x, y) => x.timestamp - y.timestamp)[0]); + } + const data = { + chats: transactions, + count: count + }; + return setImmediate(cb, null, data); + }).catch(function (err) { + library.logger.error(err.stack); + return setImmediate(cb, err); + }); + }).catch(function (err) { + library.logger.error(err.stack); + return setImmediate(cb, err); + }); +}; + +__private.listMessages = function (filter, cb) { + let params = {}, where = [], whereOr = []; + + if (filter.type >= 0) { + where.push('"c_type" = ${type}'); + params.type = filter.type; + } + if (filter.withPayments) { + where.push(`("t_type" = ${transactionTypes.CHAT_MESSAGE} OR "t_type" = ${transactionTypes.SEND})`); + } else { + where.push('"t_type" = '+ transactionTypes.CHAT_MESSAGE); + } + + if (filter.senderId) { + where.push('"t_senderId" = ${name}'); + params.name = filter.senderId; + } + + if (filter.recipientId) { + where.push('"t_recipientId" = ${name}'); + params.name = filter.recipientId; + } + + if (filter.companionId && filter.userId) { + whereOr.push('("t_senderId" = ${pname} AND "t_recipientId" = ${name})'); + whereOr.push('("t_recipientId" = ${pname} AND "t_senderId" = ${name})'); + params.pname = filter.companionId; + params.name = filter.userId; + } + + if (!filter.limit) { + params.limit = 100; + } else { + params.limit = Math.abs(filter.limit); + } + + if (!filter.offset) { + params.offset = 0; + } else { + params.offset = Math.abs(filter.offset); + } + + if (params.limit > 100) { + return setImmediate(cb, 'Invalid limit. Maximum is 100'); + } + + const orderBy = OrderBy( + filter.orderBy, { + sortFields: sql.sortFields + } + ); + + if (orderBy.error) { + return setImmediate(cb, orderBy.error); + } + library.db.query(sql.countList({ + where: where, + whereOr: whereOr + }), params).then(function (rows) { + const count = rows.length ? rows[0].count : 0; + library.db.query(sql.listMessages({ + where: where, + whereOr: whereOr, + sortField: orderBy.sortField, + sortMethod: orderBy.sortMethod + }), params).then(function (rows) { + let transactions = []; + for (let i = 0; i < rows.length; i++) { + const trs = library.logic.transaction.dbRead(rows[i]); + transactions.push(trs); + } + const data = { + messages: transactions, + participants: transactions.length ? [ + {address: transactions[0].senderId,publicKey: transactions[0].senderPublicKey}, + {address: transactions[0].recipientId,publicKey: transactions[0].recipientPublicKey} + ] : [], + count: count + }; + return setImmediate(cb, null, data); + }).catch(function (err) { + library.logger.error(err.stack); + return setImmediate(cb, err); + }); + }).catch(function (err) { + library.logger.error(err.stack); + return setImmediate(cb, err); + }); +}; + +Chatrooms.prototype.onBind = function (scope) { + modules = { + transactions: scope.transactions, + accounts: scope.accounts, + peers: scope.peers, + sql: scope.sql, + }; + __private.assetTypes[transactionTypes.CHAT_MESSAGE].bind( + scope.accounts, + scope.rounds + ); +}; + +/** + * Checks if `modules` is loaded. + * @return {boolean} True if `modules` is loaded. + */ +Chatrooms.prototype.isLoaded = function () { + return !!modules; +}; + +Chatrooms.prototype.internal = { + getChats: function (req, cb) { + let validRequest; + [validRequest, req.body.userId,req.body.companionId] = req.path.match(/(U[0-9]+)\/?(U[0-9]+)?/); + if (!validRequest) { return setImmediate(cb, 'Invalid Request path'); } + async.waterfall([ + function (waterCb) { + const params = req.body; + + library.schema.validate(params, schema.getChats, function (err) { + if (err) { + return setImmediate(waterCb, err[0].message); + } else { + return setImmediate(waterCb, null); + } + }); + }, + function (waterCb) { + __private.listChats(req.body, function (err, data) { + if (err) { + return setImmediate(waterCb, 'Failed to get transactions: ' + err); + } else { + return setImmediate(waterCb, null, { + chats: _.uniqBy(data.chats, (x) => x.id), + count: data.count + }); + } + }); + } + ], function (err, res) { + return setImmediate(cb, err, res); + }); + }, + getMessages: function (req, cb) { + let validRequest; + [validRequest, req.body.userId,req.body.companionId] = req.path.match(/(U[0-9]+)\/?(U[0-9]+)?/); + if (!validRequest) { return setImmediate(cb, 'Invalid Request path'); } + async.waterfall([ + function (waterCb) { + const params = req.body; + library.schema.validate(params, schema.getChats, function (err) { + if (err) { + return setImmediate(waterCb, err[0].message); + } else { + return setImmediate(waterCb, null); + } + }); + }, + function (waterCb) { + __private.listMessages(req.body, function (err, data) { + if (err) { + return setImmediate(waterCb, 'Failed to get transactions: ' + err); + } else { + return setImmediate(waterCb, null, { + messages: data.messages, + participants: data.participants, + count: data.count + }); + } + }); + } + ], function (err, res) { + return setImmediate(cb, err, res); + }); + } +}; + +Chatrooms.prototype.shared = {}; + +module.exports = Chatrooms; \ No newline at end of file diff --git a/modules/clientWs.js b/modules/clientWs.js new file mode 100644 index 00000000..23b796a1 --- /dev/null +++ b/modules/clientWs.js @@ -0,0 +1,72 @@ +class ClientWs { + constructor (config, logger, cb) { + if (!config || !config.enabled) { + return false; + } + const port = config.portWS; + const io = require('socket.io')(port); + this.describes = {}; + this.logger = logger; + io.sockets.on('connection', (socket) => { + try { + let address = ''; + let aId = ''; + socket.on('address', a => { + address = a; + aId = address + '_' + socket.id; + this.describes[aId] = socket; + }); + socket.on('disconnect', () => { + delete this.describes[aId]; + }); + } catch (e) { + logger.debug('Error Connection socket: ' + e); + } + }); + + if (cb) { + return setImmediate(cb, null, this); + } + } + + emit (t) { + if (lastTransactionsIds[t.id]) { + return; + } + lastTransactionsIds[t.id] = getUTime(); + try { + const subs = findSubs(t.recipientId, t.senderId, this.describes); + subs.forEach(s => { + s.emit('newTrans', t); + }); + } catch (e) { + this.logger.debug('Socket error emit ' + e); + } + } +} + +const lastTransactionsIds = {}; + +setInterval(() => { + for (let id in lastTransactionsIds) { + if (getUTime() - lastTransactionsIds[id] >= 60) { + delete lastTransactionsIds[id]; + } + } +}, 60 * 1000); + +function getUTime () { + return new Date().getTime() / 1000; +} + +function findSubs (address1, address2, subs) { + const filterred = []; + for (let aId in subs) { + if (aId.startsWith(address1) || aId.startsWith(address2)) { + filterred.push(subs[aId]); + } + } + return filterred; +} + +module.exports = ClientWs; \ No newline at end of file diff --git a/modules/node.js b/modules/node.js new file mode 100644 index 00000000..98cbf573 --- /dev/null +++ b/modules/node.js @@ -0,0 +1,140 @@ +'use strict'; + +var _ = require('lodash'); +var async = require('async'); +var constants = require('../helpers/constants.js'); +var jobsQueue = require('../helpers/jobsQueue.js'); +var extend = require('extend'); +var pgp = require('pg-promise')(); // We also initialize library here +var schema = require('../schema/node.js'); +var BlockReward = require('../logic/blockReward.js'); +var util = require('util'); + +// Private fields +var modules, library, self, __private = {}, shared = {}; + +__private.blockReward = new BlockReward(); + +/** + * Initializes library with scope content. + * @memberof module:node + * @class + * @classdesc Main node methods. + * @param {function} cb - Callback function. + * @param {scope} scope - App instance. + * @return {setImmediateCallback} Callback function with `self` as data. + */ +// Constructor +function Node (cb, scope) { + library = { + logger: scope.logger, + db: scope.db, + schema: scope.schema, + bus: scope.bus, + nonce: scope.nonce, + build: scope.build, + logic: scope.logic, + lastCommit: scope.lastCommit, + config: { + peers: scope.config.peers, + version: scope.packageJson.version, + wsClient: scope.config.wsClient + }, + }; + self = this; + + setImmediate(cb, null, self); +} + +// Private methods + + +// Public methods + +// Events +/** + * assigns scope to modules variable + * @param {modules} scope + */ +Node.prototype.onBind = function (scope) { + modules = { + blocks: scope.blocks, + transport: scope.transport, + system: scope.system + }; +}; + +/** + * Triggers onPeersReady after: + * - Ping to every member of peers list. + * - Load peers from database and checks every peer state and updated time. + * - Discover peers by getting list and validates them. + */ +Node.prototype.onBlockchainReady = function () { +}; + + +/** + * Checks if `modules` is loaded. + * @return {boolean} True if `modules` is loaded. + */ +Node.prototype.isLoaded = function () { + return !!modules; +}; + +// Shared API +/** + * @todo implement API comments with apidoc. + * @see {@link http://apidocjs.com/} + */ +Node.prototype.shared = { + + /* + * Returns information about node status + * + * @public + * @async + * @method status + * @param {Object} req HTTP request object + * @param {Function} cb Callback function + * @return {Function} cb Callback function from params (through setImmediate) + * @return {Object} cb.err Always return `null` here + * @return {Object} cb.obj Anonymous object with version info + * @return {String} cb.obj.build Build information (if available, otherwise '') + * @return {String} cb.obj.commit Hash of last git commit (if available, otherwise '') + * @return {String} cb.obj.version Lisk version from config file + */ + getStatus: function (req, cb) { + var lastBlock = modules.blocks.lastBlock.get(); + var wsClientEnabled = false; + if (library.config.wsClient) { + if (library.config.wsClient.enabled) { + wsClientEnabled = true; + } + } + return setImmediate(cb, null, + { + wsClient: { + enabled: wsClientEnabled + }, + network: { + broadhash: modules.system.getBroadhash(), + epoch: constants.epochTime, + height: lastBlock.height, + fee: library.logic.block.calculateFee(), + milestone: __private.blockReward.calcMilestone(lastBlock.height), + nethash: modules.system.getNethash(), + reward: __private.blockReward.calcReward(lastBlock.height), + supply: __private.blockReward.calcSupply(lastBlock.height) + }, + version: { + build: library.build, + commit: library.lastCommit, + version: library.config.version + } + }); + } +}; + +// Export +module.exports = Node; diff --git a/modules/peers.js b/modules/peers.js index 67a79402..a64b3ce5 100644 --- a/modules/peers.js +++ b/modules/peers.js @@ -42,7 +42,7 @@ function Peers (cb, scope) { }, config: { peers: scope.config.peers, - version: scope.config.version, + version: scope.packageJson.version, }, }; self = this; diff --git a/modules/system.js b/modules/system.js index 03fea001..6782e809 100644 --- a/modules/system.js +++ b/modules/system.js @@ -35,10 +35,10 @@ function System (cb, scope) { db: scope.db, nonce: scope.nonce, config: { - version: scope.config.version, + version: scope.packageJson.version, port: scope.config.port, nethash: scope.config.nethash, - minVersion: scope.config.minVersion, + minVersion: scope.packageJson.config.minVersion, }, }; self = this; diff --git a/modules/transactions.js b/modules/transactions.js index 57d517f5..3b33dae8 100644 --- a/modules/transactions.js +++ b/modules/transactions.js @@ -92,7 +92,11 @@ __private.list = function (filter, cb) { recipientPublicKeys: 'ENCODE ("m_recipientPublicKey", \'hex\') IN (${recipientPublicKeys:csv})', minAmount: '"t_amount" >= ${minAmount}', maxAmount: '"t_amount" <= ${maxAmount}', + minFee: '"t_fee" >= ${minFee}', + maxFee: '"t_fee" <= ${maxFee}', type: '"t_type" = ${type}', + types: '"t_type" IN (${types:csv})', + noClutter: '("t_amount" > 0 OR "t_type" < 8)', minConfirmations: 'confirmations >= ${minConfirmations}', limit: null, offset: null, @@ -133,7 +137,7 @@ __private.list = function (filter, cb) { } // Checking for empty parameters, 0 is allowed for few - if (!value && !(value === 0 && _.includes(['fromTimestamp', 'minAmount', 'minConfirmations', 'type', 'offset'], field[1]))) { + if (!value && !(value === 0 && _.includes(['fromTimestamp', 'minAmount', 'minConfirmations', 'type', 'offset', 'minFee'], field[1]))) { throw new Error('Value for parameter [' + field[1] + '] cannot be empty'); } @@ -624,13 +628,17 @@ Transactions.prototype.shared = { _.each(req.body, function (value, key) { var param = String(key).replace(pattern, ''); // Dealing with array-like parameters (csv comma separated) - if (_.includes(['senderIds', 'recipientIds', 'senderPublicKeys', 'recipientPublicKeys'], param)) { + if (_.includes(['senderIds', 'recipientIds', 'senderPublicKeys', 'recipientPublicKeys', 'types'], param)) { value = String(value).split(','); req.body[key] = value; } params[param] = value; }); + if(params.types) { + params.types = params.types.map(x => parseInt(x)); + } + library.schema.validate(params, schema.getTransactions, function (err) { if (err) { return setImmediate(waterCb, err[0].message); diff --git a/package.json b/package.json index 7fde4567..264e66e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adamant", - "version": "0.4.2", + "version": "0.5.0", "private": true, "scripts": { "start": "node app.js", @@ -48,7 +48,7 @@ "redis": "=2.7.1", "rimraf": "=2.5.4", "semver": "=5.3.0", - "socket.io": "=1.7.2", + "socket.io": "^1.7.2", "sodium": "^3.0.2", "sodium-browserify-tweetnacl": "*", "strftime": "=0.10.0", @@ -79,5 +79,8 @@ "moment": "=2.19.3", "sinon": "=1.17.7", "supertest": "=3.0.0" + }, + "config": { + "minVersion": ">=0.4.0" } } diff --git a/schema/chatrooms.js b/schema/chatrooms.js new file mode 100644 index 00000000..19679f33 --- /dev/null +++ b/schema/chatrooms.js @@ -0,0 +1,175 @@ +'use strict'; + +const constants = require('../helpers/constants.js'); + +module.exports = { + getChats: { + id: 'chats.getTransactions', + type: 'object', + properties: { + blockId: { + type: 'string', + format: 'id', + minLength: 1, + maxLength: 20 + }, + type: { + type: 'integer', + minimum: 0, + maximum: 10 + }, + senderId: { + type: 'string', + format: 'address', + minLength: 1, + maxLength: 22 + }, + senderPublicKey: { + type: 'string', + format: 'publicKey' + }, + ownerPublicKey: { + type: 'string', + format: 'publicKey' + }, + ownerAddress: { + type: 'string', + format: 'address', + minLength: 1, + maxLength: 22 + }, + recipientId: { + type: 'string', + format: 'address', + minLength: 1, + maxLength: 22 + }, + amount: { + type: 'integer', + minimum: 0, + maximum: constants.fixedPoint + }, + fee: { + type: 'integer', + minimum: 0, + maximum: constants.fixedPoint + }, + senderPublicKeys: { + type: 'array', + minItems: 1, + 'items': { + type: 'string', + format: 'publicKey' + } + }, + recipientPublicKeys: { + type: 'array', + minItems: 1, + 'items': { + type: 'string', + format: 'publicKey' + } + }, + senderIds: { + type: 'array', + minItems: 1, + 'items': { + type: 'string', + format: 'address', + minLength: 1, + maxLength: 22 + } + }, + recipientIds: { + type: 'array', + minItems: 1, + 'items': { + type: 'string', + format: 'address', + minLength: 1, + maxLength: 22 + } + }, + fromHeight: { + type: 'integer', + minimum: 1 + }, + toHeight: { + type: 'integer', + minimum: 1 + }, + fromTimestamp: { + type: 'integer', + minimum: 0 + }, + toTimestamp: { + type: 'integer', + minimum: 1 + }, + fromUnixTime: { + type: 'integer', + minimum: (constants.epochTime.getTime() / 1000) + }, + toUnixTime: { + type: 'integer', + minimum: (constants.epochTime.getTime() / 1000 + 1) + }, + minAmount: { + type: 'integer', + minimum: 0 + }, + maxAmount: { + type: 'integer', + minimum: 1 + }, + minConfirmations: { + type: 'integer', + minimum: 0 + }, + orderBy: { + type: 'string' + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 1000 + }, + offset: { + type: 'integer', + minimum: 0 + } + } + }, + normalize: { + id: 'chats.normalize', + type: 'object', + properties: { + message: { + type: 'string', + minLength: 1 + }, + recipientId: { + type: 'string', + format: 'address', + minLength: 1, + maxLength: 40 + }, + publicKey: { + type: 'string', + format: 'publicKey' + } + }, + required: ['message', 'recipientId', 'publicKey'] + }, + process: { + id: 'chats.process', + type: 'object', + properties: { + signature: { + type: 'string', + format: 'signature' + } + }, + required: ['signature'] + }, +}; \ No newline at end of file diff --git a/schema/config.js b/schema/config.js index 9fa0ad36..146728e2 100644 --- a/schema/config.js +++ b/schema/config.js @@ -14,15 +14,6 @@ module.exports = { type: 'string', format: 'ip' }, - version: { - type: 'string', - format: 'version', - minLength: 5, - maxLength: 12 - }, - minVersion: { - type: 'string' - }, fileLogLevel: { type: 'string' }, @@ -314,6 +305,6 @@ module.exports = { format: 'hex' } }, - required: ['port', 'address', 'version', 'minVersion', 'fileLogLevel', 'logFileName', 'consoleLogLevel', 'trustProxy', 'topAccounts', 'db', 'api', 'peers', 'broadcasts', 'transactions', 'forging', 'loading', 'ssl', 'dapp', 'nethash', 'cacheEnabled', 'redis'] + required: ['port', 'address', 'fileLogLevel', 'logFileName', 'consoleLogLevel', 'trustProxy', 'topAccounts', 'db', 'api', 'peers', 'broadcasts', 'transactions', 'forging', 'loading', 'ssl', 'dapp', 'nethash', 'cacheEnabled', 'redis'] } }; diff --git a/schema/node.js b/schema/node.js new file mode 100644 index 00000000..0e903a8b --- /dev/null +++ b/schema/node.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + +}; diff --git a/schema/transactions.js b/schema/transactions.js index 222a37f6..9226eeac 100644 --- a/schema/transactions.js +++ b/schema/transactions.js @@ -128,6 +128,28 @@ module.exports = { type: 'integer', minimum: 1 }, + minFee: { + type: 'integer', + minimum: 1 + }, + maxFee: { + type: 'integer', + minimum: 1 + }, + types: { + type: 'array', + minItems: 1, + 'items': { + type: 'integer', + minimum: 0, + maximum: 10 + } + }, + noClutter: { + type: 'integer', + minimum: 0, + maximum: 1 + }, minConfirmations: { type: 'integer', minimum: 0 diff --git a/sql/chats.js b/sql/chats.js index 122b66ed..01700b5a 100644 --- a/sql/chats.js +++ b/sql/chats.js @@ -23,19 +23,103 @@ var ChatsSql = { return [ 'SELECT COUNT(1) FROM full_blocks_list', (params.where.length ? 'WHERE ' + params.where.join(' AND ') : ''), + ((params.whereOr && params.whereOr.length) ? 'AND (' + params.whereOr.join(' OR ') + ')': ''), (params.sortField ? 'ORDER BY ' + [params.sortField, params.sortMethod].join(' ') : '') ].filter(Boolean).join(' '); }, - // Need to fix "or" or "and" in query - list: function (params) { - return [ - 'SELECT *, t_timestamp as timestamp FROM full_blocks_list', - (params.where.length ? 'WHERE ' + params.where.join(' AND ') : ''), - (params.sortField ? 'ORDER BY ' + [params.sortField, params.sortMethod].join(' ') : ''), - 'LIMIT ${limit} OFFSET ${offset}' - ].filter(Boolean).join(' '); - }, + countChats: function (params) { + let y = [ + 'SELECT COUNT(1) FROM', + '(SELECT', + 'CONCAT(LEAST("t_senderId", "t_recipientId"), GREATEST("t_senderId", "t_recipientId")) as "srt",', + 'first("t_id") as "t_id",', + 'first("t_senderPublicKey") as "t_senderPublicKey",', + 'first("m_recipientPublicKey") as "m_recipientPublicKey",', + 'first("t_senderId") as "t_senderId",', + 'first("t_recipientId") as "t_recipientId",', + 'first("t_timestamp") as "timestamp",', + 'first("t_type") as "t_type"', + 'FROM ( SELECT *, t_timestamp as timestamp, ENCODE("publicKey", \'hex\') as "m_recipientPublicKey"', + 'FROM full_blocks_list', + 'LEFT OUTER JOIN mem_accounts ON address = "t_recipientId"', + + (params.where.length ? 'WHERE ' + params.where.join(' AND ') : ''), + (params.whereOr.length ? 'AND (' + params.whereOr.join(' OR ') + ')': ''), + ') as foo GROUP by srt', + (params.sortField ? 'ORDER BY ' + [params.sortField, params.sortMethod].join(' ') : ''), + ') as bar' + // + // + // 'WHERE "t_type" = 8', + // 'AND ("t_senderId" = \'U1283640763437948723\'', + // 'OR "t_recipientId" = \'U1020291227689695733\')', + // 'ORDER BY "t_timestamp" DESC) as foo GROUP by srt' + ].filter(Boolean).join(' '); + return y; + // return [ + // 'SELECT COUNT(DISTINCT "t_recipientId") FROM full_blocks_list', + // (params.where.length ? 'WHERE ' + params.where.join(' AND ') : ''), + // (params.whereOr.length ? 'AND (' + params.whereOr.join(' OR ') + ')': ''), + // (params.sortField ? 'ORDER BY ' + [params.sortField, params.sortMethod].join(' ') : '') + // ].filter(Boolean).join(' '); + }, + list: function (params) { + return [ + + 'SELECT *, t_timestamp as timestamp FROM full_blocks_list', + (params.where.length ? 'WHERE ' + params.where.join(' AND ') : ''), + (params.sortField ? 'ORDER BY ' + [params.sortField, params.sortMethod].join(' ') : ''), + 'LIMIT ${limit} OFFSET ${offset}' + ].filter(Boolean).join(' '); + }, + listMessages: function (params) { + let x = [ + 'SELECT *, t_timestamp as timestamp, ENCODE("publicKey", \'hex\') as "m_recipientPublicKey" FROM full_blocks_list', + 'LEFT OUTER JOIN mem_accounts ON address = "t_recipientId"', + (params.where.length ? 'WHERE ' + params.where.join(' AND ') : ''), + (params.whereOr.length ? 'AND (' + params.whereOr.join(' OR ') + ')': ''), + (params.sortField ? 'ORDER BY ' + [params.sortField, params.sortMethod].join(' ') : ''), + 'LIMIT ${limit} OFFSET ${offset}' + ].filter(Boolean).join(' '); + return x; + }, + listChats: function (params) { + + let y = [ + 'SELECT', + 'CONCAT(LEAST("t_senderId", "t_recipientId"), GREATEST("t_senderId", "t_recipientId")) as "srt",', + 'first("t_id") as "t_id",', + 'first("t_senderPublicKey") as "t_senderPublicKey",', + 'first("m_recipientPublicKey") as "m_recipientPublicKey",', + 'first("t_senderId") as "t_senderId",', + 'first("t_recipientId") as "t_recipientId",', + 'first("t_timestamp") as "t_timestamp",', + 'first("t_timestamp") as "timestamp",', + 'first("t_amount") as "t_amount",', + 'first("t_fee") as "t_fee",', + 'first("c_message") as "c_message",', + 'first("c_own_message") as "c_own_message",', + 'first("c_type") as "c_type",', + 'first("t_type") as "t_type"', + 'FROM ( SELECT *, t_timestamp as timestamp, ENCODE("publicKey", \'hex\') as "m_recipientPublicKey"', + 'FROM full_blocks_list', + 'LEFT OUTER JOIN mem_accounts ON address = "t_recipientId"', + + (params.where.length ? 'WHERE ' + params.where.join(' AND ') : ''), + (params.whereOr.length ? 'AND (' + params.whereOr.join(' OR ') + ')': ''), + ') as foo GROUP by srt', + (params.sortField ? 'ORDER BY ' + [params.sortField, params.sortMethod].join(' ') : ''), + 'LIMIT ${limit} OFFSET ${offset}' + // + // + // 'WHERE "t_type" = 8', + // 'AND ("t_senderId" = \'U1283640763437948723\'', + // 'OR "t_recipientId" = \'U1020291227689695733\')', + // 'ORDER BY "t_timestamp" DESC) as foo GROUP by srt' + ].filter(Boolean).join(' '); + return y; + }, getGenesis: 'SELECT b."height" AS "height", b."id" AS "id", t."senderId" AS "authorId" FROM trs t INNER JOIN blocks b ON t."blockId" = b."id" WHERE t."id" = ${id}' diff --git a/sql/migrations/20190114165600_createLastFirstFunctions.sql b/sql/migrations/20190114165600_createLastFirstFunctions.sql new file mode 100644 index 00000000..37631d92 --- /dev/null +++ b/sql/migrations/20190114165600_createLastFirstFunctions.sql @@ -0,0 +1,25 @@ +-- Create a function that always returns the first non-NULL item +CREATE OR REPLACE FUNCTION public.first_agg ( anyelement, anyelement ) +RETURNS anyelement LANGUAGE SQL IMMUTABLE STRICT AS $$ + SELECT $1; +$$; + +-- And then wrap an aggregate around it +CREATE AGGREGATE public.FIRST ( + sfunc = public.first_agg, + basetype = anyelement, + stype = anyelement +); + +-- Create a function that always returns the last non-NULL item +CREATE OR REPLACE FUNCTION public.last_agg ( anyelement, anyelement ) +RETURNS anyelement LANGUAGE SQL IMMUTABLE STRICT AS $$ + SELECT $2; +$$; + +-- And then wrap an aggregate around it +CREATE AGGREGATE public.LAST ( + sfunc = public.last_agg, + basetype = anyelement, + stype = anyelement +); \ No newline at end of file diff --git a/test/api/accounts.js b/test/api/accounts.js index de40a279..a2f5ce7d 100644 --- a/test/api/accounts.js +++ b/test/api/accounts.js @@ -105,6 +105,7 @@ describe('GET /api/accounts/getBalance?address=', function () { node.expect(res.body).to.have.property('success').to.be.ok; node.expect(res.body).to.have.property('balance').that.is.a('string'); node.expect(res.body).to.have.property('unconfirmedBalance').that.is.a('string'); + node.expect(res.body.balance).to.equal(res.body.unconfirmedBalance); done(); }); }); @@ -114,6 +115,7 @@ describe('GET /api/accounts/getBalance?address=', function () { node.expect(res.body).to.have.property('success').to.be.ok; node.expect(res.body).to.have.property('balance').that.is.a('string'); node.expect(res.body).to.have.property('unconfirmedBalance').that.is.a('string'); + node.expect(res.body.balance).to.equal(res.body.unconfirmedBalance); done(); }); }); diff --git a/test/api/chatrooms.js b/test/api/chatrooms.js new file mode 100644 index 00000000..8b0a9bf1 --- /dev/null +++ b/test/api/chatrooms.js @@ -0,0 +1,473 @@ +'use strict'; + +const node = require('./../node.js'); +const Mnemonic = require('bitcore-mnemonic'); +const _ = require('lodash'); + +function sendADM (params, done) { + node.put('/api/transactions/', params, function (err, res) { + done(err, res); + }); +} + +function postMessage (transaction, done) { + node.post('/api/transactions', { transaction: transaction }, done); +} + +function getChats (senderId, done, params) { + const args = _.keys(params).map((key) => `${key}=${params[key]}`); + node.get( `/api/chatrooms/${senderId}${args.length > 0 ? '?'+args.join('&') : ''}`, done); +} + +function getMessages (authorId, companionId, done, params) { + const args = _.keys(params).map((key) => `${key}=${params[key]}`); + node.get( `/api/chatrooms/${authorId}/${companionId}${args.length > 0 ? '?'+args.join('&') : ''}`, done); +} + +describe('GET /api/chatrooms/:ID/:ID', function () { + const sender = node.randomAccount(); + const recipient1 = node.randomAccount(); + const recipient2 = node.randomAccount(); + + // send ADM to message sender + before(function (done) { + sendADM({ + secret: node.gAccount.password, + amount: node.fees.messageFee*3+node.fees.transactionFee*2, + recipientId: sender.address + }, function () { + done(); + }); + }); + + // send ADM to recipient1 + before(function (done) { + sendADM({ + secret: node.gAccount.password, + amount: node.fees.messageFee, + recipientId: recipient1.address + }, function () { + done(); + }); + }); + + // send ADM to recipient2 + before(function (done) { + sendADM({ + secret: node.gAccount.password, + amount: node.fees.messageFee, + recipientId: recipient2.address + }, function () { + done(); + }); + }); + + before(function (done) { + node.onNewBlock(function () { + done(); + }); + }); + + // send a message from a sender to recipient1 + before(function (done) { + const transaction = node.createChatTransaction({ + keyPair: sender.keypair, + recipientId: recipient1.address, + message: new Mnemonic(Mnemonic.Words.ENGLISH).toString(), + own_message: '', + type: 1 + }); + postMessage(transaction, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('transactionId').that.is.not.empty; + done(); + }); + }); + + // send a message from a recipient1 to recipient2 + before(function (done) { + const transaction = node.createChatTransaction({ + keyPair: recipient1.keypair, + recipientId: recipient2.address, + message: new Mnemonic(Mnemonic.Words.ENGLISH).toString(), + own_message: '', + type: 1 + }); + postMessage(transaction, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('transactionId').that.is.not.empty; + done(); + }); + }); + + // send a message from a recipient2 to recipient1 + before(function (done) { + const transaction = node.createChatTransaction({ + keyPair: recipient2.keypair, + recipientId: recipient1.address, + message: new Mnemonic(Mnemonic.Words.ENGLISH).toString(), + own_message: '', + type: 1 + }); + postMessage(transaction, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('transactionId').that.is.not.empty; + done(); + }); + }); + + before(function (done) { + node.onNewBlock(function () { + done(); + }); + }); + + // send a message from a sender to recipient2 + before(function (done) { + const transaction = node.createChatTransaction({ + keyPair: sender.keypair, + recipientId: recipient2.address, + message: new Mnemonic(Mnemonic.Words.ENGLISH).toString(), + own_message: '', + type: 1 + }); + postMessage(transaction, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('transactionId').that.is.not.empty; + done(); + }); + }); + + before(function (done) { + node.onNewBlock(function () { + done(); + }); + }); + + // send a second message from a sender to recipient1 + before(function (done) { + const transaction = node.createChatTransaction({ + keyPair: sender.keypair, + recipientId: recipient1.address, + message: new Mnemonic(Mnemonic.Words.ENGLISH).toString(), + own_message: '', + type: 1 + }); + postMessage(transaction, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('transactionId').that.is.not.empty; + done(); + }); + }); + + // send ADM to message sender + before(function (done) { + sendADM({ + secret: sender.password, + amount: node.fees.transactionFee, + recipientId: recipient1.address + }, function () { + done(); + }); + }); + + before(function (done) { + node.onNewBlock(function () { + done(); + }); + }); + + it('should return the chats list for a valid transaction', function (done) { + getChats(sender.address, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('count').to.equal('2'); + node.expect(res.body).to.have.property('chats').to.have.lengthOf(2); + for (let i = 0; i < res.body.chats.length; i++) { + node.expect(res.body.chats[i]).to.have.property('participants').to.have.lengthOf(2); + node.expect(res.body.chats[i].participants[0].address).to.equal(sender.address); + node.expect(res.body.chats[i].participants[0].publicKey).to.equal(sender.publicKey.toString('hex')); + node.expect(res.body.chats[i].participants[1].publicKey).to.not.equal(null); + node.expect(res.body.chats[i].timestamp).to.not.equal(null); + node.expect(res.body.chats[i].fee).to.not.equal(null); + node.expect(res.body.chats[i].amount).to.not.equal(null); + node.expect(res.body.chats[i]).to.have.property('asset').to.be.an('object'); + node.expect(res.body.chats[i].asset).to.have.property('chat').to.be.an('object'); + node.expect(res.body.chats[i].asset.chat).to.have.property('message').to.not.equal(null); + node.expect(res.body.chats[i].asset.chat).to.have.property('own_message').to.not.equal(null); + node.expect(res.body.chats[i].asset.chat).to.have.property('type').to.not.equal(null); + } + done(); + }); + }); + + it('should return the chats list for a valid transaction for recipient1', function (done) { + getChats(recipient1.address, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('count').to.equal('2'); + node.expect(res.body).to.have.property('chats').to.have.lengthOf(2); + for (let i = 0; i < res.body.chats.length; i++) { + node.expect(res.body.chats[i]).to.have.property('participants').to.have.lengthOf(2); + node.expect(res.body.chats[i].participants[0].publicKey).to.not.equal(null); + node.expect(res.body.chats[i].participants[1].publicKey).to.not.equal(null); + node.expect(res.body.chats[i].timestamp).to.not.equal(null); + node.expect(res.body.chats[i].fee).to.not.equal(null); + node.expect(res.body.chats[i].amount).to.not.equal(null); + } + done(); + }, { orderBy: 'timestamp:desc'}); + }); + + it('should return the chats list for a valid transaction for recipient2', function (done) { + getChats(recipient2.address, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('count').to.equal('2'); + node.expect(res.body).to.have.property('chats').to.have.lengthOf(2); + for (let i = 0; i < res.body.chats.length; i++) { + node.expect(res.body.chats[i]).to.have.property('participants').to.have.lengthOf(2); + node.expect(res.body.chats[i].participants[0].publicKey).to.not.equal(null); + node.expect(res.body.chats[i].participants[1].publicKey).to.not.equal(null); + node.expect(res.body.chats[i].timestamp).to.not.equal(null); + node.expect(res.body.chats[i].fee).to.not.equal(null); + node.expect(res.body.chats[i].amount).to.not.equal(null); + } + done(); + }); + }); + + it('should return the chats list for a valid transaction with limit', function (done) { + getChats(sender.address, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('count').to.equal('2'); + node.expect(res.body).to.have.property('chats').to.have.lengthOf(1); + for (let i = 0; i < res.body.chats.length; i++) { + node.expect(res.body.chats[i]).to.have.property('participants').to.have.lengthOf(2); + for (let y = 0; y < res.body.chats[i].participants.length; y++) { + node.expect(res.body.chats[i].participants[y].publicKey).to.not.equal(null); + } + } + done(); + }, { limit: 1 }); + }); + + it('should return the chats list for a valid transaction with offset', function (done) { + getChats(sender.address, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('count').to.equal('2'); + node.expect(res.body).to.have.property('chats').to.have.lengthOf(1); + for (let i = 0; i < res.body.chats.length; i++) { + node.expect(res.body.chats[i]).to.have.property('participants').to.have.lengthOf(2); + for (let y = 0; y < res.body.chats[i].participants.length; y++) { + node.expect(res.body.chats[i].participants[y].publicKey).to.not.equal(null); + } + } + done(); + }, { offset: 1 }); + }); + + it('should return the chats list for a valid transaction with orderBy=timestamp:desc', function (done) { + getChats(sender.address, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('count').to.equal('2'); + node.expect(res.body).to.have.property('chats').to.have.lengthOf(2); + for (let i = 0; i < res.body.chats.length; i++) { + node.expect(res.body.chats[i]).to.have.property('participants').to.have.lengthOf(2); + for (let y = 0; y < res.body.chats[i].participants.length; y++) { + node.expect(res.body.chats[i].participants[y].publicKey).to.not.equal(null); + } + } + done(); + }, { orderBy: 'timestamp:desc' }); + }); + + it('should return the chats list for a valid transaction with orderBy=timestamp:asc', function (done) { + getChats(sender.address, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('count').to.equal('2'); + node.expect(res.body).to.have.property('chats').to.have.lengthOf(2); + for (let i = 0; i < res.body.chats.length; i++) { + node.expect(res.body.chats[i]).to.have.property('participants').to.have.lengthOf(2); + for (let y = 0; y < res.body.chats[i].participants.length; y++) { + node.expect(res.body.chats[i].participants[y].publicKey).to.not.equal(null); + } + } + done(); + }, { orderBy: 'timestamp:asc' }); + }); + + it('should return the chats list for a valid transaction with sender and recipient chats', function (done) { + getChats(recipient1.address, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('count').to.equal('2'); + node.expect(res.body).to.have.property('chats').to.have.lengthOf(2); + for (let i = 0; i < res.body.chats.length; i++) { + node.expect(res.body.chats[i]).to.have.property('participants').to.have.lengthOf(2); + for (let y = 0; y < res.body.chats[i].participants.length; y++) { + node.expect(res.body.chats[i].participants[y].publicKey).to.not.equal(null); + } + } + done(); + }); + }); + + it('should return the messages list for a valid transaction', function (done) { + getMessages(sender.address, recipient1.address, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('count').to.equal('2'); + node.expect(res.body).to.have.property('messages').to.have.lengthOf(2); + node.expect(res.body).to.have.property('participants').to.have.lengthOf(2); + node.expect(res.body.participants[0].address).to.equal(sender.address); + node.expect(res.body.participants[0].publicKey).to.equal(sender.publicKey.toString('hex')); + node.expect(res.body.participants[1].address).to.equal(recipient1.address); + node.expect(res.body.participants[1].publicKey).to.equal(recipient1.publicKey.toString('hex')); + done(); + }); + }); + + it('should return the messages list for a valid transaction with a limit', function (done) { + getMessages(sender.address, recipient1.address, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('count').to.equal('2'); + node.expect(res.body).to.have.property('messages').to.have.lengthOf(1); + node.expect(res.body).to.have.property('participants').to.have.lengthOf(2); + node.expect(res.body.participants[0].address).to.equal(sender.address); + node.expect(res.body.participants[0].publicKey).to.equal(sender.publicKey.toString('hex')); + node.expect(res.body.participants[1].address).to.equal(recipient1.address); + node.expect(res.body.participants[1].publicKey).to.equal(recipient1.publicKey.toString('hex')); + done(); + }, { + limit: 1 + }); + }); + + it('should return the messages list for a valid transaction with an offset', function (done) { + getMessages(sender.address, recipient1.address, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('count').to.equal('2'); + node.expect(res.body).to.have.property('messages').to.have.lengthOf(1); + node.expect(res.body).to.have.property('participants').to.have.lengthOf(2); + node.expect(res.body.participants[0].address).to.equal(sender.address); + node.expect(res.body.participants[0].publicKey).to.equal(sender.publicKey.toString('hex')); + node.expect(res.body.participants[1].address).to.equal(recipient1.address); + node.expect(res.body.participants[1].publicKey).to.equal(recipient1.publicKey.toString('hex')); + done(); + }, { + offset: 1 + }); + }); + + it('should return the messages list for a valid transaction with an orderBy=timestamp:desc', function (done) { + getMessages(sender.address, recipient1.address, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('count').to.equal('2'); + node.expect(res.body).to.have.property('messages').to.have.lengthOf(2); + node.expect(res.body).to.have.property('participants').to.have.lengthOf(2); + node.expect(res.body.participants[0].address).to.equal(sender.address); + node.expect(res.body.participants[0].publicKey).to.equal(sender.publicKey.toString('hex')); + node.expect(res.body.participants[1].address).to.equal(recipient1.address); + node.expect(res.body.participants[1].publicKey).to.equal(recipient1.publicKey.toString('hex')); + done(); + }, { + orderBy: 'timestamp:desc' + }); + }); + + it('should return the messages list for a valid transaction with an orderBy=timestamp:asc', function (done) { + getMessages(sender.address, recipient1.address, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('count').to.equal('2'); + node.expect(res.body).to.have.property('messages').to.have.lengthOf(2); + node.expect(res.body).to.have.property('participants').to.have.lengthOf(2); + node.expect(res.body.participants[0].address).to.equal(sender.address); + node.expect(res.body.participants[0].publicKey).to.equal(sender.publicKey.toString('hex')); + node.expect(res.body.participants[1].address).to.equal(recipient1.address); + node.expect(res.body.participants[1].publicKey).to.equal(recipient1.publicKey.toString('hex')); + done(); + }, { + orderBy: 'timestamp:asc' + }); + }); + + it('should return the messages list for a valid transaction with payments', function (done) { + getMessages(sender.address, recipient1.address, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('count').to.equal('3'); + node.expect(res.body).to.have.property('messages').to.have.lengthOf(3); + node.expect(res.body).to.have.property('participants').to.have.lengthOf(2); + node.expect(res.body.participants[0].address).to.equal(sender.address); + node.expect(res.body.participants[0].publicKey).to.equal(sender.publicKey.toString('hex')); + node.expect(res.body.participants[1].address).to.equal(recipient1.address); + node.expect(res.body.participants[1].publicKey).to.equal(recipient1.publicKey.toString('hex')); + done(); + }, { + withPayments: true + }); + }); + + it('should return the messages list for a valid transaction with payments with a limit', function (done) { + getMessages(sender.address, recipient1.address, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('count').to.equal('3'); + node.expect(res.body).to.have.property('messages').to.have.lengthOf(1); + node.expect(res.body).to.have.property('participants').to.have.lengthOf(2); + node.expect(res.body.participants[0].address).to.equal(sender.address); + node.expect(res.body.participants[0].publicKey).to.equal(sender.publicKey.toString('hex')); + node.expect(res.body.participants[1].address).to.equal(recipient1.address); + node.expect(res.body.participants[1].publicKey).to.equal(recipient1.publicKey.toString('hex')); + done(); + }, { + withPayments: true, + limit: 1 + }); + }); + + it('should return the messages list for a valid transaction with payments and with an offset', function (done) { + getMessages(sender.address, recipient1.address, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('count').to.equal('3'); + node.expect(res.body).to.have.property('messages').to.have.lengthOf(2); + node.expect(res.body).to.have.property('participants').to.have.lengthOf(2); + node.expect(res.body.participants[0].address).to.equal(sender.address); + node.expect(res.body.participants[0].publicKey).to.equal(sender.publicKey.toString('hex')); + node.expect(res.body.participants[1].address).to.equal(recipient1.address); + node.expect(res.body.participants[1].publicKey).to.equal(recipient1.publicKey.toString('hex')); + done(); + }, { + withPayments: true, + offset: 1 + }); + }); + + it('should return the messages list for a valid transaction with payments and with an orderBy=timestamp:desc', function (done) { + getMessages(sender.address, recipient1.address, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('count').to.equal('3'); + node.expect(res.body).to.have.property('messages').to.have.lengthOf(3); + node.expect(res.body).to.have.property('participants').to.have.lengthOf(2); + node.expect(res.body.participants[0].address).to.equal(sender.address); + node.expect(res.body.participants[0].publicKey).to.equal(sender.publicKey.toString('hex')); + node.expect(res.body.participants[1].address).to.equal(recipient1.address); + node.expect(res.body.participants[1].publicKey).to.equal(recipient1.publicKey.toString('hex')); + done(); + }, { + withPayments: true, + orderBy: 'timestamp:desc' + }); + }); + + it('should return the messages list for a valid transaction with payments and with an orderBy=timestamp:asc', function (done) { + getMessages(sender.address, recipient1.address, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('count').to.equal('3'); + node.expect(res.body).to.have.property('messages').to.have.lengthOf(3); + node.expect(res.body).to.have.property('participants').to.have.lengthOf(2); + node.expect(res.body.participants[0].address).to.equal(sender.address); + node.expect(res.body.participants[0].publicKey).to.equal(sender.publicKey.toString('hex')); + node.expect(res.body.participants[1].address).to.equal(recipient1.address); + node.expect(res.body.participants[1].publicKey).to.equal(recipient1.publicKey.toString('hex')); + done(); + }, { + withPayments: true, + orderBy: 'timestamp:asc' + }); + }); +}); \ No newline at end of file diff --git a/test/api/peer.js b/test/api/peer.js index a5df4453..d6302731 100644 --- a/test/api/peer.js +++ b/test/api/peer.js @@ -2,6 +2,7 @@ var node = require('./../node.js'); var ip = require('ip'); +var packageJSON = require('./../../package.json'); describe('GET /peer/list', function () { @@ -31,7 +32,7 @@ describe('GET /peer/list', function () { node.debug('> Response:'.grey, JSON.stringify(res.body)); node.expect(res.body).to.have.property('success').to.be.not.ok; node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); - node.expect(res.body).to.have.property('expected').to.eql('0.0.3a'); + node.expect(res.body).to.have.property('expected').to.eql(packageJSON.config.minVersion); node.expect(res.body).to.have.property('received').to.eql('0.1.0a'); done(); }); @@ -91,7 +92,7 @@ describe('GET /peer/height', function () { node.debug('> Response:'.grey, JSON.stringify(res.body)); node.expect(res.body).to.have.property('success').to.be.not.ok; node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); - node.expect(res.body).to.have.property('expected').to.eql('0.0.3a'); + node.expect(res.body).to.have.property('expected').to.eql(packageJSON.config.minVersion); node.expect(res.body).to.have.property('received').to.eql('0.1.0a'); done(); }); diff --git a/test/api/peer.signatures.js b/test/api/peer.signatures.js index 507bd565..3e5828ef 100644 --- a/test/api/peer.signatures.js +++ b/test/api/peer.signatures.js @@ -1,6 +1,7 @@ 'use strict'; var node = require('./../node.js'); +var packageJSON = require('./../../package.json'); describe('GET /peer/signatures', function () { @@ -22,7 +23,7 @@ describe('GET /peer/signatures', function () { node.debug('> Response:'.grey, JSON.stringify(res.body)); node.expect(res.body).to.have.property('success').to.be.not.ok; node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); - node.expect(res.body).to.have.property('expected').to.eql('0.0.3a'); + node.expect(res.body).to.have.property('expected').to.eql(packageJSON.config.minVersion); node.expect(res.body).to.have.property('received').to.eql('0.1.0a'); done(); }); @@ -89,7 +90,7 @@ describe('POST /peer/signatures', function () { node.debug('> Response:'.grey, JSON.stringify(res.body)); node.expect(res.body).to.have.property('success').to.be.not.ok; node.expect(res.body).to.have.property('message').to.eql('Request is made from incompatible version'); - node.expect(res.body).to.have.property('expected').to.eql('0.0.3a'); + node.expect(res.body).to.have.property('expected').to.eql(packageJSON.config.minVersion); node.expect(res.body).to.have.property('received').to.eql('0.1.0a'); done(); }); diff --git a/test/api/transactions.js b/test/api/transactions.js index 5219469b..f5e73d1d 100644 --- a/test/api/transactions.js +++ b/test/api/transactions.js @@ -260,6 +260,36 @@ describe('GET /api/transactions', function () { }); }); + it('using minFee with and:maxFee ordered by fee and limited should be ok', function (done) { + const limit = 10; + const offset = 0; + const orderBy = 'fee:asc'; + const minFee = constants.fees.delegate;; + const maxFee = constants.fees.delegate; + + var params = [ + 'minFee=' + minFee, + 'and:maxFee=' + maxFee, + 'limit=' + limit, + 'offset=' + offset, + 'orderBy=' + orderBy + ]; + + node.get('/api/transactions?' + params.join('&'), function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('transactions').that.is.an('array'); + node.expect(res.body.transactions).to.have.length.within(2, limit); + node.expect(res.body.transactions[0].fee).to.be.equal(minFee); + node.expect(res.body.transactions[res.body.transactions.length-1].fee).to.be.equal(maxFee); + for (var i = 0; i < res.body.transactions.length; i++) { + if (res.body.transactions[i + 1]) { + node.expect(res.body.transactions[i].fee).to.be.at.most(res.body.transactions[i + 1].fee); + } + } + done(); + }); + }); + it('using valid parameters with/without and/or should be ok', function (done) { var limit = 10; var offset = 0; @@ -393,6 +423,39 @@ describe('GET /api/transactions', function () { }); }); + it('using array-like types should be ok', function (done) { + const types = [node.txTypes.VOTE,node.txTypes.DELEGATE]; + const params = 'types=' + types.join(','); + + node.get('/api/transactions?' + params, function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('transactions').that.is.an('array'); + for (var i = 0; i < res.body.transactions.length; i++) { + if (res.body.transactions[i]) { + node.expect(res.body.transactions[i].type).to.be.oneOf(types); + } + } + done(); + }); + }); + + it('using noClutter param should be ok', function (done) { + node.get('/api/transactions?noClutter=1', function (err, res) { + node.expect(res.body).to.have.property('success').to.be.ok; + node.expect(res.body).to.have.property('transactions').that.is.an('array'); + for (var i = 0; i < res.body.transactions.length; i++) { + if (res.body.transactions[i]) { + try { + node.expect(res.body.transactions[i].type).to.be.not.equal(8); + } catch (e) { + node.expect(res.body.transactions[i].amount).to.be.not.equal(0); + } + } + } + done(); + }); + }); + it('using no params should be ok', function (done) { node.get('/api/transactions', function (err, res) { node.expect(res.body).to.have.property('success').to.be.ok; diff --git a/test/common/initModule.js b/test/common/initModule.js index 4a196870..b58e1ebb 100644 --- a/test/common/initModule.js +++ b/test/common/initModule.js @@ -9,6 +9,7 @@ var _ = require('lodash'); var async = require('../node').async; var dirname = path.join(__dirname, '..', '..'); var config = require(path.join(dirname, '/config.json')); +var packageJson = require(path.join(dirname, '/package.json')); var database = require(path.join(dirname, '/helpers', 'database.js')); var genesisblock = require(path.join(dirname, '/genesisBlock.json')); var Logger = require(dirname + '/logger.js'); @@ -26,6 +27,7 @@ var modulesLoader = new function () { this.logger = new Logger({ echo: null, errorLevel: config.fileLogLevel, filename: config.logFileName }); this.scope = { config: config, + packageJson: packageJson, genesisblock: { block: genesisblock }, logger: this.logger, network: { diff --git a/test/config.json b/test/config.json index f11c2704..abdba3f9 100644 --- a/test/config.json +++ b/test/config.json @@ -1,8 +1,6 @@ { "port": 36667, "address": "0.0.0.0", - "version": "0.0.3a", - "minVersion": "0.0.3a", "fileLogLevel": "info", "logFileName": "logs/adamant.log", "consoleLogLevel": "debug", diff --git a/test/node.js b/test/node.js index 03ceb37f..7e31ff9a 100644 --- a/test/node.js +++ b/test/node.js @@ -12,6 +12,7 @@ const bignum = require('../helpers/bignum.js'); const ByteBuffer = require('bytebuffer'); const Mnemonic = require('bitcore-mnemonic'); const transactionTypes = require('../helpers/transactionTypes.js'); +var packageJson = require('../package.json'); // Requires node.bignum = require('../helpers/bignum.js'); @@ -38,7 +39,7 @@ node.api = node.supertest(node.baseUrl); node.normalizer = 100000000; // Use this to convert LISK amount to normal value node.blockTime = 10000; // Block time in miliseconds node.blockTimePlus = 12000; // Block time + 2 seconds in miliseconds -node.version = node.config.version; // Node version +node.version = packageJson.version; // Node version // Transaction fees node.fees = { @@ -47,7 +48,8 @@ node.fees = { secondPasswordFee: node.constants.fees.secondsignature, delegateRegistrationFee: node.constants.fees.delegate, multisignatureRegistrationFee: node.constants.fees.multisignature, - dappAddFee: node.constants.fees.dapp + dappAddFee: node.constants.fees.dapp, + messageFee: node.constants.fees.chat_message }; // Test application @@ -199,6 +201,21 @@ node.createSignatureTransaction = function (data) { return transaction; }; +node.createChatTransaction = function (data) { + data.transactionType = this.txTypes.CHAT_MESSAGE; + let transaction = this.createBasicTransaction(data); + transaction.asset = {'chat' : { + message: data.message, + own_message: data.own_message, + type : data.message_type || 1 + }}; + transaction.recipientId = data.recipientId; + transaction.amount = 0; + transaction.signature = this.transactionSign(transaction, data.keyPair); + transaction.fee = this.constants.fees.chat_message; + return transaction; +}; + node.createSendTransaction = function (data) { data.transactionType = transactionTypes.SEND; let transaction = this.createBasicTransaction(data);