bitcore-wallet-service/lib/storage.js

318 lines
7.6 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
var _ = require('lodash');
var async = require('async');
var $ = require('preconditions').singleton();
var log = require('npmlog');
log.debug = log.verbose;
log.disableColor();
var util = require('util');
var mongodb = require('mongodb');
var Model = require('./model');
var collections = {
WALLETS: 'wallets',
TXS: 'txs',
ADDRESSES: 'addresses',
NOTIFICATIONS: 'notifications',
};
var Storage = function() {};
Storage.prototype.connect = function(opts, cb) {
var self = this;
opts = opts || {};
this.db = opts.db;
if (this.db) return cb();
var config = opts.mongoDb || {};
var url = 'mongodb://' + (config.host || 'localhost') + ':' + (config.port ||  27017) + '/bws';
mongodb.MongoClient.connect(url, function(err, db) {
if (err) {
log.error('Unable to connect to the mongoDB server.');
return cb(err);
}
self.db = db;
console.log('Connection established to ', url);
return cb();
});
};
Storage.prototype.fetchWallet = function(id, cb) {
this.db.collection(collections.WALLETS).findOne({
id: id
}, function(err, result) {
if (err) return cb(err);
if (!result) return cb();
return cb(null, Model.Wallet.fromObj(result));
});
};
Storage.prototype.storeWallet = function(wallet, cb) {
this.db.collection(collections.WALLETS).update({
id: wallet.id
}, wallet, {
w: 1,
upsert: true,
}, cb);
};
Storage.prototype.storeWalletAndUpdateCopayersLookup = function(wallet, cb) {
return this.storeWallet(wallet, cb);
};
Storage.prototype.fetchCopayerLookup = function(copayerId, cb) {
this.db.collection(collections.WALLETS).findOne({
'copayers.id': copayerId
}, {
fields: {
id: 1,
copayers: 1,
},
}, function(err, result) {
if (err) return cb(err);
if (!result) return cb();
var copayer = _.find(result.copayers, {
id: copayerId
});
return cb(null, {
walletId: result.id,
requestPubKey: copayer.requestPubKey,
});
});
};
// TODO: should be done client-side
Storage.prototype._completeTxData = function(walletId, txs, cb) {
var txList = [].concat(txs);
this.fetchWallet(walletId, function(err, wallet) {
if (err) return cb(err);
_.each(txList, function(tx) {
tx.creatorName = wallet.getCopayer(tx.creatorId).name;
_.each(tx.actions, function(action) {
action.copayerName = wallet.getCopayer(action.copayerId).name;
});
});
return cb(null, txs);
});
};
Storage.prototype.fetchTx = function(walletId, txProposalId, cb) {
var self = this;
this.db.collection(collections.TXS).findOne({
id: txProposalId,
walletId: walletId
}, function(err, result) {
if (err) return cb(err);
if (!result) return cb();
return self._completeTxData(walletId, Model.TxProposal.fromObj(result), cb);
});
};
Storage.prototype.fetchPendingTxs = function(walletId, cb) {
var self = this;
this.db.collection(collections.TXS).find({
walletId: walletId,
isPending: true
}).sort({
createdOn: -1
}).toArray(function(err, result) {
if (err) return cb(err);
if (!result) return cb();
var txs = _.map(result, function(tx) {
return Model.TxProposal.fromObj(tx);
});
return self._completeTxData(walletId, txs, cb);
});
};
/**
* fetchTxs. Times are in UNIX EPOCH (seconds)
*
* @param walletId
* @param opts.minTs
* @param opts.maxTs
* @param opts.limit
*/
Storage.prototype.fetchTxs = function(walletId, opts, cb) {
var self = this;
opts = opts || {};
var tsFilter = {};
if (_.isNumber(opts.minTs)) tsFilter.$gte = opts.minTs;
if (_.isNumber(opts.maxTs)) tsFilter.$lte = opts.maxTs;
var filter = {
walletId: walletId
};
if (!_.isEmpty(tsFilter)) filter.createdOn = tsFilter;
var mods = {};
if (_.isNumber(opts.limit)) mods.limit = opts.limit;
this.db.collection(collections.TXS).find(filter, mods).sort({
createdOn: -1
}).toArray(function(err, result) {
if (err) return cb(err);
if (!result) return cb();
var txs = _.map(result, function(tx) {
return Model.TxProposal.fromObj(tx);
});
return self._completeTxData(walletId, txs, cb);
});
};
/**
* fetchNotifications
*
* @param walletId
* @param opts.minTs
* @param opts.maxTs
* @param opts.limit
* @param opts.reverse
*/
Storage.prototype.fetchNotifications = function(walletId, opts, cb) {
var self = this;
opts = opts || {};
var tsFilter = {};
if (_.isNumber(opts.minTs)) tsFilter.$gte = opts.minTs;
if (_.isNumber(opts.maxTs)) tsFilter.$lte = opts.maxTs;
var filter = {
walletId: walletId
};
if (!_.isEmpty(tsFilter)) filter.createdOn = tsFilter;
var mods = {};
if (_.isNumber(opts.limit)) mods.limit = opts.limit;
this.db.collection(collections.NOTIFICATIONS).find(filter, mods).sort({
id: opts.reverse ? -1 : 1,
}).toArray(function(err, result) {
if (err) return cb(err);
if (!result) return cb();
var notifications = _.map(result, function(notification) {
return Model.Notification.fromObj(notification);
});
return cb(null, notifications);
});
};
// TODO: remove walletId from signature
Storage.prototype.storeNotification = function(walletId, notification, cb) {
this.db.collection(collections.NOTIFICATIONS).insert(notification, {
w: 1
}, cb);
};
// TODO: remove walletId from signature
Storage.prototype.storeTx = function(walletId, txp, cb) {
txp.isPending = txp.isPending(); // Persist attribute to use when querying
this.db.collection(collections.TXS).update({
id: txp.id,
walletId: walletId
}, txp, {
w: 1,
upsert: true,
}, cb);
};
Storage.prototype.removeTx = function(walletId, txProposalId, cb) {
this.db.collection(collections.TXS).findAndRemove({
id: txProposalId,
walletId: walletId
}, {
w: 1
}, cb);
};
Storage.prototype.removeWallet = function(walletId, cb) {
var self = this;
async.parallel([
function(next) {
self.db.collection(collections.WALLETS).findAndRemove({
id: walletId
}, next);
},
function(next) {
self.db.collection(collections.ADDRESSES).remove({
walletId: walletId
}, next);
},
function(next) {
self.db.collection(collections.TXS).remove({
walletId: walletId
}, next);
},
function(next) {
self.db.collection(collections.NOTIFICATIONS).remove({
walletId: walletId
}, next);
},
], cb);
};
Storage.prototype.fetchAddresses = function(walletId, cb) {
var self = this;
this.db.collection(collections.ADDRESSES).find({
walletId: walletId,
}).sort({
createdOn: 1
}).toArray(function(err, result) {
if (err) return cb(err);
if (!result) return cb();
var addresses = _.map(result, function(address) {
return Model.Address.fromObj(address);
});
return cb(null, addresses);
});
};
Storage.prototype.storeAddressAndWallet = function(wallet, addresses, cb) {
var self = this;
var addresses = [].concat(addresses);
if (addresses.length == 0) return cb();
this.db.collection(collections.ADDRESSES).insert(addresses, {
w: 1
}, function(err) {
if (err) return cb(err);
self.storeWallet(wallet, cb);
});
};
Storage.prototype._dump = function(cb, fn) {
fn = fn || console.log;
cb = cb || function() {};
var self = this;
this.db.collections(function(err, collections) {
if (err) return cb(err);
async.eachSeries(collections, function(col, next) {
col.find().toArray(function(err, items) {
fn('--------', col.s.name);
fn(items);
fn('------------------------------------------------------------------\n\n');
next(err);
});
}, cb);
});
};
module.exports = Storage;