bitcore-wallet-service/lib/storage.js

467 lines
11 KiB
JavaScript
Raw Normal View History

2015-01-27 05:18:45 -08:00
'use strict';
var _ = require('lodash');
2015-02-09 13:07:15 -08:00
var async = require('async');
2015-01-27 05:18:45 -08:00
var $ = require('preconditions').singleton();
var log = require('npmlog');
log.debug = log.verbose;
2015-04-18 02:55:24 -07:00
log.disableColor();
2015-04-20 15:53:19 -07:00
var util = require('util');
var mongodb = require('mongodb');
2015-01-27 05:18:45 -08:00
2015-04-20 15:53:19 -07:00
var Model = require('./model');
var collections = {
WALLETS: 'wallets',
TXS: 'txs',
ADDRESSES: 'addresses',
NOTIFICATIONS: 'notifications',
COPAYERS_LOOKUP: 'copayers_lookup',
2015-04-27 11:38:33 -07:00
PREFERENCES: 'preferences',
2015-04-28 05:47:25 -07:00
EMAIL_QUEUE: 'email_queue',
2015-04-20 15:53:19 -07:00
};
2015-01-27 05:18:45 -08:00
2015-04-20 17:11:10 -07:00
var Storage = function(opts) {
opts = opts || {};
this.db = opts.db;
};
2015-04-20 16:46:45 -07:00
2015-05-07 14:16:10 -07:00
Storage.prototype._createIndexes = function() {
this.db.collection(collections.WALLETS).createIndex({
id: 1
});
this.db.collection(collections.COPAYERS_LOOKUP).createIndex({
copayerId: 1
});
this.db.collection(collections.TXS).createIndex({
walletId: 1,
id: 1,
});
this.db.collection(collections.TXS).createIndex({
walletId: 1,
isPending: 1,
});
this.db.collection(collections.NOTIFICATIONS).createIndex({
walletId: 1,
id: 1,
});
this.db.collection(collections.ADDRESSES).createIndex({
walletId: 1
});
this.db.collection(collections.ADDRESSES).createIndex({
address: 1,
});
2015-06-09 14:22:06 -07:00
this.db.collection(collections.EMAIL_QUEUE).createIndex({
notificationId: 1,
});
2015-05-07 14:16:10 -07:00
};
2015-04-20 16:46:45 -07:00
Storage.prototype.connect = function(opts, cb) {
var self = this;
2015-02-02 12:07:18 -08:00
opts = opts || {};
2015-04-05 11:42:56 -07:00
2015-05-07 14:16:10 -07:00
if (this.db) return cb();
2015-04-20 16:46:45 -07:00
var config = opts.mongoDb || {};
2015-04-23 12:30:16 -07:00
mongodb.MongoClient.connect(config.uri, function(err, db) {
2015-04-20 16:46:45 -07:00
if (err) {
2015-04-23 12:30:16 -07:00
log.error('Unable to connect to the mongoDB server on ', config.uri);
2015-04-20 16:46:45 -07:00
return cb(err);
}
self.db = db;
2015-05-07 14:16:10 -07:00
self._createIndexes();
2015-04-23 12:30:16 -07:00
console.log('Connection established to ', config.uri);
2015-05-07 14:16:10 -07:00
return cb();
2015-04-21 10:43:35 -07:00
});
};
Storage.prototype.disconnect = function(cb) {
var self = this;
this.db.close(true, function(err) {
if (err) return cb(err);
self.db = null;
2015-04-20 16:46:45 -07:00
return cb();
});
2015-01-27 05:18:45 -08:00
};
2015-02-06 12:04:10 -08:00
Storage.prototype.fetchWallet = function(id, cb) {
2015-04-20 15:53:19 -07:00
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));
2015-02-02 12:07:18 -08:00
});
2015-01-27 05:18:45 -08:00
};
2015-02-06 12:04:10 -08:00
Storage.prototype.storeWallet = function(wallet, cb) {
2015-04-20 15:53:19 -07:00
this.db.collection(collections.WALLETS).update({
id: wallet.id
}, wallet, {
w: 1,
upsert: true,
}, cb);
2015-01-29 10:00:35 -08:00
};
2015-02-06 12:04:10 -08:00
Storage.prototype.storeWalletAndUpdateCopayersLookup = function(wallet, cb) {
var self = this;
2015-02-06 05:46:41 -08:00
var copayerLookups = _.map(wallet.copayers, function(copayer) {
return {
copayerId: copayer.id,
walletId: wallet.id,
2015-04-20 15:53:19 -07:00
requestPubKey: copayer.requestPubKey,
};
});
this.db.collection(collections.COPAYERS_LOOKUP).remove({
walletId: wallet.id
}, {
w: 1
}, function(err) {
if (err) return cb(err);
self.db.collection(collections.COPAYERS_LOOKUP).insert(copayerLookups, {
w: 1
}, function(err) {
if (err) return cb(err);
return self.storeWallet(wallet, cb);
2015-04-20 15:53:19 -07:00
});
2015-02-06 05:46:41 -08:00
});
};
2015-04-21 12:24:01 -07:00
Storage.prototype.fetchCopayerLookup = function(copayerId, cb) {
this.db.collection(collections.COPAYERS_LOOKUP).findOne({
copayerId: copayerId
}, function(err, result) {
2015-04-21 12:24:01 -07:00
if (err) return cb(err);
if (!result) return cb();
return cb(null, result);
2015-04-21 12:24:01 -07:00
});
};
2015-04-20 15:53:19 -07:00
// TODO: should be done client-side
2015-02-13 13:27:35 -08:00
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;
2015-02-20 07:33:46 -08:00
_.each(tx.actions, function(action) {
2015-02-13 13:27:35 -08:00
action.copayerName = wallet.getCopayer(action.copayerId).name;
});
});
return cb(null, txs);
});
};
2015-02-11 10:42:49 -08:00
2015-02-13 13:27:35 -08:00
Storage.prototype.fetchTx = function(walletId, txProposalId, cb) {
var self = this;
2015-04-20 15:53:19 -07:00
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);
2015-02-11 10:42:49 -08:00
});
};
2015-06-12 12:05:33 -07:00
Storage.prototype.fetchLastTxs = function(walletId, creatorId, limit, cb) {
var self = this;
this.db.collection(collections.TXS).find({
walletId: walletId,
creatorId: creatorId,
}, {
limit: limit || 5
}).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 cb(null, txs);
});
};
2015-02-06 12:26:43 -08:00
Storage.prototype.fetchPendingTxs = function(walletId, cb) {
2015-02-13 13:27:35 -08:00
var self = this;
2015-04-20 15:53:19 -07:00
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);
2015-02-06 12:26:43 -08:00
});
2015-04-20 15:53:19 -07:00
return self._completeTxData(walletId, txs, cb);
});
2015-02-06 12:26:43 -08:00
};
2015-02-06 23:09:45 -08:00
/**
2015-02-12 05:26:13 -08:00
* fetchTxs. Times are in UNIX EPOCH (seconds)
2015-02-06 23:09:45 -08:00
*
* @param walletId
* @param opts.minTs
* @param opts.maxTs
* @param opts.limit
*/
Storage.prototype.fetchTxs = function(walletId, opts, cb) {
2015-02-13 13:27:35 -08:00
var self = this;
2015-02-06 23:09:45 -08:00
opts = opts || {};
2015-04-20 15:53:19 -07:00
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);
2015-02-02 12:07:18 -08:00
});
2015-04-20 15:53:19 -07:00
return self._completeTxData(walletId, txs, cb);
});
2015-01-27 05:18:45 -08:00
};
2015-02-11 10:42:49 -08:00
/**
* fetchNotifications
*
* @param walletId
* @param opts.minTs
* @param opts.maxTs
* @param opts.limit
2015-04-20 15:53:19 -07:00
* @param opts.reverse
2015-02-11 10:42:49 -08:00
*/
Storage.prototype.fetchNotifications = function(walletId, opts, cb) {
2015-04-20 15:53:19 -07:00
var self = this;
2015-02-11 10:42:49 -08:00
opts = opts || {};
2015-04-20 15:53:19 -07:00
var tsFilter = {};
if (_.isNumber(opts.minTs)) tsFilter.$gte = opts.minTs;
if (_.isNumber(opts.maxTs)) tsFilter.$lte = opts.maxTs;
2015-02-11 10:42:49 -08:00
2015-04-20 15:53:19 -07:00
var filter = {
walletId: walletId
};
if (!_.isEmpty(tsFilter)) filter.createdOn = tsFilter;
2015-02-11 10:42:49 -08:00
2015-04-20 15:53:19 -07:00
var mods = {};
if (_.isNumber(opts.limit)) mods.limit = opts.limit;
2015-02-11 10:42:49 -08:00
2015-04-20 15:53:19 -07:00
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);
2015-02-06 14:57:52 -08:00
});
2015-04-20 15:53:19 -07:00
return cb(null, notifications);
});
2015-01-28 05:36:49 -08:00
};
2015-02-07 17:31:37 -08:00
2015-04-20 15:53:19 -07:00
// TODO: remove walletId from signature
Storage.prototype.storeNotification = function(walletId, notification, cb) {
this.db.collection(collections.NOTIFICATIONS).insert(notification, {
w: 1
}, cb);
2015-02-07 17:31:37 -08:00
};
2015-04-20 15:53:19 -07:00
// 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);
2015-02-07 17:31:37 -08:00
};
2015-04-20 15:53:19 -07:00
Storage.prototype.removeTx = function(walletId, txProposalId, cb) {
this.db.collection(collections.TXS).findAndRemove({
id: txProposalId,
walletId: walletId
}, {
w: 1
}, cb);
2015-02-09 13:07:15 -08:00
};
2015-02-07 17:31:37 -08:00
Storage.prototype.removeWallet = function(walletId, cb) {
var self = this;
2015-04-20 15:53:19 -07:00
async.parallel([
2015-02-11 10:42:49 -08:00
2015-02-09 13:26:25 -08:00
function(next) {
2015-04-20 15:53:19 -07:00
self.db.collection(collections.WALLETS).findAndRemove({
id: walletId
}, next);
},
function(next) {
var otherCollections = _.without(_.values(collections), collections.WALLETS);
async.each(otherCollections, function(col, next) {
self.db.collection(col).remove({
walletId: walletId
}, next);
2015-04-20 15:53:19 -07:00
}, next);
2015-02-07 17:31:37 -08:00
},
], cb);
};
2015-02-06 12:04:10 -08:00
Storage.prototype.fetchAddresses = function(walletId, cb) {
2015-04-20 15:53:19 -07:00
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);
2015-02-02 12:07:18 -08:00
});
2015-04-20 15:53:19 -07:00
return cb(null, addresses);
});
2015-01-27 05:18:45 -08:00
};
2015-04-01 12:42:12 -07:00
Storage.prototype.storeAddressAndWallet = function(wallet, addresses, cb) {
2015-04-20 15:53:19 -07:00
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);
2015-04-01 12:42:12 -07:00
});
2015-01-28 12:40:37 -08:00
};
2015-05-06 10:32:01 -07:00
Storage.prototype.fetchAddress = function(address, cb) {
2015-05-06 08:48:43 -07:00
var self = this;
this.db.collection(collections.ADDRESSES).findOne({
address: address,
}, function(err, result) {
if (err) return cb(err);
if (!result) return cb();
2015-05-06 10:32:01 -07:00
return cb(null, Model.Address.fromObj(result));
2015-05-06 08:48:43 -07:00
});
};
2015-04-27 11:38:33 -07:00
Storage.prototype.fetchPreferences = function(walletId, copayerId, cb) {
2015-04-28 20:34:18 -07:00
this.db.collection(collections.PREFERENCES).find({
2015-04-27 11:38:33 -07:00
walletId: walletId,
2015-04-28 20:34:18 -07:00
}).toArray(function(err, result) {
2015-04-27 11:38:33 -07:00
if (err) return cb(err);
2015-04-28 20:34:18 -07:00
if (copayerId) {
result = _.find(result, {
copayerId: copayerId
});
}
2015-04-27 11:38:33 -07:00
if (!result) return cb();
2015-04-28 20:34:18 -07:00
var preferences = _.map([].concat(result), function(r) {
return Model.Preferences.fromObj(r);
});
if (copayerId) {
preferences = preferences[0];
}
return cb(null, preferences);
2015-04-27 11:38:33 -07:00
});
};
Storage.prototype.storePreferences = function(preferences, cb) {
this.db.collection(collections.PREFERENCES).update({
walletId: preferences.walletId,
copayerId: preferences.copayerId,
}, preferences, {
w: 1,
upsert: true,
}, cb);
};
2015-05-06 08:48:43 -07:00
2015-04-28 05:47:25 -07:00
Storage.prototype.storeEmail = function(email, cb) {
this.db.collection(collections.EMAIL_QUEUE).update({
id: email.id,
2015-04-28 20:34:18 -07:00
}, email, {
2015-04-28 05:47:25 -07:00
w: 1,
upsert: true,
}, cb);
};
Storage.prototype.fetchUnsentEmails = function(cb) {
this.db.collection(collections.EMAIL_QUEUE).find({
status: 'pending',
2015-04-28 20:34:18 -07:00
}).toArray(function(err, result) {
2015-04-28 05:47:25 -07:00
if (err) return cb(err);
2015-04-30 12:05:49 -07:00
if (!result || _.isEmpty(result)) return cb(null, []);
2015-04-28 05:47:25 -07:00
return cb(null, Model.Email.fromObj(result));
});
};
2015-06-09 14:22:06 -07:00
Storage.prototype.fetchEmailByNotification = function(notificationId, cb) {
this.db.collection(collections.EMAIL_QUEUE).findOne({
notificationId: notificationId,
}, function(err, result) {
if (err) return cb(err);
if (!result) return cb();
return cb(null, Model.Email.fromObj(result));
});
};
2015-04-28 05:47:25 -07:00
2015-02-09 13:07:15 -08:00
Storage.prototype._dump = function(cb, fn) {
fn = fn || console.log;
2015-04-20 15:53:19 -07:00
cb = cb || function() {};
2015-02-09 13:07:15 -08:00
2015-04-20 15:53:19 -07:00
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);
});
2015-01-27 05:18:45 -08:00
};
Storage.collections = collections;
2015-01-27 05:18:45 -08:00
module.exports = Storage;