Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes and fixes #3

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions constants.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
/*
Note that COINBASE_PREORDER_DATA_CODE is the button code from
coinbase.com/merchant_tools, used for the Preorder Button. This is
different from the COINBASE_API key in .env file, which is from
coinbase.com/account/integrations
Note that COINBASE_PREORDER_DATA_CODE is the button code from
coinbase.com/merchant_tools, used for the Preorder Button. This is
different from the COINBASE_API key in .env file, which is from
coinbase.com/account/integrations

The button code can be shown publicly, while the API key should only be
included in a .env file and never shown publicly. The former allows
people to send you money, the latter allows people to send money from
your account.
The button code can be shown publicly, while the API key should only be
included in a .env file and never shown publicly. The former allows
people to send you money, the latter allows people to send money from
your account.

For FUNDING_UNIT_SYMBOL, we use mBTC to represent 1/1000 of a Bitcoin and
FUNDING_SI_SCALE for the corresponding multiplier.
For FUNDING_UNIT_SYMBOL, we use mBTC to represent 1/1000 of a Bitcoin and
FUNDING_SI_SCALE for the corresponding multiplier.

Note that for FUNDING_UNIT_SYMBOL, in theory we could use the Thai Baht
symbol, but then we'd have to change the font. If you use another
payment backend, you can substitute "$" for the dollar or use one of the
other currency symbols.
Note that for FUNDING_UNIT_SYMBOL, in theory we could use the Thai Baht
symbol, but then we'd have to change the font. If you use another
payment backend, you can substitute "$" for the dollar or use one of the
other currency symbols.

- https://en.bitcoin.it/wiki/Bitcoin_symbol#Existing_Unicode_symbol
- http://webdesign.about.com/od/localization/l/blhtmlcodes-cur.htm#codes
- https://en.bitcoin.it/wiki/Bitcoin_symbol#Existing_Unicode_symbol
- http://webdesign.about.com/od/localization/l/blhtmlcodes-cur.htm#codes
*/
var Constants = {
APP_NAME: "Bitstarter",
APP_NAME: "Bitstarter",
FUNDING_TARGET: 10.00,
FUNDING_UNIT_SYMBOL: "mBTC",
FUNDING_SI_SCALE: 1000,
Expand All @@ -32,7 +32,7 @@ var Constants = {
TWITTER_TWEET: "This student crowdfunder looks interesting.",
COINBASE_PREORDER_DATA_CODE: "13b56883764b54e6ab56fef3bcc7229c",
days_left: function() {
return Math.max(Math.ceil((this.FUNDING_END_DATE - new Date()) / (1000*60*60*24)), 0);
return Math.max(Math.ceil((this.FUNDING_END_DATE - new Date()) / (1000*60*60*24)), 0);
}
};

Expand Down
129 changes: 71 additions & 58 deletions models/coinbase.js
Original file line number Diff line number Diff line change
@@ -1,69 +1,80 @@
/*
The Coinbase API limits the number of orders that can be mirrored at a
time to 25, with subsequent orders on new pages.

The following code hits the API once to determine the number of pages,
and then uses this input to set up an async.mapLimit that pulls
the order data and merges it together.

Note that there are several improvements possible here:

- You can add much more sophisticated error handling for each Coinbase
API call, along with retries for fails, delays between requests, and
the like.

- You can make each Coinbase API call write directly to the database,
rather than aggregating them and writing in one block. Depending
on what you want to do, this might be preferable.

- If you have a very large number of orders, you might have an issue
with the default Heroku deployment process, which requires a port to
be bound within 60 seconds of deployment. In this case you might not
be able to do database update and deploy in one step and would have to
revisit how web.js is set up; for example you might only download as
many orders as you can get in the first 60 seconds, and then have the
rest downloaded after the app boots. Or you might mirror all the
Coinbase data offline and have the database separate from the main
app.

Overall, though, this is another good illustration of using async.compose
to manage asynchrony.
The Coinbase API limits the number of orders that can be mirrored at a
time to 25, with subsequent orders on new pages.

The following code hits the API once to determine the number of pages,
and then uses this input to set up an async.mapLimit that pulls
the order data and merges it together.

Note that there are several improvements possible here:

- You can add much more sophisticated error handling for each Coinbase
API call, along with retries for fails, delays between requests, and
the like.

- You can make each Coinbase API call write directly to the database,
rather than aggregating them and writing in one block. Depending
on what you want to do, this might be preferable.

- If you have a very large number of orders, you might have an issue
with the default Heroku deployment process, which requires a port to
be bound within 60 seconds of deployment. In this case you might not
be able to do database update and deploy in one step and would have to
revisit how web.js is set up; for example you might only download as
many orders as you can get in the first 60 seconds, and then have the
rest downloaded after the app boots. Or you might mirror all the
Coinbase data offline and have the database separate from the main
app.

Overall, though, this is another good illustration of using async.compose
to manage asynchrony.
*/
var async = require('async');
var request = require('request');
var uu = require('underscore');
var async = require('async')
, request = require('request')
, uu = require('underscore');

var coinbase_api_url = function(page) {
return "https://coinbase.com/api/v1/orders?page=" +
page.toString() + "&api_key=" + process.env.COINBASE_API_KEY;
return "https://coinbase.com/api/v1/orders?page=" +
page.toString() + "&api_key=" + process.env.COINBASE_API_KEY;
};

var get_ncoinbase_page = function(init, cb) {
request.get(coinbase_api_url(init), function(err, resp, body) {
var orders_json = JSON.parse(body);
console.log("Finished get_ncoinbase_page");
cb(null, orders_json.num_pages);
});
request.get(coinbase_api_url(init), function(err, resp, body) {
var orders_json = JSON.parse(body);
console.log("Finished get_ncoinbase_page");
// Handle the errors
if (orders_json.error) {
console.log("Error: %s", orders_json.error);
cb(orders_json.error);
} else {
cb(null, orders_json.num_pages);
}
});
};

var ncoinbase_page2coinbase_json = function(npage, cb) {
console.log("Starting ncoinbase_page2coinbase_json with npage = " + npage);
var inds = uu.range(1, npage + 1);
var LIMIT = 5;
var getjson = function(item, cb2) {
request.get(coinbase_api_url(item), function(err, resp, body) {
var orders_json = JSON.parse(body);
console.log("Finished API request for Coinbase Order Page " + item);
cb2(null, orders_json.orders);
});
};
async.mapLimit(inds, LIMIT, getjson, function(err, results) {
cb(null, uu.flatten(results));
console.log("Starting ncoinbase_page2coinbase_json with npage = " + npage);
var inds = uu.range(1, npage + 1);
var LIMIT = 5;

var getjson = function(item, cb2) {
request.get(coinbase_api_url(item), function(err, resp, body) {
var orders_json = JSON.parse(body);
console.log("Finished API request for Coinbase Order Page " + item);
cb2(null, orders_json.orders);
});
};

async.mapLimit(inds, LIMIT, getjson, function(err, results) {
cb(null, uu.flatten(results));
});
};

var get_coinbase_json = async.compose(ncoinbase_page2coinbase_json,
get_ncoinbase_page);
var get_coinbase_json = async.compose(
ncoinbase_page2coinbase_json,
get_ncoinbase_page
);

/*
Example of API use.

Expand All @@ -76,10 +87,12 @@ var get_coinbase_json = async.compose(ncoinbase_page2coinbase_json,
...and parse the num_pages field from it first.
*/
var debug_get_coinbase_json = function() {
get_coinbase_json(1, function(err, result) {
console.log(result);
});
get_coinbase_json(1, function(err, result) {
console.log(result);
});
};

module.exports = { 'get_coinbase_json': get_coinbase_json,
'debug_get_coinbase_json': debug_get_coinbase_json};
module.exports = {
'get_coinbase_json': get_coinbase_json,
'debug_get_coinbase_json': debug_get_coinbase_json
};
99 changes: 50 additions & 49 deletions models/index.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,54 @@
if (!global.hasOwnProperty('db')) {
var Sequelize = require('sequelize');
var sq = null;
var fs = require('fs');
var path = require('path');
var PGPASS_FILE = path.join(__dirname, '../.pgpass');
if (process.env.DATABASE_URL) {
/* Remote database
Do `heroku config` for details. We will be parsing a connection
string of the form:
postgres://bucsqywelrjenr:ffGhjpe9dR13uL7anYjuk3qzXo@\
ec2-54-221-204-17.compute-1.amazonaws.com:5432/d4cftmgjmremg1
*/
var pgregex = /postgres:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/;
var match = process.env.DATABASE_URL.match(pgregex);
var user = match[1];
var password = match[2];
var host = match[3];
var port = match[4];
var dbname = match[5];
var config = {
dialect: 'postgres',
protocol: 'postgres',
port: port,
host: host,
logging: true //false
};
sq = new Sequelize(dbname, user, password, config);
} else {
/* Local database
We parse the .pgpass file for the connection string parameters.
*/
var pgtokens = fs.readFileSync(PGPASS_FILE).toString().trimRight().split(':');
var host = pgtokens[0];
var port = pgtokens[1];
var dbname = pgtokens[2];
var user = pgtokens[3];
var password = pgtokens[4];
var config = {
dialect: 'postgres',
protocol: 'postgres',
port: port,
host: host,
};
var sq = new Sequelize(dbname, user, password, config);
}
global.db = {
Sequelize: Sequelize,
sequelize: sq,
Order: sq.import(__dirname + '/order')
var Sequelize = require('sequelize');
var sq = null;
var fs = require('fs');
var path = require('path');
var PGPASS_FILE = path.join(__dirname, '../.pgpass');
if (process.env.DATABASE_URL) {
/* Remote database
Do `heroku config` for details. We will be parsing a connection
string of the form:
postgres://bucsqywelrjenr:ffGhjpe9dR13uL7anYjuk3qzXo@\
ec2-54-221-204-17.compute-1.amazonaws.com:5432/d4cftmgjmremg1
*/
var pgregex = /postgres:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/;
var match = process.env.DATABASE_URL.match(pgregex);
var user = match[1];
var password = match[2];
var host = match[3];
var port = match[4];
var dbname = match[5];
var config = {
dialect: 'postgres',
protocol: 'postgres',
port: port,
host: host,
logging: true //false
};
sq = new Sequelize(dbname, user, password, config);
} else {
/* Local database
We parse the .pgpass file for the connection string parameters.
*/
var pgtokens = fs.readFileSync(PGPASS_FILE).toString().trimRight().split(':');
var host = pgtokens[0];
var port = pgtokens[1];
var dbname = pgtokens[2];
var user = pgtokens[3];
var password = pgtokens[4];
var config = {
dialect: 'postgres',
protocol: 'postgres',
port: port,
host: host,
};
var sq = new Sequelize(dbname, user, password, config);
}

global.db = {
Sequelize: Sequelize,
sequelize: sq,
Order: sq.import(__dirname + '/order')
};
}
module.exports = global.db;
Loading