WIP
This commit is contained in:
parent
47dd9c89b6
commit
234dd9ef94
2 changed files with 142 additions and 206 deletions
|
|
@ -10,46 +10,50 @@ var PG_ERRORS = {
|
|||
23505: 'uniqueViolation'
|
||||
};
|
||||
|
||||
var client = null;
|
||||
var conString = null;
|
||||
|
||||
function rollback(client) {
|
||||
function rollback(client, done) {
|
||||
//terminating a client connection will
|
||||
//automatically rollback any uncommitted transactions
|
||||
//so while it's not technically mandatory to call
|
||||
//ROLLBACK it is cleaner and more correct
|
||||
logger.warn('Rolling back transaction.');
|
||||
client.query('ROLLBACK', function() {
|
||||
client.end();
|
||||
client.query('ROLLBACK', function(err) {
|
||||
return done(err);
|
||||
});
|
||||
}
|
||||
|
||||
function getInsertQuery(tableName, fields) {
|
||||
function getInsertQuery(tableName, fields, hasId) {
|
||||
|
||||
// outputs string like: '$1, $2, $3...' with proper No of items
|
||||
var placeholders = fields.map(function(_, i) {
|
||||
return '$' + (i + 1);
|
||||
}).join(', ');
|
||||
|
||||
return 'INSERT INTO ' + tableName +
|
||||
var query = 'INSERT INTO ' + tableName +
|
||||
' (' + fields.join(', ') + ')' +
|
||||
' VALUES' +
|
||||
' (' + placeholders + ')';
|
||||
|
||||
if (hasId) query += ' RETURNING id';
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
exports.init = function init(conString) {
|
||||
if (client !== null) return;
|
||||
|
||||
exports.init = function init(_conString) {
|
||||
conString = _conString;
|
||||
if (!conString) {
|
||||
throw new Error('Postgres connection string is required');
|
||||
}
|
||||
|
||||
client = new pg.Client(conString);
|
||||
client.on('error', function (err) { logger.error(err); });
|
||||
|
||||
client.connect();
|
||||
};
|
||||
|
||||
function connect(cb) {
|
||||
pg.connect(conString, function(err, client, done) {
|
||||
if (err) logger.error(err);
|
||||
cb(err, client, done);
|
||||
});
|
||||
}
|
||||
|
||||
// logs inputted bill and overall tx status (if available)
|
||||
exports.recordBill = function recordBill(deviceFingerprint, rec, cb) {
|
||||
|
|
@ -86,32 +90,39 @@ exports.recordBill = function recordBill(deviceFingerprint, rec, cb) {
|
|||
fields.push('uuid');
|
||||
}
|
||||
|
||||
client.query(getInsertQuery('bills', fields), values, function(err) {
|
||||
if (err && PG_ERRORS[err.code] === 'uniqueViolation')
|
||||
return cb(null, {code: 204});
|
||||
connect(function(err, client, done) {
|
||||
if (err) return cb(err);
|
||||
client.query(client, getInsertQuery('bills', fields), values, function(err) {
|
||||
done();
|
||||
if (err && PG_ERRORS[err.code] === 'uniqueViolation')
|
||||
return cb(null, {code: 204});
|
||||
|
||||
cb(); // 201 => Accepted / saved
|
||||
cb(); // 201 => Accepted / saved
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.recordDeviceEvent = function recordDeviceEvent(deviceFingerprint, event, cb) {
|
||||
client.query('INSERT INTO device_events (device_fingerprint, event_type, note, device_time)' +
|
||||
'VALUES ($1, $2, $3, $4)',
|
||||
[deviceFingerprint, event.eventType, event.note, event.deviceTime],
|
||||
cb);
|
||||
exports.recordDeviceEvent = function recordDeviceEvent(deviceFingerprint, event) {
|
||||
connect(function(err, client, done) {
|
||||
if (err) return;
|
||||
client.query('INSERT INTO device_events (device_fingerprint, event_type, note, device_time)' +
|
||||
'VALUES ($1, $2, $3, $4)',
|
||||
[deviceFingerprint, event.eventType, event.note, event.deviceTime],
|
||||
done);
|
||||
});
|
||||
};
|
||||
|
||||
function query(queryStr, values, cb) {
|
||||
function query(client, queryStr, values, cb) {
|
||||
client.query(queryStr, values, cb);
|
||||
}
|
||||
|
||||
function silentQuery(queryStr, values, cb) {
|
||||
function silentQuery(client, queryStr, values, cb) {
|
||||
client.query(queryStr, values, function(err) {
|
||||
cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
function billsAndTxs(txid, currencyCode, deviceFingerprint, cb) {
|
||||
function billsAndTxs(client, txid, currencyCode, deviceFingerprint, cb) {
|
||||
var billsQuery = 'SELECT COALESCE(SUM(denomination), 0) as fiat, ' +
|
||||
'COALESCE(SUM(satoshis), 0) AS satoshis ' +
|
||||
'FROM bills ' +
|
||||
|
|
@ -124,8 +135,8 @@ function billsAndTxs(txid, currencyCode, deviceFingerprint, cb) {
|
|||
var txValues = billsValues; // They happen to be the same
|
||||
|
||||
async.parallel([
|
||||
async.apply(query, billsQuery, billsValues),
|
||||
async.apply(query, txQuery, txValues)
|
||||
async.apply(query, client, billsQuery, billsValues),
|
||||
async.apply(query, client, txQuery, txValues)
|
||||
], function(err, results) {
|
||||
if (err) return cb(err);
|
||||
|
||||
|
|
@ -154,10 +165,26 @@ function computeSendAmount(tx, totals) {
|
|||
return result;
|
||||
}
|
||||
|
||||
function insertTx(deviceFingerprint, tx, totals, cb) {
|
||||
function removePendingTx(client, tx, cb) {
|
||||
silentQuery(client, 'DELETE FROM TRANSACTIONS WHERE txid=$1 AND status=$2',
|
||||
[tx.txid, 'pending'], cb);
|
||||
}
|
||||
|
||||
function maybeInsertTx(client, deviceFingerprint, tx, totals, cb) {
|
||||
var sendAmount = computeSendAmount(tx, totals);
|
||||
if (sendAmount.satoshis === 0) return cb();
|
||||
|
||||
var status = _.isNumber(tx.fiat) ? 'machineSend' : 'timeout';
|
||||
var satoshis = sendAmount.satoshis;
|
||||
var fiat = sendAmount.fiat;
|
||||
insertTx(client, deviceFingerprint, tx, satoshis, fiat, status, function(err, results) {
|
||||
// unique violation shouldn't happen, since then sendAmount would be 0
|
||||
if (err) return cb(err);
|
||||
cb(null, {id: results.rows[0].id, satoshisToSend: sendAmount.satoshis});
|
||||
});
|
||||
}
|
||||
|
||||
function insertTx(client, deviceFingerprint, tx, satoshis, fiat, status, cb) {
|
||||
var fields = [
|
||||
'txid',
|
||||
'status',
|
||||
|
|
@ -171,90 +198,55 @@ function insertTx(deviceFingerprint, tx, totals, cb) {
|
|||
|
||||
var values = [
|
||||
tx.txId,
|
||||
_.isNumber(tx.fiat) ? 'machineSend' : 'timeout',
|
||||
status,
|
||||
tx.tx_type || 'buy',
|
||||
deviceFingerprint,
|
||||
tx.toAddress,
|
||||
sendAmount.satoshis,
|
||||
satoshis,
|
||||
tx.currencyCode,
|
||||
sendAmount.fiat
|
||||
fiat
|
||||
];
|
||||
|
||||
query(getInsertQuery('transactions', fields), values, function(err, result) {
|
||||
// unique violation shouldn't happen, since then sendAmount would be 0
|
||||
if (err) return cb(err);
|
||||
cb(null, sendAmount.satoshis);
|
||||
});
|
||||
query(client, getInsertQuery('transactions', fields, true), values, cb);
|
||||
}
|
||||
|
||||
function processTx(deviceFingerprint, tx, cb) {
|
||||
async.waterfall([
|
||||
async.apply(silentQuery, 'BEGIN'),
|
||||
async.apply(billsAndTxs, tx.currencyCode, deviceFingerprint),
|
||||
async.apply(insertTx, deviceFingerprint, tx)
|
||||
], function(err, satoshisToSend) {
|
||||
// if (err) DO some rollback
|
||||
silentQuery('COMMIT', function() {
|
||||
client.end();
|
||||
cb(null, satoshisToSend);
|
||||
exports.addPendingTx = function addPendingTx(deviceFingerprint, tx) {
|
||||
connect(function(err, client, done) {
|
||||
if (err) return;
|
||||
insertTx(client, deviceFingerprint, tx, 0, 0, 'pending',
|
||||
function(err) {
|
||||
done();
|
||||
|
||||
// If pending tx already exists, do nothing
|
||||
if (err && PG_ERRORS[err.code] !== 'uniqueViolation')
|
||||
logger.error(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Calling function should only send bitcoins if result !== null
|
||||
exports.addTx = function addTx(deviceFingerprint, tx, cb) {
|
||||
connect(function(err, client, done) {
|
||||
if (err) return cb(err);
|
||||
async.waterfall([
|
||||
async.apply(silentQuery, client, 'BEGIN'),
|
||||
async.apply(removePendingTx, client, tx),
|
||||
async.apply(billsAndTxs, client, tx.currencyCode, deviceFingerprint),
|
||||
async.apply(maybeInsertTx, client, deviceFingerprint, tx)
|
||||
], function(err, result) {
|
||||
if (err) {
|
||||
rollback(client, done);
|
||||
return cb(err);
|
||||
}
|
||||
silentQuery(client, 'COMMIT', function() {
|
||||
done();
|
||||
cb(null, result);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
exports.insertTx = function insertTx(deviceFingerprint, tx, cb) {
|
||||
var fields = [
|
||||
'id',
|
||||
'status',
|
||||
'tx_type',
|
||||
'device_fingerprint',
|
||||
'to_address',
|
||||
'satoshis',
|
||||
'currency_code',
|
||||
'fiat'
|
||||
];
|
||||
|
||||
var values = [
|
||||
tx.txId,
|
||||
tx.status || 'pending',
|
||||
tx.tx_type || 'buy',
|
||||
deviceFingerprint,
|
||||
tx.toAddress,
|
||||
tx.satoshis,
|
||||
tx.currencyCode,
|
||||
tx.fiat
|
||||
];
|
||||
|
||||
if (tx.partial_id && tx.partial_id > 1) {
|
||||
fields.push('partial_id');
|
||||
values.push(tx.partial_id);
|
||||
}
|
||||
|
||||
if (typeof tx.is_completed !== 'undefined') {
|
||||
fields.push('is_completed');
|
||||
values.push(tx.is_completed);
|
||||
}
|
||||
|
||||
// ----------------
|
||||
|
||||
async.waterfall([
|
||||
async.apply(query, 'BEGIN'),
|
||||
async.apply(query, 'BEGIN'),
|
||||
])
|
||||
client.query('BEGIN', function(err, result) {
|
||||
if(err) return rollback(client);
|
||||
client.query('INSERT INTO account(money) VALUES(100) WHERE id = $1', [1], function(err, result) {
|
||||
if(err) return rollback(client);
|
||||
client.query('INSERT INTO account(money) VALUES(-100) WHERE id = $1', [2], function(err, result) {
|
||||
if(err) return rollback(client);
|
||||
//disconnect after successful commit
|
||||
client.query('COMMIT', client.end.bind(client));
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
*/
|
||||
|
||||
exports.decrementCartridges =
|
||||
function decrementCartridges(fingerprint, cartridge1, cartridge2, cb) {
|
||||
var query = 'UPDATE devices SET cartridge_1_bills = cartridge_1_bills - $1, ' +
|
||||
|
|
@ -270,6 +262,7 @@ exports.fillCartridges =
|
|||
'WHERE fingerprint = $3';
|
||||
client.query(query, [cartridge1, cartridge2, fingerprint], cb);
|
||||
};
|
||||
*/
|
||||
|
||||
var tx = {fiat: 100, satoshis: 10090000};
|
||||
exports.init('psql://lamassu:lamassu@localhost/lamassu');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue