├── config
├── test.js
├── index.js
├── production.js
└── development.js
├── .babelrc
├── .travis.yml
├── views
├── index.jade
├── error.jade
└── layout.jade
├── graph
├── index.js
└── CoinDailyHistoryGraph.js
├── utils
├── PromiseUtils.js
├── ObjectUtils.js
├── MarketUtils.js
└── APIUtils.js
├── models
├── DBModel.js
├── CacheStorage
│ ├── index.js
│ └── Coins.js
├── DiskStorage
│ ├── Coin
│ │ ├── util.js
│ │ ├── CoinList.js
│ │ └── CoinUpdate.js
│ ├── Exchange
│ │ ├── ExchangeUpdate.js
│ │ ├── ExchangeSave.js
│ │ └── ExchangeList.js
│ └── index.js
├── APIStorage
│ ├── Exchange
│ │ ├── utils.js
│ │ ├── ExchangeConstants.js
│ │ └── ExchangeList.js
│ ├── index.js
│ └── Coin
│ │ └── CoinList.js
├── GetCoinList.js
├── CoinModels.js
└── ExchangeModels.js
├── constants.js
├── .gitignore
├── logs
└── logger.js
├── routes
├── cron
│ ├── index.js
│ └── CronRoutes.js
├── schema
│ ├── index.js
│ ├── ExchangeTables.js
│ └── CreateTable.js
├── exchange
│ ├── index.js
│ └── ExchangeRoutes.js
├── coins
│ ├── index.js
│ └── CoinRoutes.js
└── index.js
├── package.json
├── app.js
├── bin
└── www
├── test
└── exchange.test.js
├── README.md
└── LICENSE
/config/test.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015"]
3 | }
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 |
--------------------------------------------------------------------------------
/config/production.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "iojs"
4 | - "7"
--------------------------------------------------------------------------------
/views/index.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | h1= title
5 | p Welcome to #{title}
6 |
--------------------------------------------------------------------------------
/views/error.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | h1= message
5 | h2= error.status
6 | pre #{error.stack}
7 |
--------------------------------------------------------------------------------
/views/layout.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html
3 | head
4 | title= title
5 | link(rel='stylesheet', href='/stylesheets/style.css')
6 | body
7 | block content
8 |
--------------------------------------------------------------------------------
/config/development.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 |
3 | 'use strict';
4 |
5 | module.exports = {
6 | env: 'development',
7 | db: 'mongodb://localhost/Tododb',
8 | port: process.env.PORT || 4000,
9 | };
10 |
11 |
--------------------------------------------------------------------------------
/graph/index.js:
--------------------------------------------------------------------------------
1 | // pRoy24 tokenplex
2 |
3 | const CoinDailyHistoryGraph = require('../graph/CoinDailyHistoryGraph');
4 | module.exports = {
5 | chartCoinDailyHistoryGraph: function (responseData) {
6 | return CoinDailyHistoryGraph.chartDailyCoinHistory(responseData);
7 | }
8 | }
--------------------------------------------------------------------------------
/utils/PromiseUtils.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 |
3 | module.exports = {
4 | getUserAuthToken: function(headers) {
5 | const headerParts = req.headers.authorization.split(' ')
6 | let token = "";
7 | if (headerParts[0].toLowerCase() === 'bearer') {
8 | token = headerParts[1];
9 | }
10 | return token;
11 | }
12 | }
--------------------------------------------------------------------------------
/models/DBModel.js:
--------------------------------------------------------------------------------
1 |
2 | const cassandra = require('cassandra-driver');
3 | var Constants = require('../constants');
4 | const cassandraClient = new cassandra.Client({contactPoints: [Constants.CQL_API_SERVER]});
5 |
6 | module.exports = {
7 | getCassandraClientConnection: function() {
8 | return cassandraClient.connect()
9 | },
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/constants.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 | /**
3 | * Change these values to the host that you are running
4 | * @type {{CQL_API_SERVER: string, REDIS_API_SERVER: string}}
5 | */
6 | module.exports = {
7 | REDIS_API_SERVER: "127.0.0.1", // If running remote redis server, create a REDIS host here.
8 | CQL_API_SERVER: "127.0.0.1:9042" // If running remote CQL server, create a CQL host here.
9 | };
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 | /public
23 |
24 | /logs/debug.log
25 | /logs/error.log
26 |
27 | /.idea
28 |
29 | /.env
--------------------------------------------------------------------------------
/models/CacheStorage/index.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 | var Coins = require('./Coins');
3 |
4 | module.exports = {
5 | saveCoinList: function(coinList) {
6 | return Coins.saveCoinList(coinList);
7 | },
8 | getCoinList: function(rangeRequest) {
9 | return Coins.getCoinList(rangeRequest);
10 | },
11 | searchCoin: function (coinSearchString) {
12 | return Coins.searchCoin(coinSearchString);
13 | },
14 | deleteCoinList: function(token) {
15 | return Coins.deleteCoinList(token);
16 | },
17 | findCoinRow: function(coinSymbol) {
18 | return Coins.findCoinRow(coinSymbol);
19 | }
20 | }
--------------------------------------------------------------------------------
/models/DiskStorage/Coin/util.js:
--------------------------------------------------------------------------------
1 | // pRoy24 tokenplex
2 |
3 | module.exports = {
4 | normalizeCoinSnapShotData: function(coinResponseData) {
5 | const ccMarketData = [coinResponseData.data.Data['AggregatedData']];
6 | const exchangeMarketData = coinResponseData.data.Data['Exchanges'];
7 |
8 | const coinTradeData = exchangeMarketData.concat(ccMarketData);
9 | let coinTradeNormalizedData = coinTradeData.map(function(tradeDataItem){
10 | return Object.assign(...Object.keys(tradeDataItem).map(function(keyItem){
11 | let obj = {};
12 | obj[keyItem.toLowerCase()] = tradeDataItem[keyItem];
13 | return obj;
14 | }))
15 | });
16 | return coinTradeNormalizedData;
17 | }
18 | }
--------------------------------------------------------------------------------
/logs/logger.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 |
3 | const winston = require('winston');
4 |
5 |
6 | const logger = winston.createLogger({
7 | level: 'info',
8 | format: winston.format.json(),
9 | transports: [
10 | //
11 | // - Write to all logs with level `info` and below to `combined.log`
12 | // - Write all logs error (and below) to `error.log`.
13 | //
14 | new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
15 | new winston.transports.File({ filename: 'logs/debug.log' })
16 | ]
17 | });
18 |
19 | //
20 | // If we're not in production then log to the `console` with the format:
21 | // `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
22 | //
23 | if (process.env.NODE_ENV !== 'production') {
24 | logger.add(new winston.transports.Console({
25 | format: winston.format.simple()
26 | }));
27 | }
28 |
29 | module.exports = logger;
--------------------------------------------------------------------------------
/routes/cron/index.js:
--------------------------------------------------------------------------------
1 |
2 | var express = require('express')
3 | , router = express.Router()
4 | , CronRoutes = require('./CronRoutes');
5 |
6 | /**
7 | * @api {get} /cron/query-daily-history-table Run timed query for coin day data and generate graph.
8 | * @apiName getCoinDayGraph
9 | * @apiGroup Cron Request
10 | * @apiParam {String} App Secret Key
11 | * @apiSuccess {String} Success Message String.
12 | * @apiError {String} Server error String.
13 | */
14 | router.route('/query-daily-history-table').get(CronRoutes.getCoinDayGraph);
15 |
16 | /**
17 | * @api {get} /cron/query-coin-list-table Start Coin List query and save coin ticker array to Redis server.
18 | * @apiName getCoinListAndMerge
19 | * @apiGroup Cron Request
20 | * @apiParam {String} App Secret Key
21 | * @apiSuccess {String} Success Message String.
22 | * @apiError {String} Server error String.
23 | */
24 | router.route('/query-coin-list-table').get(CronRoutes.getCoinListAndMerge);
25 |
26 | module.exports = router;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "TokenPlex",
3 | "version": "0.0.9",
4 | "private": false,
5 | "scripts": {
6 | "start": "node ./bin/www"
7 | },
8 | "url": "https://github.com/TokenPlex/tokenplex-core",
9 | "email": "pritam@tokenplex.io",
10 | "apidoc": {
11 | "title": "TokenPlex API Docs",
12 | "url": "https://api.tokenplex.io"
13 | },
14 | "dependencies": {
15 | "aws-sdk": "^2.156.0",
16 | "axios": "^0.16.2",
17 | "bluebird": "^3.5.1",
18 | "body-parser": "~1.17.1",
19 | "cassandra-driver": "^3.2.2",
20 | "ccxt": "^1.10.453",
21 | "cookie-parser": "~1.4.3",
22 | "cors": "^2.8.4",
23 | "cron": "^1.3.0",
24 | "debug": "^2.6.9",
25 | "dotenv": "^4.0.0",
26 | "express": "~4.15.2",
27 | "fs": "0.0.1-security",
28 | "hat": "0.0.3",
29 | "jade": "~1.11.0",
30 | "mongoose": "^4.13.0",
31 | "morgan": "~1.8.1",
32 | "redis": "^2.8.0",
33 | "request": "^2.81.0",
34 | "serve-favicon": "~2.4.2",
35 | "url": "^0.11.0",
36 | "winston": "^3.0.0-rc1"
37 | },
38 | "devDependencies": {
39 | "babel-preset-es2015": "^6.24.1",
40 | "chai": "^4.1.2",
41 | "mocha": "^4.1.0",
42 | "should": "^13.2.1",
43 | "supertest": "^3.0.0"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/models/APIStorage/Exchange/utils.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 | const ObjectUtils = require('../../../utils/ObjectUtils');
3 |
4 | module.exports = {
5 | mergeQuotesForBaseInExchange: function(exchangeActiveMarkets) {
6 | let exchangeMarketMap = {};
7 | exchangeActiveMarkets.forEach(function(activeMarket){
8 | if (ObjectUtils.isNonEmptyArray(exchangeMarketMap[activeMarket.base])) {
9 | if (exchangeMarketMap[activeMarket.base].indexOf(activeMarket.quote) === -1) {
10 | exchangeMarketMap[activeMarket.base].push(activeMarket.quote);
11 | }
12 | } else {
13 | exchangeMarketMap[activeMarket.base] = [activeMarket.quote];
14 | }
15 | });
16 | let normalizedExchangeList = [];
17 | Object.keys(exchangeMarketMap).forEach(function(base){
18 | normalizedExchangeList.push({
19 | base: base, quote: exchangeMarketMap[base],
20 | timestamp: exchangeActiveMarkets[0].timestamp, market: exchangeActiveMarkets[0].market
21 | })
22 | });
23 | return normalizedExchangeList;
24 | },
25 |
26 | groupCoinByMarketMaps: function(exchangeNormalizedArray) {
27 | let itemGroups = {};
28 | exchangeNormalizedArray.forEach(function(item){
29 | if (ObjectUtils.isNonEmptyObject(itemGroups[item.base])) {
30 | itemGroups[item.base][item.market] = item.quote;
31 | } else {
32 | itemGroups[item.base] = {};
33 | itemGroups[item.base][item.market] = item.quote;
34 | }
35 | });
36 | return itemGroups;
37 | }
38 | }
--------------------------------------------------------------------------------
/routes/schema/index.js:
--------------------------------------------------------------------------------
1 | // pRoy24 tokenplex
2 |
3 | var express = require('express')
4 | , router = express.Router()
5 | , CreateTable = require('./CreateTable')
6 | , ExchangeTables = require('./ExchangeTables');
7 |
8 | router.route('/create-coin-list-table').get(CreateTable.createCoinListTable);
9 |
10 | router.route('/create-coin-snapshot-table').get(CreateTable.createCoinSnapshotTable);
11 |
12 | router.route('/create-week-history-table').get(CreateTable.createCoinWeekHistoryTable);
13 |
14 | router.route('/create-all-time-history-table').get(CreateTable.createAllTimeHistoryTable);
15 |
16 | router.route('/create-day-history-table').get(CreateTable.createDayHistoryTable);
17 |
18 | router.route('/create-year-history-table').get(CreateTable.createCoinYearlyHistoryTable);
19 |
20 | router.route('/create-exchange-table').get(CreateTable.createExchangeTable);
21 |
22 | router.route('/create-social-table').get(CreateTable.createCoinSocialTable);
23 |
24 | router.route('/create-coin-exchange-table').get(ExchangeTables.createTokenExchangeListTable);
25 |
26 | router.route('/create-market-detail-table').get(ExchangeTables.createExchangeMarketDetailTable);
27 |
28 | router.route('/create-exchange-history-tables').get(ExchangeTables.createExchangeOLHCVTables);
29 | /**
30 | * @api {get} /create/create-all-tables Create all coin tables
31 | * @apiName createAllTables
32 | * @apiGroup Create Table
33 | * @apiParam {String} App Secret
34 | * @apiSuccess {String} API request submitted message string.
35 | * @apiError {String} Server error String.
36 | */
37 | router.route('/create-all-tables').get(CreateTable.createAllTables);
38 |
39 | module.exports = router;
--------------------------------------------------------------------------------
/utils/ObjectUtils.js:
--------------------------------------------------------------------------------
1 | // pRoy24 tokenplex
2 |
3 | module.exports = {
4 | isNonEmptyObject: function(obj) {
5 | if (typeof(obj) === "undefined" || obj === null || Object.keys(obj).length === 0) {
6 | return false;
7 | }
8 | return true;
9 | },
10 | isNonEmptyArray: function(arr) {
11 | if (typeof arr === "undefined" || arr === null || arr.length === 0) {
12 | return false;
13 | }
14 | return true;
15 | },
16 |
17 | isEmptyString: function(str) {
18 | if (typeof str === "undefined" || str === null || str.length === 0) {
19 | return true;
20 | }
21 | return false;
22 | },
23 |
24 | isNonEmptyString: function(str) {
25 | if (typeof str === "undefined" || str === null || str.length === 0) {
26 | return false;
27 | }
28 | return true;
29 | },
30 |
31 | writeFileToS3Location: function(coinSymbol, coinName) {
32 | var AWS = require('aws-sdk'),
33 | fs = require('fs');
34 | const coinURI = 'public/images/charts/' + coinName + '.png';
35 | // For dev purposes only
36 | // Add AWS Config
37 | // Read in the file, convert it to base64, store to S3
38 | fs.readFile(coinURI, function (err, data) {
39 | if (err) {
40 | // Silently absorb write error for now
41 | // TODO handle upload to S3 error
42 | console.log(err);
43 | }
44 | const base64data = new Buffer(data, 'binary');
45 | const s3 = new AWS.S3();
46 | s3.upload({
47 | Bucket: 'images.tokenplex.io',
48 | Key: coinSymbol+".png",
49 | Body: base64data,
50 | ACL: 'public-read'
51 | },function (resp) {
52 | console.log(arguments);
53 | console.log('Successfully uploaded package.');
54 | });
55 | });
56 | }
57 | }
--------------------------------------------------------------------------------
/models/GetCoinList.js:
--------------------------------------------------------------------------------
1 | var axios = require('axios');
2 | // call the GitHub API to fetch information about the user's repositories
3 |
4 | module.exports = {
5 | getCoinMarketCapCoinList: function()
6 | {
7 | const getCoinMarketCapListEndpoint = "https://api.coinmarketcap.com/v1/ticker";
8 | return axios.get(getCoinMarketCapListEndpoint);
9 | },
10 |
11 | getCryptoCompareCoinList: function () {
12 | const getCruptoCompreListEndpoint = "https://www.cryptocompare.com/api/data/coinlist/";
13 | return axios.get(getCruptoCompreListEndpoint);
14 | },
15 | getCoinDayHistogram: function(coinSymbol) {
16 | const histogramDataEndpoint = "https://min-api.cryptocompare.com/data/histohour?fsym="+coinSymbol+"&tsym=USD&limit=24&aggregate=1";
17 | return axios.get(histogramDataEndpoint);
18 | },
19 |
20 | getCoinSnapShot: function(coinSymbol) {
21 | const histogramDataEndpoint = "https://www.cryptocompare.com/api/data/coinsnapshot/?fsym="+coinSymbol+"&tsym=BTC";
22 | return axios.get(histogramDataEndpoint);
23 | },
24 |
25 | getCoinMinuteData(coinSymbol) {
26 | const histogramDataEndpoint = "https://min-api.cryptocompare.com/data/histominute?fsym="+coinSymbol+"&tsym=USD&&aggregate=3&e=CCCAGG";
27 | return axios.get(histogramDataEndpoint);
28 | },
29 | getCoinSocialData(coinID) {
30 | const histogramDataEndpoint = "https://www.cryptocompare.com/api/data/socialstats/?id="+coinID;
31 | return axios.get(histogramDataEndpoint);
32 | },
33 |
34 |
35 | getCoinDailyData() {
36 | const dailyHistogramEndpoint = "https://min-api.cryptocompare.com/data/histoday?fsym=BTC&tsym=USD&limit=60&aggregate=3&e=CCCAGG,";
37 |
38 | },
39 |
40 | getCoinDetailSnapshot(coinID) {
41 | const detailSnapshotEndpoint = "https://www.cryptocompare.com/api/data/coinsnapshotfullbyid/?id="+coinID;
42 | }
43 | }
--------------------------------------------------------------------------------
/routes/exchange/index.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 |
3 | var express = require('express')
4 | , router = express.Router()
5 | , Exchange = require('./ExchangeRoutes');
6 |
7 | /**
8 | * @api {get} /list Get list of all supported exchanges
9 | * @apiName listExchangeMetadata
10 | * @apiGroup Exchange
11 | * @apiSuccess {String} Exchange List Array
12 | * @apiError {String} Server error String.
13 | */
14 | router.route('/list').get(Exchange.listExchangeMetadata);
15 |
16 | router.route('/list-metadata').get(Exchange.listExchangeMetadata);
17 |
18 | /**
19 | * @api {get} /list-details Get list of details for each exchange keyed by market
20 | * @apiName listExchangeDetails
21 | * @apiGroup Exchange
22 | * @apiSuccess {String} Exchange List Array
23 | * @apiError {String} Server error String
24 | */
25 | router.route('/list-details').get(Exchange.listExchangeDetails);
26 |
27 | /**
28 | * @api {get} /list Get list markets for given exchange
29 | * @apiName getExchangeMarkets
30 | * @apiGroup Exchange
31 | * @apiSuccess {String} Market List Array keyed by Base token
32 | * @apiError {String} Server error String.
33 | */
34 | router.route('/:exchangeName/markets').get(Exchange.getExchangeMarkets);
35 |
36 | /**
37 | * @api {get} /list Get list of all supported exchanges
38 | * @apiName getExchangeOrderBook
39 | * @apiGroup Exchange
40 | * @apiSuccess {String} Exchange Order-book array
41 | * @apiError {String} Server error String.
42 | */
43 | router.route('/:exchangeName/order-book').get(Exchange.getExchangeOrderBook);
44 |
45 | /**
46 | * @api {get} /list Get list of all supported exchanges
47 | * @apiName getExchangeHistoryData
48 | * @apiGroup Exchange
49 | * @apiSuccess {String} Exchange History data array
50 | * @apiError {String} Server error String.
51 | */
52 | router.route('/:exchangeName/history-data').get(Exchange.getExchangeHistoryData);
53 |
54 | module.exports = router;
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | // pRoy24 tokenplex
2 |
3 | var express = require('express');
4 | var path = require('path');
5 | var favicon = require('serve-favicon');
6 | var logger = require('morgan');
7 | var cookieParser = require('cookie-parser');
8 | var bodyParser = require('body-parser');
9 |
10 | var index = require('./routes/index');
11 |
12 | var create_table = require('./routes/schema');
13 |
14 | const cron_route_api = require('./routes/cron');
15 | const coin_detail_api = require('./routes/coins');
16 | const exchange_api = require('./routes/exchange');
17 |
18 | var cors = require('cors')
19 |
20 | require('dotenv').config()
21 |
22 | var app = express();
23 | app.use(cors())
24 | app.use(express.static('public'))
25 | // view engine setup
26 | app.set('views', path.join(__dirname, 'views'));
27 | app.set('view engine', 'jade');
28 |
29 | // uncomment after placing your favicon in /public
30 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
31 | app.use(logger('dev'));
32 | app.use(bodyParser.json());
33 | app.use(bodyParser.urlencoded({ extended: false }));
34 | app.use(cookieParser());
35 | app.use(express.static(path.join(__dirname, 'public')));
36 |
37 | app.use('/', index);
38 | app.use('/coin', coin_detail_api);
39 | app.use('/create', create_table);
40 | app.use('/exchange', exchange_api);
41 | app.use('/cron', cron_route_api);
42 |
43 | // catch 404 and forward to error handler
44 | app.use(function(req, res, next) {
45 | var err = new Error('Not Found');
46 | err.status = 404;
47 | next(err);
48 | });
49 |
50 |
51 | // error handler
52 | app.use(function(err, req, res, next) {
53 | // set locals, only providing error in development
54 | res.locals.message = err.message;
55 | res.locals.error = req.app.get('env') === 'development' ? err : {};
56 |
57 | // render the error page
58 | res.status(err.status || 500);
59 | res.render('error');
60 | });
61 |
62 | module.exports = app;
63 |
--------------------------------------------------------------------------------
/models/DiskStorage/Exchange/ExchangeUpdate.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 |
3 |
4 | const cassandra = require('cassandra-driver');
5 | var Constants = require('../../../constants');
6 | const cassandraClient = new cassandra.Client({contactPoints: [Constants.CQL_API_SERVER]});
7 |
8 | module.exports = {
9 | saveMarketActiveExchanges: function(coinActiveExchangesArray) {
10 | let queries = [];
11 |
12 | coinActiveExchangesArray.forEach(function(item, idx){
13 | const placeHolders = "?, ?, ?, ?";
14 | let values = [];
15 | let keyItems = "";
16 | Object.keys(item).forEach(function(key, idx, arr){
17 | keyItems += key;
18 | if (idx !== arr.length -1) {
19 | keyItems += ", ";
20 | }
21 | values.push(item[key]);
22 | });
23 |
24 | const MARKET_LIST_TTL = 86400;
25 | const query = 'INSERT INTO tokenplex.markets_list (' + keyItems + ') VALUES (' + placeHolders +
26 | ') USING TTL ' + MARKET_LIST_TTL;
27 | queries.push({
28 | query: query,
29 | params: values
30 | });
31 | });
32 |
33 | cassandraClient.batch(queries, { prepare: true }, function(err, res){
34 | if (err) {
35 |
36 | }
37 | });
38 | },
39 |
40 | saveMarketListByCoin: function(coinToMarketExchangesMap) {
41 | Object.keys(coinToMarketExchangesMap).forEach(function(baseCoin){
42 | const placeHolders = "?, ?";
43 | let values = [baseCoin, coinToMarketExchangesMap[baseCoin]];
44 | let keyItems = ["base", "market"];
45 | const MARKET_LIST_TTL = 86400;
46 | const query = 'INSERT INTO tokenplex.markets_list (' + keyItems + ') VALUES (' + placeHolders +
47 | ') USING TTL ' + MARKET_LIST_TTL;
48 | cassandraClient.execute(query, values, {prepare: true}, function(err, res){
49 | if (err) {
50 | console.log(err);
51 | }
52 | });
53 | });
54 | }
55 | }
--------------------------------------------------------------------------------
/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 |
7 | var app = require('../app');
8 | var debug = require('debug')('server:server');
9 | var http = require('http');
10 |
11 | /**
12 | * Get port from environment and store in Express.
13 | */
14 |
15 | var port = normalizePort(process.env.PORT || '9000');
16 | app.set('port', port);
17 |
18 | /**
19 | * Create HTTP server.
20 | */
21 |
22 | var server = http.createServer(app);
23 |
24 | /**
25 | * Listen on provided port, on all network interfaces.
26 | */
27 |
28 | server.listen(port);
29 | server.on('error', onError);
30 | server.on('listening', onListening);
31 |
32 | /**
33 | * Normalize a port into a number, string, or false.
34 | */
35 |
36 | function normalizePort(val) {
37 | var port = parseInt(val, 10);
38 |
39 | if (isNaN(port)) {
40 | // named pipe
41 | return val;
42 | }
43 |
44 | if (port >= 0) {
45 | // port number
46 | return port;
47 | }
48 |
49 | return false;
50 | }
51 |
52 | /**
53 | * Event listener for HTTP server "error" event.
54 | */
55 |
56 | function onError(error) {
57 | if (error.syscall !== 'listen') {
58 | throw error;
59 | }
60 |
61 | var bind = typeof port === 'string'
62 | ? 'Pipe ' + port
63 | : 'Port ' + port;
64 |
65 | // handle specific listen errors with friendly messages
66 | switch (error.code) {
67 | case 'EACCES':
68 | console.error(bind + ' requires elevated privileges');
69 | process.exit(1);
70 | break;
71 | case 'EADDRINUSE':
72 | console.error(bind + ' is already in use');
73 | process.exit(1);
74 | break;
75 | default:
76 | throw error;
77 | }
78 | }
79 |
80 | /**
81 | * Event listener for HTTP server "listening" event.
82 | */
83 |
84 | function onListening() {
85 | var addr = server.address();
86 | var bind = typeof addr === 'string'
87 | ? 'pipe ' + addr
88 | : 'port ' + addr.port;
89 | debug('Listening on ' + bind);
90 | }
91 |
--------------------------------------------------------------------------------
/routes/exchange/ExchangeRoutes.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 |
3 | const ExchangeModels = require('../../models/ExchangeModels');
4 | const DiskStorage = require('../../models/DiskStorage');
5 | const MarketUtils = require('../../utils/MarketUtils');
6 | var ObjectUtils = require('../../utils/ObjectUtils');
7 | const url = require('url');
8 |
9 | module.exports = {
10 |
11 | listExchangeMetadata: function(req, res, next) {
12 | ExchangeModels.listExchangeMetadata().then(function(marketMetadata){
13 | let formattedMarketMetadata = marketMetadata.filter(function(item){
14 | return ObjectUtils.isNonEmptyObject(item);
15 | })
16 | res.send({data: formattedMarketMetadata});
17 | });
18 | },
19 |
20 | listExchangeDetails: function(req, res, next) {
21 | ExchangeModels.getExchangeDetailsList().then(function(exchangeDetailResponse){
22 | let filteredData = exchangeDetailResponse.filter(function(item){
23 | if (ObjectUtils.isNonEmptyObject(item) && ObjectUtils.isNonEmptyArray(item[Object.keys(item)[0]])) {
24 | return item;
25 | }
26 | });
27 | res.send({data: filteredData});
28 | }).catch(function(err){
29 | res.send({error: err});
30 | });
31 | },
32 |
33 | getExchangeMarkets: function(req, res, next) {
34 | const marketCode = req.url.split("/markets")[0].split("/")[1].toLowerCase();
35 | ExchangeModels.getMarketsForExchange(marketCode).then(function(marketListForExchangeResponse){
36 | res.send({data: marketListForExchangeResponse});
37 | });
38 | },
39 |
40 | // Get current open orders for exchange
41 | getExchangeOrderBook: function(req, res, next) {
42 | const marketCode = MarketUtils.fetchMarketCodeFromQuery(req.url);
43 | const base = req.query.base;
44 | const quote = req.query.quote;
45 | return ExchangeModels.getExchangeOrderbook(marketCode, base, quote).then(function(exchangeOrderbookResponse){
46 | res.send({data: exchangeOrderbookResponse});
47 | });
48 | },
49 |
50 | //History Data Controllers
51 | getExchangeHistoryData: function(req, res, next) {
52 | const marketCode = MarketUtils.fetchMarketCodeFromQuery(req.url);
53 | const base = req.query.base;
54 | const quote = req.query.quote;
55 | let sampleQuery = req.query.rate;
56 |
57 | let rate = "1h";
58 | if (sampleQuery === "min") {
59 | rate = "1m";
60 | } else if (sampleQuery === "hour") {
61 | rate = "1h";
62 | } else {
63 | rate = "1d";
64 | }
65 | ExchangeModels.getExchangeHistoryData(marketCode, base, quote, rate).then(function(exchangeModelResponse){
66 | res.send({data: exchangeModelResponse});
67 | });
68 | }
69 |
70 | }
--------------------------------------------------------------------------------
/test/exchange.test.js:
--------------------------------------------------------------------------------
1 | const request = require('supertest');
2 | const express = require('express');
3 | var chai = require('chai');
4 | var expect = chai.expect;
5 | const app = require('../app');
6 |
7 | // UNIT test begin
8 |
9 |
10 | describe('Todos list API Integration Tests', function() {
11 | describe('#GET / tasks', function() {
12 | it('should get all tasks', function(done) {
13 | request(app).get('/')
14 | .end(function(err, res) {
15 | expect(res.statusCode).to.equal(200);
16 |
17 | done();
18 | });
19 | });
20 | });
21 | });
22 |
23 | describe('List Exchange API', function(){
24 | describe('#GET /exchange/list', function() {
25 | it('should list all exchanges', function(done) {
26 | request(app).get('/exchange/list')
27 | .end(function(err, res){
28 | expect(res.statusCode).to.equal(200);
29 | done();
30 | });
31 | })
32 | });
33 | });
34 |
35 | describe('List Exchange Metadata API', function() {
36 | describe('#GET /exchange/list-metadata', function(){
37 | it('should list exchange metadata', function(done){
38 | request(app).get('/exchange/list-metadata')
39 | .end(function(err, res){
40 | expect(res.statusCode).to.equal(200);
41 | done();
42 | })
43 | })
44 | });
45 | });
46 |
47 | //
48 | describe('List Exchange Details', function() {
49 | describe('#GET list-details', function(){
50 | it('should list exchange details', function(done){
51 | request(app).get('/exchange/list-details')
52 | .end(function(err, res){
53 | expect(res.statusCode).to.equal(200);
54 | done();
55 | })
56 | })
57 | });
58 | });
59 |
60 | describe('List Exchange Metadata API', function() {
61 | describe('#GET /exchange/list-metadata', function(){
62 | it('should list exchange metadata', function(done){
63 | request(app).get('/exchange/binance/markets')
64 | .end(function(err, res){
65 | expect(res.statusCode).to.equal(200);
66 | done();
67 | })
68 | })
69 | });
70 | });
71 |
72 | describe('List Exchange Metadata API', function() {
73 | describe('#GET /exchange/list-metadata', function(){
74 | it('should list exchange metadata', function(done){
75 | request(app).get('/exchange/binance/order-book')
76 | .end(function(err, res){
77 | expect(res.statusCode).to.equal(200);
78 | done();
79 | })
80 | })
81 | });
82 | });
83 |
84 |
85 | describe('List Exchange Metadata API', function() {
86 | describe('#GET /exchange/list-metadata', function(){
87 | it('should list exchange metadata', function(done){
88 | request(app).get('/exchange/list-metadata')
89 | .end(function(err, res){
90 | expect(res.statusCode).to.equal(200);
91 | done();
92 | });
93 | });
94 | });
95 | });
96 |
97 |
--------------------------------------------------------------------------------
/models/CacheStorage/Coins.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 | //import {} from '../';
3 | var Constants = require('../../constants');
4 |
5 | var redis = require("redis"),
6 | client = redis.createClient({host: Constants.REDIS_API_SERVER});
7 | bluebird = require('bluebird');
8 | bluebird.promisifyAll(redis.RedisClient.prototype);
9 | bluebird.promisifyAll(redis.Multi.prototype);
10 | var ObjectUtils = require('../../utils/ObjectUtils');
11 | const logger = require('../../logs/logger');
12 |
13 | module.exports = {
14 | getCoinList: function(range) {
15 | return client.hgetallAsync("coins").then(function(response){
16 | let coinListArray = [];
17 | Object.keys(response).forEach(function(coinKey){
18 | if (response[coinKey]) {
19 | try {
20 | coinListArray.push(JSON.parse(response[coinKey]));
21 | } catch (e){
22 | // Log Error response
23 | }
24 | }
25 | });
26 | return {data: coinListArray};
27 | });
28 | },
29 |
30 | findCoinList: function() {
31 | client.hgetall("coins", function(err, res){
32 | if (err) {
33 | console.log(err);
34 | }
35 | res.send({"success": true});
36 | })
37 | },
38 |
39 | saveCoinList: function(coinList) {
40 | let arrayLog = [];
41 | arrayLog.push("coins");
42 | coinList.forEach(function(coinItem){
43 | if (ObjectUtils.isNonEmptyObject(coinItem)) {
44 | arrayLog.push(coinItem.symbol);
45 | arrayLog.push(coinItem);
46 | client.hset("coins", coinItem.symbol, JSON.stringify(coinItem), function (err, response) {
47 | if (err) {
48 | logger.log({"level": "error", "message": JSON.stringify(err)});
49 | }
50 | });
51 | }
52 | });
53 | return("started coin list scrape");
54 | },
55 |
56 | searchCoin: function(coinSearchString) {
57 | return client.hgetallAsync("coins").then(function(response){
58 | let coinListArray = [];
59 | Object.keys(response).forEach(function(coinKey){
60 | let coinResponse = JSON.parse(response[coinKey]);
61 | let coinFullName = coinResponse.fullname;
62 |
63 | if (new RegExp(coinSearchString.toLowerCase()).test(coinFullName.toLowerCase())) {
64 | coinListArray.push(coinResponse);
65 | }
66 | });
67 | return {data: coinListArray};
68 | });
69 | },
70 |
71 | deleteCoinList: function(token) {
72 | if (token === "proy24") {
73 | return client.hgetallAsync("coins").then(function(response) {
74 | if (ObjectUtils.isNonEmptyObject(response)) {
75 | Object.keys(response).forEach(function (coinKey) {
76 | client.hdel("coins", coinKey);
77 | });
78 | }
79 | });
80 | }
81 | },
82 |
83 | findCoinRow: function(coinSymbol){
84 | return client.hgetAsync("coins", coinSymbol).then(function(coinDataResponse){
85 | return coinDataResponse;
86 | })
87 | }
88 |
89 |
90 | }
--------------------------------------------------------------------------------
/models/DiskStorage/Coin/CoinList.js:
--------------------------------------------------------------------------------
1 | // pRoy24 tokenplex
2 |
3 | const cassandra = require('cassandra-driver');
4 | var Constants = require('../../../constants');
5 | const cassandraClient = new cassandra.Client({contactPoints: [Constants.CQL_API_SERVER]});
6 |
7 | module.exports = {
8 | getCoinItem: function(coinSymbol) {
9 | const query = "select * from tokenplex.coins where symbol = '"+coinSymbol+"'";
10 | return cassandraClient.execute(query).then(function(response){
11 | return {data: response.rows[0]};
12 | });
13 | },
14 | getCoinSnapShot: function(coinSymbol) {
15 | const query = "select * from tokenplex.coin_details where fromsymbol = '"+coinSymbol+"'";
16 | return cassandraClient.execute(query).then(function(response){
17 | if (response && response.rows.length > 0) {
18 | return response.rows;
19 | } else {}
20 | });
21 | },
22 | getCoinSocialData: function(coinID) {
23 | const query = "select * from tokenplex.coin_social where id = '"+coinID +"'";
24 | return cassandraClient.execute(query).then(function(response){
25 | return {data: response.rows};
26 | }).catch(function(err){
27 | return {error: err};
28 | });
29 | },
30 | getCoinDataArray: function() {
31 | const query = "select * from tokenplex.coins";
32 | return cassandraClient.execute(query).then(function(response){
33 | return {data: response.rows};
34 | });
35 | },
36 | getCoinDayHistoryData: function(coinSymbol) {
37 | const query = "SELECT * from tokenplex.daily_history_data where symbol='" + coinSymbol+"'";
38 | return cassandraClient.execute(query).then(function(response){
39 | return {data: response.rows};
40 | });
41 | },
42 |
43 | getCoinArbitrage: function(fromSymbol, toSymbol) {
44 |
45 | const query = "SELECT * from tokenplex.coin_details WHERE fromsymbol='"
46 | + fromSymbol + "' AND tosymbol='" + toSymbol + "'";
47 | return cassandraClient.execute(query).then(function(queryResponse) {
48 | return ({data: queryResponse});
49 | });
50 | },
51 |
52 | getCoinWeekHistoryData: function(coinSymbol, toSymbol) {
53 | const query = "SELECT * from tokenplex.week_history_data where symbol='" + coinSymbol + "' AND tosymbol='"
54 | + toSymbol + "'";
55 | return cassandraClient.execute(query).then(function(response){
56 | return {data: response};
57 | });
58 | },
59 |
60 | getCoinYearDayHistoryData: function(fromSymbol, toSymbol) {
61 | const query = "SELECT * from tokenplex.year_history_data where symbol='" + fromSymbol +"' AND tosymbol='"
62 | + toSymbol + "'";
63 | return cassandraClient.execute(query).then(function(queryResponse){
64 | return {data: queryResponse.rows};
65 | });
66 | },
67 |
68 | searchCoinByQuery: function(coinSearchQuery) {
69 | const query = "SELECT * from tokenplex.coins where fullname like '%" + coinSearchQuery.trim() + "%'";
70 | return cassandraClient.execute(query).then(function(response){
71 | return {data: response.rows};
72 | });
73 | }
74 | }
--------------------------------------------------------------------------------
/routes/cron/CronRoutes.js:
--------------------------------------------------------------------------------
1 |
2 | const DataFetchAPI = require('../../models/CoinModels');
3 |
4 | let express = require('express');
5 | var DBConnection = require('../../models/DBModel');
6 | const cassandra = require('cassandra-driver');
7 | const cassandraClient = new cassandra.Client({contactPoints: ['127.0.0.1']});
8 | var CronJob = require('cron').CronJob;
9 | let _ = require('lodash');
10 | const DiskStorage = require('../../models/DiskStorage'),
11 | APIStorage = require('../../models/APIStorage'),
12 | CacheStorage = require('../../models/CacheStorage'),
13 | CoinGraph = require('../../graph');
14 | ObjectUtils = require('../../utils/ObjectUtils');
15 | const logger = require('../../logs/logger');
16 |
17 | module.exports = {
18 | getCoinDayGraph: function(req, res, next) {
19 | startCoinGraphCron();
20 | // Chart every coin graph with a timeout of 4 seconds
21 | // Repeat logic every hour
22 | const FETCH_COIN_LOOP = 3600000;
23 | setInterval(function(){
24 | try {
25 | startCoinGraphCron();
26 | } catch(e) {
27 | logger.log({level: "error", message: "caught exception " + e + " retry in "+FETCH_COIN_LOOP+" ms"})
28 | }
29 | }, FETCH_COIN_LOOP);
30 | res.send({"data": "Started 24 History Data Request"});
31 | },
32 |
33 | getCoinListAndMerge: function(req, res, next) {
34 | setInterval(function(){
35 | logger.log('info', 'querying API for Coin List', {
36 | "timestamp": Date.now()
37 | });
38 | try {
39 | APIStorage.findCoinList().then(function (apiCoinSnapshotResponse) {
40 | const coinListResponse = apiCoinSnapshotResponse.data;
41 | if (ObjectUtils.isNonEmptyArray(coinListResponse)) {
42 | return CacheStorage.saveCoinList(coinListResponse);
43 | } else {
44 | return null;
45 | }
46 | });
47 | } catch(e){
48 | logger.log({"level": "error", "detail": "could not fetch coinlist at timestamp"+Date.now()})
49 | }
50 | }, 10000);
51 |
52 | res.send({"data": "Started Coin List Data Request"});
53 | }
54 | }
55 |
56 | function startCoinGraphCron() {
57 | DataFetchAPI.getCoinList(1000).then(function(coinDataList) {
58 | if (coinDataList.length > 0) {
59 | function createDailyServerSideGraphMap(n, fn, delay) {
60 | if (ObjectUtils.isNonEmptyObject(coinDataList[n])) {
61 | createCoinHistoryMap(coinDataList[n]);
62 | }
63 | if (n > 0) {
64 | fn();
65 | if (n > 1) {
66 | setTimeout(function() {
67 | createDailyServerSideGraphMap(n - 1, fn, delay);
68 | }, delay);
69 | }
70 | }
71 | }
72 | createDailyServerSideGraphMap(coinDataList.length, function() {
73 |
74 | }, 4000);
75 | }
76 | });
77 | }
78 |
79 | function createCoinHistoryMap(coinDetailObject) {
80 | let coinSymbol = coinDetailObject.symbol;
81 | APIStorage.findCoinDayHistoryData(coinSymbol).then(function (apiCoinDayHistoryDataResponse) {
82 | const coinDayHistoryResponse = apiCoinDayHistoryDataResponse.data.Data;
83 | if (ObjectUtils.isNonEmptyArray(coinDayHistoryResponse)) {
84 | const responseData = {};
85 | responseData[coinSymbol] = coinDayHistoryResponse;
86 | logger.log({"level": "info", "message": "charting coin history for " + coinSymbol});
87 | CoinGraph.chartCoinDailyHistoryGraph(responseData);
88 | } else {
89 | logger.log({"level": "error", "message": "could not fetch coin history data"})
90 | }
91 | });
92 |
93 | }
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/models/APIStorage/index.js:
--------------------------------------------------------------------------------
1 | // pRoy24 tokenplex
2 |
3 | const CoinList = require('./Coin/CoinList');
4 | const ExchangeList = require('./Exchange/ExchangeList');
5 | const MarketList = require('./Exchange/ExchangeList');
6 |
7 | module.exports = {
8 | findCoinSnapshot: function (coinSymbol, toSymbol) {
9 | return CoinList.getCoinSnapShot(coinSymbol, toSymbol);
10 | },
11 |
12 | findCoinSocialData: function(coinID) {
13 | return CoinList.getCoinSocialData(coinID);
14 | },
15 |
16 | findExchangeList: function() {
17 | return ExchangeList.getExchangeList();
18 | },
19 |
20 | findCoinList: function() {
21 | return CoinList.getCoinList();
22 | },
23 |
24 | findCoinDayHistoryData: function(coinSymbol) {
25 | return CoinList.getCoinDayHistoryData(coinSymbol);
26 | },
27 |
28 | findCoinWeekMinuteHistoryData: function(fromSymbol, toSymbol) {
29 | return CoinList.getCoinWeekHistoryData(fromSymbol, toSymbol);
30 | },
31 |
32 | findCoinYearDayHistoryData: function(fromSymbol, toSymbol) {
33 | return CoinList.getCoinYearHistoryData(fromSymbol, toSymbol);
34 | },
35 |
36 | searchCoin: function (coinSymbol) {
37 | return CoinList.searchCoin(coinSymbol);
38 | },
39 | findCoinRow: function(coinSymbol) {
40 | return CoinList.getCoinList().then(function(coinListResponse){
41 | let currentCoinItem = coinListResponse.data.find((coinItem) => (coinItem.symbol === coinSymbol));
42 | return ({data: currentCoinItem});
43 | });
44 | },
45 | getCoinHistoricalPrice: function(fromSymbol, exchange, timeStamp) {
46 | return CoinList.getCoinHistoricalPrice(fromSymbol, exchange, timeStamp);
47 | },
48 |
49 | getCoinArbitrage: function(fromSymbol, toSymbol) {
50 | return CoinList.getCoinArbitrage(fromSymbol, toSymbol);
51 | },
52 |
53 |
54 | // Market Operations
55 | getMarketList: function() {
56 | return MarketList.getMarketList();
57 | },
58 |
59 | getMarketActiveExchanges: function(currentExchange, exchangeName) {
60 | return MarketList.getMarketActiveExchanges(currentExchange, exchangeName);
61 | },
62 |
63 | getMarketExchangeByToken: function() {
64 | return MarketList.getMarketExchangeByToken();
65 | },
66 |
67 | getMarketTrades: function(baseToken, quoteToken) {
68 | return MarketList.getMarketTrades(baseToken, quoteToken);
69 | },
70 |
71 |
72 | getMarketsForExchange: function(exchangeName){
73 | return ExchangeList.getMarketList(exchangeName);
74 | },
75 |
76 | getExchangeWeekHistory: function(exchangeCode, baseToken, quoteToken) {
77 | return ExchangeList.getExchangeWeekHistory(exchangeCode, baseToken, quoteToken);
78 | },
79 |
80 | getExchangeOrderbook: function(exchangeCode, baseToken, quoteToken) {
81 | return ExchangeList.getExchangeOrderbook(exchangeCode, baseToken, quoteToken);
82 | },
83 |
84 | getExchangeDetails: function(exchangeName) {
85 | return ExchangeList.findExchangeDetails(exchangeName);
86 | },
87 |
88 | findExchangesForToken: function(baseToken) {
89 | return ExchangeList.findExchangesForToken(baseToken);
90 | },
91 |
92 | getMinutelySampledHistoryData: function(exchangeCode, baseToken, quoteToken) {
93 | return ExchangeList.getMinutelySampledHistoryData(exchangeCode, baseToken, quoteToken);
94 | },
95 |
96 | getDailySampledHistoryData: function(exchangeCode, baseToken, quoteToken) {
97 | return ExchangeList.getDailySampledHistoryData(exchangeCode, baseToken, quoteToken);
98 | },
99 |
100 | getSampledHistoryData: function(exchangeCode, baseToken, quoteToken, rate) {
101 | return ExchangeList.getSampledHistoryData(exchangeCode, baseToken, quoteToken, rate);
102 | }
103 |
104 |
105 | // findCoinArbitrageResponse: function(fromS)
106 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## What is TokenCaps
3 |
4 | TokenCaps is an express/node based framework to aggregate crypto-currency data from the web, normalize and cleanse it,
5 | and provide simple easy to digest endpoints.
6 |
7 | It implements the [ccxt](https://github.com/ccxt/ccxt) public library and provides a load-balancer and query-server over 80 supported exchanges.
8 |
9 | [](https://travis-ci.org/pRoy24/tokenplex)
10 |
11 |
12 | ## Installation
13 |
14 | ```
15 | npm install
16 | npm start
17 | ```
18 |
19 | To Create all tables
20 |
21 | ```
22 | GET /create/create-all-tables
23 | ```
24 |
25 | To start cron job for querying coin ticker data
26 |
27 | ```
28 | GET /cron/query-coin-list-table
29 | ```
30 |
31 | ### Optional
32 | For server side rendering of Graph data, you need to install [chartjs-node](https://github.com/vmpowerio/chartjs-node).
33 | It requires canvas.js and cairo to be pre-installed in your system.
34 |
35 | and then simply call this endpoint to start cron job for querying 24 hour data
36 | and server side graph rendering.
37 |
38 | ```
39 | GET /cron/query-daily-history-table
40 | ```
41 |
42 | You are now running an API load balancer for serving crypto-currency data.
43 |
44 | ## HA Deployment
45 | To run TokenPlex in a fault-tolerant manner, you should use a HA data cluster and run your API server behind a load balancer. Since NodeJS is essentially single-threaded, you should run the CRON proceess in a separate node/container so that normal requests do not get jammed up due to blocked aggregation queries.
46 | You can use [YugaByte](https://yugabyte.com) a polyglot database with unified CQL + Redis implemetation
47 |
48 |
49 |
50 | ## Hosted Solution
51 |
52 | ### User Interface Endpoint at https://tokenplex.io
53 |
54 | #### Exchange View
55 | This provides a snapshot view public API's of 79 exchanges. The API is implemented over the [ccxt](https://github.com/ccxt/ccxt) library.
56 |
57 |
58 |
59 | #### Token View
60 |
61 | This provides a list and details view of 2000 coins. The details join public aggregator API's and provide a normalized view.
62 | Data is refreshed every 10 seconds.
63 |
64 |
65 | #### Portfolio View
66 |
67 | This provides a portfolio management screen. Currently only aggregate exchanges supported. More exchange support coming soon.
68 |
69 |
70 |
71 | ### API Endpoint at https://api.tokenplex.io
72 |
73 | Tokenplex API is a hosted implementation of this repository. It currently uses an RF-1 Yugabyte node as it's database.
74 | API docs can be found [here](https://api.tokenplex.io/docs)
75 |
76 | For production applications, it is recommended that you run your own hosted solution.
77 |
78 |
79 | ## How It Works
80 |
81 | Ticker data is stored in a Redis Cache and is by default updated every 3 seconds.
82 | History Data requests have a TTL depending on sample rate of the request.
83 | For eg. minutely data table has a TTL of 30 seconds while daily sampled data tables have a TTL of 12 hours.
84 | Exchange-Markets table has a TTL of 6 Seconds.
85 | Coin-Detail tables have a TTL of 10 Seconds.
86 |
87 | TimeSeries metrics data and token details is stored in a CQL database and is updated
88 | on last request with a TTL strategy of 120 seconds.
89 |
90 | Additionally you can specify an S3 Image server location for server side rendering of graph images.
91 |
92 | You can run your own load balancer and application server on top of this architecture.
93 |
94 | ## Supported Exchanges
95 |
96 |
97 |
--------------------------------------------------------------------------------
/graph/CoinDailyHistoryGraph.js:
--------------------------------------------------------------------------------
1 | // pRoy24 tokenplex
2 |
3 | var moment = require('moment');
4 | var dotenv = require('dotenv').config()
5 | //const config = require('../constants/config');
6 |
7 | module.exports = {
8 | // Coin Daily History Graph
9 | chartDailyCoinHistory: function(responseData) {
10 | const coinSymbol = Object.keys(responseData)[0];
11 | const historyData = responseData[coinSymbol];
12 | const ChartjsNode = require('chartjs-node');
13 |
14 | if (historyData.length === 0) {
15 | console.log("Empty data response");
16 | }
17 |
18 | const historyDataMap = historyData.map((dataItem) => ({y: dataItem.high, x: dataItem.time}));
19 | const historyLabels = historyData.map((dataItem)=>moment(dataItem.time).format("hh:mm"));
20 |
21 | const chartOptions = {
22 | "type": "line",
23 | "data": {
24 | "labels": historyLabels,
25 | "datasets": [{
26 | "data": historyDataMap,
27 | "borderWidth": 2,
28 | backgroundColor: 'rgb(103,110,117)',
29 | borderColor: 'rgb(0,176,241)',
30 | }]
31 | },
32 | "options": {
33 | legend: {
34 | display: false
35 | },
36 | tooltips: {
37 | enabled: false
38 | },
39 | elements: { point: { radius: 0 } },
40 | "pointRadius": 0,
41 | "scales": {
42 | "yAxes": [{
43 | gridLines: {
44 | display:false
45 | },
46 | drawOnChartArea: false,
47 | "ticks": {
48 | fontColor: "#ECECEC",
49 | fontSize: 12,
50 | fontWeight: "bold",
51 | maxTicksLimit: 4,
52 | "beginAtZero": false
53 | }
54 | }],
55 | "xAxes": [{
56 | display: false,
57 | gridLines: {
58 | display:false
59 | },
60 | drawOnChartArea: false
61 | }]
62 | }
63 | }
64 | }
65 |
66 |
67 | var chartNode = new ChartjsNode(180, 90);
68 | return chartNode.drawChart(chartOptions)
69 | .then(() => {
70 | // chart is created
71 |
72 | // get image as png buffer
73 | return chartNode.getImageBuffer('image/png');
74 | })
75 | .then(buffer => {
76 | Array.isArray(buffer) // => true
77 | // as a stream
78 | return chartNode.getImageStream('image/png');
79 | })
80 | .then(streamResult => {
81 | // using the length property you can do things like
82 | // directly upload the image to s3 by using the
83 | // stream and length properties
84 | streamResult.stream // => Stream object
85 | streamResult.length // => Integer length of stream
86 |
87 | const filePath = 'public/images/charts/' + coinSymbol + '.png';
88 | return chartNode.writeImageToFile('image/png', filePath);
89 | })
90 | .then((file) => {
91 |
92 | // writeFileToS3Location(coinSymbol);
93 | // chart is now written to the file path
94 | // ./testimage.png
95 | writeFileToS3Location(coinSymbol);
96 | }).catch(function(err){
97 | console.log(err);
98 | return err;
99 | });
100 | },
101 |
102 | }
103 |
104 | function writeFileToS3Location(coinSymbol) {
105 | var AWS = require('aws-sdk'),
106 | fs = require('fs');
107 |
108 | const coinURI = 'public/images/charts/' + coinSymbol + '.png';
109 | // For dev purposes only
110 | AWS.config.update({ accessKeyId: process.env.AWS_ACCESS_KEY, secretAccessKey: process.env.AWS_SECRET_KEY});
111 | // Read in the file, convert it to base64, store to S3
112 | fs.readFile(coinURI, function (err, data) {
113 | if (err) {
114 | throw err;
115 | }
116 | var base64data = new Buffer(data, 'binary');
117 | var s3 = new AWS.S3();
118 | s3.upload({
119 | Bucket: 'images.tokenplex.io',
120 | Key: coinSymbol+".png",
121 | Body: base64data,
122 | ACL: 'public-read'
123 | },function (err, resp) {
124 | if (err) {
125 | console.log(err);
126 | }
127 | console.log('Successfully uploaded package.');
128 | });
129 | });
130 | }
--------------------------------------------------------------------------------
/routes/coins/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | proy24, TokenPlex
3 | */
4 |
5 |
6 | var express = require('express')
7 | , router = express.Router()
8 | , CoinRoutes = require('./CoinRoutes');
9 |
10 |
11 | /**
12 | * @api {get} /coin/coin-detail Get details of a token.
13 | * @apiName getCoinDetailSnapshot
14 | * @apiGroup Tokens
15 | * @apiParam {String} from_symbol symbol of the coin you wish to fetch
16 | * @apiParam {String} to_symbol=USD symbol of the pairing currency
17 | * @apiSuccess {Json} Coin detail object.
18 | * @apiError {String} Server error String.
19 | */
20 |
21 | router.route('/coin-detail').get(CoinRoutes.getCoinDetailSnapshot);
22 |
23 | /**
24 | * @api {get} /coin/coin-list Get list of tokens.
25 | * @apiName getCoinList
26 | * @apiGroup Tokens
27 | * @apiParam {Number} range Range of coins to get
28 | * @apiSuccess {Array} Coin List Array.
29 | * @apiError {String} Server error String.
30 | */
31 | router.route('/coin-list').get(CoinRoutes.getCoinList);
32 |
33 | /**
34 | * @api {get} /coin/search Search for a coin by name or symbol.
35 | * @apiName searchCoinByName
36 | * @apiGroup Tokens
37 | * @apiParam {String} coin_symbol Name of Symbol Regex Match for the coin
38 | * @apiSuccess {Array} Array of coins with regex approx match for given input.
39 | * @apiError {String} Server error String.
40 | */
41 | router.route('/search').get(CoinRoutes.searchCoinByName);
42 |
43 | /**
44 | * @api {get} /coin/coin-social Get Github, Reddit, Twitter, Facebook data points for a token.
45 | * @apiName getCoinSocialAndHeartbeat
46 | * @apiGroup Tokens
47 | * @apiParam {String} coin_symbol Symbol of the coin to get social data for.
48 | * @apiSuccess {Array} Array of coins with regex approx match for given input.
49 | * @apiError {String} Server error String.
50 | */
51 | router.route('/coin-social').get(CoinRoutes.getCoinSocialAndHeartbeat);
52 |
53 | /**
54 | * @api {get} /coin/coin-arbitrage Get coin detail and arbitrage data across exchanges.
55 | * @apiName getCoinArbitrage
56 | * @apiGroup Tokens
57 | * @apiParam {String} from_symbol symbol of the coin you wish to fetch
58 | * @apiParam {String} to_symbol=USD symbol of the pairing currency
59 | * @apiSuccess {Object} Data object containing exchange array and details object
60 | * @apiError {String} Server error String.
61 | */
62 | router.route('/coin-arbitrage').get(CoinRoutes.getCoinArbitrage);
63 |
64 | /**
65 | * @api {get} /coin/coin-day-history Get daily history of coin with given symbol
66 | * @apiName getCoinDailyData
67 | * @apiGroup Tokens
68 | * @apiParam {String} from_symbol symbol of the coin you wish to fetch
69 | * @apiParam {String} to_symbol=USD symbol of the pairing currency
70 | * @apiSuccess {Array} Coin Daily ticker data aggregated hourly
71 | * @apiError {String} Server error String.
72 | */
73 | router.route('/coin-day-history').get(CoinRoutes.getCoinDailyData);
74 |
75 | /**
76 | * @api {get} /coin/coin-week-history Get week history data for coin with given symbol aggregated per minute
77 | * @apiName getCoinWeekData
78 | * @apiGroup Tokens
79 | * @apiParam {String} from_symbol symbol of the coin you wish to fetch
80 | * @apiParam {String} to_symbol=USD symbol of the pairing currency
81 | * @apiSuccess {Array} Coin Weekly ticker data aggregated minutely.
82 | * @apiError {String} Server error String.
83 | */
84 | router.route('/coin-week-history').get(CoinRoutes.getCoinWeekData);
85 |
86 | /**
87 | * @api {get} /coin/coin-year-history Get yearly history data for coin with given symbol aggregated per day
88 | * @apiName getCoinYearData
89 | * @apiGroup Tokens
90 | * @apiParam {String} from_symbol symbol of the coin you wish to fetch
91 | * @apiParam {String} to_symbol=USD symbol of the pairing currency
92 | * @apiSuccess {Array} Coin Yearly ticker data aggregated daily.
93 | * @apiError {String} Server error String.
94 | */
95 | router.route('/coin-year-history').get(CoinRoutes.getCoinYearData);
96 |
97 | /**
98 | * @api {get} /coin/coin-trade-quote Get list of currencies the coin trades against.
99 | */
100 |
101 |
102 |
103 | router.route('/save-coin-list').get(CoinRoutes.saveCoinListToCache);
104 |
105 | router.route('/delete-coin-list').delete(CoinRoutes.deleteCoinList);
106 |
107 |
108 |
109 |
110 | // Legacy routes, to be deprecated
111 | router.route('/get-coin-detail').get(CoinRoutes.getCoinDetailSnapshot);
112 |
113 | router.route('/get-coin-list').get(CoinRoutes.getCoinList);
114 |
115 | router.route('/:coinID/exchanges').get(CoinRoutes.getCoinExchanges);
116 |
117 | module.exports = router;
--------------------------------------------------------------------------------
/routes/schema/ExchangeTables.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 |
3 | let express = require('express');
4 | var DBConnection = require('../../models/DBModel');
5 | const cassandra = require('cassandra-driver');
6 | var Constants = require('../../constants');
7 | const cassandraClient = new cassandra.Client({contactPoints: [Constants.CQL_API_SERVER]});
8 |
9 | const logger = require('../../logs/logger');
10 |
11 | module.exports = {
12 |
13 | // Create Exchange Market Table
14 | createTokenExchangeListTable: function(req, res, next) {
15 | DBConnection.getCassandraClientConnection()
16 | .then(function(){
17 | const createMarketsListQuery = "CREATE TABLE IF NOT EXISTS tokenplex.token_exchange_list" +
18 | "(market map>>," +
19 | "base varchar," +
20 | "timestamp timestamp," +
21 | "quoteId varchar, PRIMARY KEY (base))";
22 | const deleteQuery = "DROP TABLE IF EXISTS tokenplex.markets_list";
23 |
24 | return cassandraClient.execute(deleteQuery).then(function(delQueryResponse){
25 | return cassandraClient.execute(createMarketsListQuery).then(function(createTableResponse){
26 | logger.log({"level": "info", "message": "markets_list table created"});
27 | res.send({"data": createTableResponse});
28 | });
29 | }).catch(function(ex){
30 | logger.log({"level": "error", "message": ex});
31 | });
32 | });
33 | },
34 |
35 | createExchangeOLHCVTables: function(req, res, next) {
36 | const createDaySampleOLHCVQuery = "CREATE TABLE IF NOT EXISTS tokenplex.exchange_day_sample_history"+
37 | "(base varchar," +
38 | "quote varchar," +
39 | "detail text," +
40 | "exchange varchar,"+
41 | "PRIMARY KEY (exchange, base, quote))";
42 |
43 | const createHourSampleOLHCVQuery = "CREATE TABLE IF NOT EXISTS tokenplex.exchange_hour_sample_history"+
44 | "(base varchar," +
45 | "quote varchar," +
46 | "detail text," +
47 | "exchange varchar,"+
48 | "PRIMARY KEY (exchange, base, quote))";
49 |
50 | const createMinSampleOLHCVQuery = "CREATE TABLE IF NOT EXISTS tokenplex.exchange_min_sample_history"+
51 | "(base varchar," +
52 | "quote varchar," +
53 | "detail text," +
54 | "exchange varchar,"+
55 | "PRIMARY KEY (exchange, base, quote))";
56 |
57 | const deleteMinSampleQuery = "DROP TABLE IF EXISTS tokenplex.exchange_min_sample_history";
58 | const deleteHourSampleQuery = "DROP TABLE IF EXISTS tokenplex.exchange_hour_sample_history";
59 | const deleteDaySampleQuery = "DROP TABLE IF EXISTS tokenplex.exchange_day_sample_history";
60 |
61 | cassandraClient.execute(deleteMinSampleQuery).then(function(response){
62 | return response;
63 | }).then(function(response){
64 | cassandraClient.execute(deleteHourSampleQuery).then(function(response){
65 | return response;
66 | }).then(function(){
67 | cassandraClient.execute(deleteDaySampleQuery).then(function(response){
68 | return response;
69 | }).then(function(){
70 | cassandraClient.execute(createMinSampleOLHCVQuery).then(function(response){
71 | return response;
72 | }).then(function(response){
73 | cassandraClient.execute(createHourSampleOLHCVQuery).then(function(response){
74 | return response;
75 | }).then(function(){
76 | cassandraClient.execute(createDaySampleOLHCVQuery).then(function(response){
77 | return response;
78 | })
79 | })
80 | })
81 | })
82 | })
83 | }).catch(function(err){
84 | console.log(err);
85 | // logger.log({type: "error", detail: err});
86 | });
87 | res.send({data: "started create table request"})
88 | },
89 |
90 | createExchangeMarketDetailTable: function(req, res, next) {
91 | DBConnection.getCassandraClientConnection()
92 | .then(function(){
93 | const createMarketsListQuery = "CREATE TABLE IF NOT EXISTS tokenplex.market_detail" +
94 | "(exchange varchar," +
95 | "detail text," +
96 | "lastupdate timestamp," +
97 | " PRIMARY KEY (exchange))";
98 | const deleteQuery = "DROP TABLE IF EXISTS tokenplex.market_detail";
99 |
100 | return cassandraClient.execute(deleteQuery).then(function(delQueryResponse){
101 | return cassandraClient.execute(createMarketsListQuery).then(function(createTableResponse){
102 | logger.log({"level": "info", "message": "markets_detail table created"});
103 | res.send({"data": createTableResponse});
104 | });
105 | }).catch(function(ex){
106 | logger.log({"level": "error", "message": ex});
107 | });
108 | });
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/models/DiskStorage/Exchange/ExchangeSave.js:
--------------------------------------------------------------------------------
1 | // pRoy24 tokenplex
2 |
3 | const cassandra = require('cassandra-driver');
4 | var Constants = require('../../../constants');
5 | const cassandraClient = new cassandra.Client({contactPoints: [Constants.CQL_API_SERVER]});
6 |
7 | module.exports ={
8 | saveExchangeList: function(exchangeList) {
9 | exchangeList.forEach(function(exchangeItem){
10 | const placeHolders = "?,?,?,?,?,?,?";
11 | let values = [];
12 | let keys = "";
13 | Object.keys(exchangeItem).forEach(function(exchangeItemKey, exchangeIdx, arr){
14 | values.push(exchangeItem[exchangeItemKey]);
15 | keys += exchangeItemKey;
16 | if (exchangeIdx < arr.length - 1) {
17 | keys += ",";
18 | }
19 | });
20 | let ttl = 6000;
21 | const query = 'INSERT INTO tokenplex.exchanges (' + keyItems + ') VALUES (' + placeHolders + ') USING TTL ' +ttl;
22 | const params = values;
23 | cassandraClient.execute(query, params, {prepare: true}, function (err, response) {
24 | if (err) {
25 | console.log(err);
26 | }
27 | });
28 |
29 | })
30 | },
31 |
32 | saveCoinDayHistoryData: function (coinHistoryData, coinSymbol) {
33 | coinHistoryData.forEach(function(dataResponseItem){
34 | const placeHolders = "?, ?, ?, ?, ?, ?, ?, ?";
35 | let values = [coinSymbol];
36 | let keyItems = "symbol,";
37 | Object.keys(dataResponseItem).forEach(function(key, idx, arr){
38 | if (key !== "symbol") {
39 | keyItems += key;
40 | if (idx < arr.length - 1) {
41 | keyItems += ",";
42 | }
43 | values.push(dataResponseItem[key]);
44 | }
45 | });
46 |
47 | let ttl = 6000;
48 | const query = 'INSERT INTO tokenplex.daily_history_data (' + keyItems + ') VALUES (' + placeHolders + ') USING TTL ' +ttl;
49 | const params = values;
50 |
51 | cassandraClient.execute(query, params, {prepare: true}, function (err, response) {
52 | if (err) {
53 | console.log(err);
54 | }
55 | });
56 | });
57 | },
58 |
59 |
60 | saveExchangeDetails: function(exchangeData) {
61 | let currentTimeStamp = Date.now();
62 | const placeHolder = "? , ?, ?";
63 | const exchangeName = Object.keys(exchangeData)[0];
64 | const values = [exchangeName, JSON.stringify(exchangeData[exchangeName]), currentTimeStamp];
65 | const keyItems = "exchange, detail, lastupdate";
66 |
67 | let TTL = 86400;
68 | const query = "INSERT INTO tokenplex.market_detail ("+keyItems+") VALUES ("+ placeHolder +") USING TTL " + TTL;
69 | return cassandraClient.execute(query, values, {prepare: true}).then(function(response){
70 | return response;
71 | }).catch(function(err){
72 |
73 | })
74 | },
75 |
76 | saveMarketsForExchange: function(exchangeMarketData) {
77 | return new Promise(resolve => {
78 | setTimeout(() => {
79 | resolve({rows: []});
80 | }, 100);
81 | });
82 | },
83 |
84 | saveMinutelySampledHistoryData: function(exchangeCode, baseToken, quoteToken, apiWeekHistoryResponse) {
85 | const placeholders = "?, ?, ?, ?";
86 | let values = [exchangeCode, baseToken, quoteToken, JSON.stringify(apiWeekHistoryResponse)];
87 | const keyItems = "exchange, base, quote, detail";
88 | const TTL = 10;
89 |
90 | const query = "INSERT INTO tokenplex.exchange_min_sample_history ("+keyItems+") VALUES ("+ placeholders +") USING TTL " + TTL;
91 | return cassandraClient.execute(query, values, {prepare: true}, function (err, response) {
92 | return response;
93 | });
94 | },
95 |
96 | saveHourlySampledHistoryData: function(exchangeCode, baseToken, quoteToken, apiWeekHistoryResponse) {
97 | const placeholders = "?, ?, ?, ?";
98 | let values = [exchangeCode, baseToken, quoteToken, JSON.stringify(apiWeekHistoryResponse)];
99 | const keyItems = "exchange, base, quote, detail";
100 | const TTL = 30 * 60; // TTL 30 Mins
101 |
102 | const query = "INSERT INTO tokenplex.exchange_hour_sample_history ("+keyItems+") VALUES ("+ placeholders +") USING TTL " + TTL;
103 | return cassandraClient.execute(query, values, {prepare: true}, function (err, response) {
104 | return response;
105 | });
106 | },
107 |
108 | saveDailySampledHistoryData: function(exchangeCode, baseToken, quoteToken, apiWeekHistoryResponse) {
109 | const placeholders = "?, ?, ?, ?";
110 | let values = [exchangeCode, baseToken, quoteToken, JSON.stringify(apiWeekHistoryResponse)];
111 | const keyItems = "exchange, base, quote, detail";
112 | const TTL = 12 * 60 * 60; // TTL 12 Hours
113 |
114 | const query = "INSERT INTO tokenplex.exchange_day_sample_history ("+keyItems+") VALUES ("+ placeholders +") USING TTL " + TTL;
115 | return cassandraClient.execute(query, values, {prepare: true}, function (err, response) {
116 | return response;
117 | });
118 | },
119 | }
--------------------------------------------------------------------------------
/models/DiskStorage/Exchange/ExchangeList.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 |
3 | const cassandra = require('cassandra-driver');
4 | var Constants = require('../../../constants');
5 | var ObjectUtils = require('../../../utils/ObjectUtils');
6 | const cassandraClient = new cassandra.Client({contactPoints: [Constants.CQL_API_SERVER]});
7 |
8 |
9 | module.exports = {
10 | findExchangesForToken: function(baseToken) {
11 | const findMarketQuery = "SELECT * from tokenplex.token_exchange_list where base ='" + baseToken + "'";
12 | return cassandraClient.execute(findMarketQuery).then(function(response){
13 | return response;
14 | }).catch(function(err){
15 | return {data: [], "error": err};
16 | });
17 | },
18 |
19 | getMarketTradesForToken: function(baseToken, quoteToken) {
20 | return new Promise(resolve => {
21 | setTimeout(() => {
22 | let normalizedExchangeList = [];
23 | resolve(normalizedExchangeList);
24 | }, 100);
25 | });
26 | },
27 |
28 | getMarketsForExchange: function(exchangeName) {
29 | return new Promise(resolve => {
30 | setTimeout(() => {
31 | resolve({rows: []});
32 | }, 100);
33 | });
34 | },
35 |
36 | getExchangeWeekHistory: function(exchangeName, baseToken, quoteToken) {
37 | return new Promise(resolve => {
38 | setTimeout(() => {
39 | resolve({rows: []});
40 | }, 100);
41 | });
42 | },
43 |
44 | getExchangeOrderbook: function(exchangeCode, baseToken, quoteToken) {
45 | return new Promise(resolve => {
46 | setTimeout(() => {
47 | resolve({rows: []});
48 | }, 100);
49 | });
50 | },
51 |
52 | getExchangeDetails: function(exchangeCode) {
53 | const findMarketQuery = "SELECT * from tokenplex.market_detail where exchange ='" + exchangeCode + "'";
54 |
55 | return cassandraClient.execute(findMarketQuery).then(function(response){
56 | if (ObjectUtils.isNonEmptyArray(response.rows)) {
57 | let responseObject = {};
58 | responseObject[response.rows[0].exchange] = JSON.parse(response.rows[0].detail);
59 |
60 | return responseObject;
61 | }
62 | else {
63 | return {};
64 | }
65 | }).catch(function(err){
66 | return {};
67 | });
68 | },
69 |
70 |
71 |
72 | getMinutelySampledHistoryData: function(exchangeCode, baseToken, quoteToken) {
73 | const query = "SELECT * from tokenplex.exchange_min_sample_history where exchange='"+
74 | exchangeCode+"' AND base='"+baseToken+"' AND quote='"+quoteToken+"'";
75 | return cassandraClient.execute(query).then(function(response){
76 | if (response && response.rows.length > 0) {
77 | return JSON.parse(response.rows[0].detail);
78 | } else {
79 | return [];
80 | }
81 | }).catch(function(err){
82 | return [];
83 | });
84 | },
85 |
86 | getHourlySampledHistoryData: function(exchangeCode, baseToken, quoteToken) {
87 | const query = "SELECT * from tokenplex.exchange_hour_sample_history where exchange='"+
88 | exchangeCode+"' AND base='"+baseToken+"' AND quote='"+quoteToken+"'";
89 | return cassandraClient.execute(query).then(function(response){
90 | if (response && response.rows.length > 0) {
91 | return JSON.parse(response.rows[0].detail);
92 | } else {
93 | return [];
94 | }
95 | }).catch(function(err){
96 | return [];
97 | });
98 | },
99 |
100 | getDailySampledHistoryData: function(exchangeCode, baseToken, quoteToken) {
101 | const query = "SELECT * from tokenplex.exchange_day_sample_history where exchange='"+
102 | exchangeCode+"' AND base='"+baseToken+"' AND quote='"+quoteToken+"'";
103 | return cassandraClient.execute(query).then(function(response){
104 | if (response && response.rows.length > 0) {
105 | return JSON.parse(response.rows[0].detail);
106 | } else {
107 | return [];
108 | }
109 | }).catch(function(err){
110 | return [];
111 | });
112 | },
113 |
114 | hasExchangeDetailsExpired(exchangeCode) {
115 | const findMarketQuery = "SELECT lastupdate from tokenplex.market_detail where exchange ='" + exchangeCode + "'";
116 |
117 | return cassandraClient.execute(findMarketQuery).then(function(response){
118 | if (ObjectUtils.isNonEmptyArray(response.rows)) {
119 | const lastUpdateValueString = response.rows[0].lastupdate;
120 | const fromTime = new Date(lastUpdateValueString);
121 | const toTime = new Date();
122 |
123 | const differenceTravel = toTime.getTime() - fromTime.getTime();
124 | const secondsElapsed = Math.floor((differenceTravel) / (1000));
125 | if (secondsElapsed > 10) {
126 | return true;
127 | } else {
128 | return false;
129 | }
130 | }
131 | else {
132 | return true;
133 | }
134 | }).catch(function(err){
135 | return true;
136 | });
137 |
138 | }
139 |
140 |
141 | }
--------------------------------------------------------------------------------
/utils/MarketUtils.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 | var ObjectUtils = require('./ObjectUtils');
3 | var APIUtils = require('./APIUtils');
4 |
5 | module.exports = {
6 | getMarketMetadata: function (marketObject) {
7 | let locations = {};
8 | if (ObjectUtils.isNonEmptyArray(marketObject["countries"]) && Array.isArray(marketObject["countries"])) {
9 | marketObject["countries"].forEach(function (country) {
10 | let marketCountryCode = country.toLowerCase();
11 | if (marketCountryCode) {
12 | let countryLocation = APIUtils.countryCodeToLatLong[marketCountryCode];
13 | locations[marketCountryCode] = countryLocation;
14 | }
15 | })
16 | } else {
17 | let marketCountryCode = marketObject["countries"].toLowerCase();
18 | if (marketCountryCode) {
19 | let countryLocation = APIUtils.countryCodeToLatLong[marketCountryCode];
20 | locations[marketCountryCode] = countryLocation
21 | }
22 | }
23 | if ((marketObject.hasFetchTicker || marketObject.hasFetchTickers)
24 | && marketObject.hasFetchOrderBook) {
25 | return {
26 | "name": marketObject["name"], "locations": locations,
27 | "urls": marketObject["urls"], "currencies": marketObject["currencies"],
28 | "markets": marketObject["markets"], "info": marketObject
29 | }
30 | } else {
31 | return null;
32 | }
33 | },
34 |
35 |
36 | mergeQuotesForBaseInExchange: function(exchangeActiveMarkets) {
37 | let exchangeMarketMap = {};
38 | exchangeActiveMarkets.forEach(function(activeMarket){
39 | if (ObjectUtils.isNonEmptyArray(exchangeMarketMap[activeMarket.base])) {
40 | if (exchangeMarketMap[activeMarket.base].indexOf(activeMarket.quote) === -1) {
41 | exchangeMarketMap[activeMarket.base].push(activeMarket.quote);
42 | }
43 | } else {
44 | exchangeMarketMap[activeMarket.base] = [activeMarket.quote];
45 | }
46 | });
47 | let normalizedExchangeList = [];
48 | Object.keys(exchangeMarketMap).forEach(function(base){
49 | normalizedExchangeList.push({
50 | base: base, quote: exchangeMarketMap[base],
51 | timestamp: exchangeActiveMarkets[0].timestamp, market: exchangeActiveMarkets[0].market
52 | })
53 | });
54 | return normalizedExchangeList;
55 | },
56 |
57 | groupCoinByMarketMaps: function(exchangeNormalizedArray) {
58 | let itemGroups = {};
59 | exchangeNormalizedArray.forEach(function(item){
60 | if (ObjectUtils.isNonEmptyObject(itemGroups[item.base])) {
61 | itemGroups[item.base][item.market] = item.quote;
62 | } else {
63 | itemGroups[item.base] = {};
64 | itemGroups[item.base][item.market] = item.quote;
65 | }
66 | });
67 | return itemGroups;
68 | },
69 |
70 | normalizeMarketListForExchange(exchangeMarketListResponse) {
71 | let marketItem = {};
72 | marketItem["base"] = exchangeMarketListResponse.base;
73 | marketItem["quote"] = exchangeMarketListResponse.quote;
74 | marketItem["symbol"] = exchangeMarketListResponse.symbol;
75 | marketItem["lastupdate"] = exchangeMarketListResponse.timestamp;
76 | marketItem["high"] = exchangeMarketListResponse.high;
77 | marketItem["low"] = exchangeMarketListResponse.low;
78 | marketItem["bid"] = exchangeMarketListResponse.bid;
79 | marketItem["ask"] = exchangeMarketListResponse.ask;
80 | marketItem["vwap"] = exchangeMarketListResponse.vwap;
81 | marketItem["open"] = exchangeMarketListResponse.open;
82 | marketItem["close"] = exchangeMarketListResponse.close;
83 | marketItem["first"] = exchangeMarketListResponse.first;
84 | marketItem["last"] = exchangeMarketListResponse.last;
85 | marketItem["change"] = exchangeMarketListResponse.change;
86 | marketItem["percentage"] = exchangeMarketListResponse.percentage;
87 | marketItem["average"] = exchangeMarketListResponse.average;
88 | marketItem["baseVolume"] = exchangeMarketListResponse.baseVolume;
89 | marketItem['quoteVolume'] = exchangeMarketListResponse.quoteVolume;
90 | return marketItem;
91 |
92 | },
93 |
94 | fetchMarketCodeFromQuery(reqURI) {
95 | try {
96 | return reqURI.split("/markets")[0].split("/")[1].toLowerCase();
97 | } catch(e) {
98 | return null;
99 | }
100 | },
101 |
102 | getExchangeNameToDetailsArray(exchangeMarketListArray) {
103 | return exchangeMarketListArray.map(function(marketListItem, mIdx, mArr){
104 | let obj = {high: marketListItem.high, low: marketListItem.low, bid: marketListItem.bid,
105 | ask: marketListItem.ask, baseVolume: marketListItem.baseVolume ? marketListItem.baseVolume : 0,
106 | quote: marketListItem.quote, quoteVolume: marketListItem.quoteVolume, open: marketListItem.open,
107 | close: marketListItem.close, symbol: marketListItem.symbol, base: marketListItem.base
108 | };
109 | let finalObj = {};
110 | finalObj[marketListItem.base] = obj;
111 | return finalObj;
112 | }).filter(Boolean);
113 | },
114 |
115 | getExchangeListKeyedByBaseToken(exchangeDetailArray) {
116 | let exchangeKeyedArray = [];
117 | let returnListArray = exchangeDetailArray.map(function(exchangeItem) {
118 | let returnObj = {};
119 | let baseItem = exchangeItem.base;
120 | returnObj[baseItem] = exchangeItem;
121 | return returnObj;
122 | });
123 | return returnListArray;
124 | }
125 | }
--------------------------------------------------------------------------------
/models/APIStorage/Exchange/ExchangeConstants.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 | var ccxt = require ('ccxt')
3 |
4 | module.exports = {
5 | getExchangeInstance: function (exchangeName) {
6 | switch (exchangeName) {
7 | case "_1btcxe":
8 | return new ccxt._1btcxe();
9 | case "1btcxe":
10 | return new ccxt._1btcxe();
11 | case "acx":
12 | return new ccxt.acx();
13 | case "anxpro":
14 | return new ccxt.anxpro();
15 | case "binance":
16 | return new ccxt.binance();
17 | case "bit2c":
18 | return new ccxt.bit2c();
19 | case "bitbay":
20 | return new ccxt.bitbay();
21 | case "bitcoincoid":
22 | return new ccxt.bitcoincoid();
23 | case "bitfinex":
24 | return new ccxt.bitfinex();
25 | case "bitflyer":
26 | return new ccxt.bitflyer();
27 | case "bithumb":
28 | return new ccxt.bithumb();
29 | case "bitlish":
30 | return new ccxt.bitlish();
31 | case "bitmarket":
32 | return new ccxt.bitmarket();
33 | case "bitmex":
34 | return new ccxt.bitmex();
35 | case "bitso":
36 | return new ccxt.bitso();
37 | case "bitstamp":
38 | return new ccxt.bitstamp();
39 | case "bitstamp1":
40 | return new ccxt.bitstamp1();
41 | case "bittrex":
42 | return new ccxt.bittrex();
43 | case "bl3p":
44 | return new ccxt.bl3p();
45 |
46 | case "btcbox":
47 | return new ccxt.btcbox();
48 | case "btcchina":
49 | return new ccxt.btcchina();
50 |
51 | case "btcmarkets":
52 | return new ccxt.btcmarkets();
53 | case "btctradeua":
54 | return new ccxt.btctradeua();
55 | case "btcturk":
56 | return new ccxt.btcturk();
57 | case "btcx":
58 | return new ccxt.btcx();
59 | case "bxinth":
60 | return new ccxt.bxinth();
61 | case "ccex":
62 | return new ccxt.ccex();
63 | case "c-cex":
64 | return new ccxt.ccex();
65 | case "cex":
66 | return new ccxt.cex();
67 | case "chbtc":
68 | return new ccxt.chbtc();
69 | case "chilebit":
70 | return new ccxt.chilebit();
71 | case "coincheck":
72 | return new ccxt.coincheck();
73 | case "coinfloor":
74 | return new ccxt.coinfloor();
75 | case "coingi":
76 | return new ccxt.coingi();
77 | case "coinmarketcap":
78 | return new ccxt.coinmarketcap();
79 | case "coinmate":
80 | return new ccxt.coinmate();
81 | case "coinsecure":
82 | return new ccxt.coinsecure();
83 | case "coinspot":
84 | return new ccxt.coinspot();
85 | case "dsx":
86 | return new ccxt.dsx();
87 | case "exmo":
88 | return new ccxt.exmo();
89 | case "foxbit":
90 | return new ccxt.foxbit();
91 | case "fybse":
92 | return new ccxt.coinmate();
93 | case "fybsg":
94 | return new ccxt.fybsg();
95 | case "gatecoin":
96 | return new ccxt.gatecoin();
97 | case "gateio":
98 | return new ccxt.gateio();
99 | case "gdax":
100 | return new ccxt.gdax();
101 | case "gemini":
102 | return new ccxt.gemini();
103 | case "getbtc":
104 | return new ccxt.getbtc();
105 | case "hitbtc":
106 | return new ccxt.hitbtc();
107 | case "hitbtc2":
108 | return new ccxt.hitbtc2();
109 | case "huobi":
110 | return new ccxt.huobi();
111 | case "huobicny":
112 | return new ccxt.huobicny();
113 | case "huobipro":
114 | return new ccxt.huobipro();
115 | case "independentreserve":
116 | return new ccxt.independentreserve();
117 | case "itbit":
118 | return new ccxt.itbit();
119 | case "kraken":
120 | return new ccxt.kraken();
121 | case "kucoin":
122 | return new ccxt.kucoin();
123 | case "lakebtc":
124 | return new ccxt.lakebtc();
125 | case "liqui":
126 | return new ccxt.liqui();
127 | case "livecoin":
128 | return new ccxt.livecoin();
129 | case "luno":
130 | return new ccxt.luno();
131 | case "mercado":
132 | return new ccxt.mercado();
133 | case "mixcoins":
134 | return new ccxt.mixcoins();
135 | case "nova":
136 | return new ccxt.nova();
137 | case "okcoinusd":
138 | return new ccxt.okcoinusd();
139 | case "okex":
140 | return new ccxt.okex();
141 | case "paymium":
142 | return new ccxt.paymium();
143 | case "poloniex":
144 | return new ccxt.poloniex();
145 | case "qryptos":
146 | return new ccxt.qryptos();
147 | case "quadrigacx":
148 | return new ccxt.quadrigacx();
149 | case "quoine":
150 | return new ccxt.quoine();
151 | case "southxchange":
152 | return new ccxt.southxchange();
153 | case "surbitcoin":
154 | return new ccxt.surbitcoin();
155 | case "therock":
156 | return new ccxt.therock();
157 |
158 | case "urdubit":
159 | return new ccxt.urdubit();
160 | case "vaultoro":
161 | return new ccxt.vaultoro();
162 | case "vbtc":
163 | return new ccxt.vbtc();
164 | case "virwox":
165 | return new ccxt.virwox();
166 | case "wex":
167 | return new ccxt.wex();
168 | case "zaif":
169 | return new ccxt.zaif();
170 | case "zb":
171 | return new ccxt.zb();
172 | default:
173 | return null;
174 | }
175 | }
176 | }
177 |
178 |
179 |
--------------------------------------------------------------------------------
/models/APIStorage/Coin/CoinList.js:
--------------------------------------------------------------------------------
1 | // pRoy24 tokenplex
2 |
3 | var axios = require('axios');
4 |
5 | module.exports = {
6 | getCoinSnapShot: function(coinSymbol, toSymbol) {
7 | const coinSnapShotEndpoint = "https://www.cryptocompare.com/api/data/coinsnapshot/?fsym="+coinSymbol+"&tsym="+toSymbol;
8 | return axios.get(coinSnapShotEndpoint);
9 | },
10 |
11 | getCoinSocialData(coinID) {
12 | const histogramDataEndpoint = "https://www.cryptocompare.com/api/data/socialstats/?id="+coinID;
13 | return axios.get(histogramDataEndpoint);
14 | },
15 |
16 | getCoinMarketCapCoinList: function()
17 | {
18 | var getCoinMarketCapListEndpoint = "https://api.coinmarketcap.com/v1/ticker";
19 | return axios.get(getCoinMarketCapListEndpoint);
20 | },
21 |
22 | getCryptoCompareCoinList: function () {
23 | const getCruptoCompreListEndpoint = "https://min-api.cryptocompare.com/data/all/coinlist";
24 | return axios.get(getCruptoCompreListEndpoint);
25 | },
26 |
27 | getCoinDayHistogram: function(coinSymbol) {
28 | const histogramDataEndpoint = "https://min-api.cryptocompare.com/data/histohour?fsym="+coinSymbol+"&tsym=USD&limit=24&aggregate=1";
29 | return axios.get(histogramDataEndpoint);
30 | },
31 |
32 | getCoinArbitrage: function(fromSymbol, toSymbol) {
33 | const coinSnapShotEndpoint = "https://www.cryptocompare.com/api/data/coinsnapshot/?fsym="+fromSymbol+"&tsym="+toSymbol;
34 | return axios.get(coinSnapShotEndpoint);
35 | },
36 |
37 | getCoinList: function() {
38 | return getCoinMarketCapCoinList().then(function (coinMarketApiResponse) {
39 | coinMarketApiResponse = coinMarketApiResponse.data;
40 | return getCryptoCompareCoinList().then(function (coinListResponse) {
41 | coinListResponse = coinListResponse.data.Data;
42 | let coinListItems = Object.keys(coinListResponse).map(function (key) {
43 | return coinListResponse[key];
44 | });
45 | coinListItems = coinListItems.sort(function (a, b) {
46 | return Number(a.Rank) - Number(b.Rank);
47 | });
48 | let joinedCoinDataList = [];
49 |
50 | for (let a = 0; a < coinMarketApiResponse.length; a++) {
51 | for (let b = 0; b < coinListItems.length; b++) {
52 | if (coinMarketApiResponse[a].symbol.toLowerCase().trim() === coinListItems[b].Name.toLowerCase().trim() ||
53 | symbolNormalizerMatches(coinMarketApiResponse[a].symbol.toLowerCase(), coinListItems[b].Name.toLowerCase()) ||
54 | symbolNormalizerMatches(coinMarketApiResponse[a].name.toLowerCase(), coinListItems[b].CoinName.toLowerCase())) {
55 | // Normalize response to store in cassandra DB
56 | const normalizedCMApiResponse =
57 | Object.assign(...Object.keys(coinMarketApiResponse[a]).map(function (cmResponseKey) {
58 | let temp = {};
59 | if (cmResponseKey === "24h_volume_usd") {
60 | temp["daily_volume_usd"] = coinMarketApiResponse[a]["24h_volume_usd"];
61 | } else {
62 | temp[cmResponseKey.toLowerCase()] = coinMarketApiResponse[a][cmResponseKey];
63 | }
64 | return temp;
65 | }));
66 |
67 | const normalizedCLApiResponse =
68 | Object.assign(...Object.keys(coinListItems[b]).map(function (clResponseKey) {
69 | let temp = {};
70 | temp[clResponseKey.toLowerCase()] = coinListItems[b][clResponseKey];
71 | return temp;
72 | }));
73 |
74 | joinedCoinDataList.push(Object.assign({}, normalizedCMApiResponse, normalizedCLApiResponse));
75 | break;
76 | }
77 | }
78 | }
79 | return {data: joinedCoinDataList};
80 | });
81 | }).catch(function(err){
82 | return ({data: [], error: err});
83 | });
84 | },
85 |
86 | // Method returns 10080 minute data endpoints for past week
87 | getCoinWeekHistoryData: function(fromSymbol, toSymbol) {
88 | const dataEndpoint = "https://min-api.cryptocompare.com/data/histominute?fsym="+fromSymbol+"&tsym="
89 | +toSymbol+"&limit=500&aggregate=3&e=CCCAGG&allData=true";
90 | return axios.get(dataEndpoint);
91 | },
92 |
93 | getCoinYearHistoryData: function(fromSymbol, toSymbol) {
94 | const dataEndpoint = "https://min-api.cryptocompare.com/data/histoday?fsym="+fromSymbol+"&tsym="+toSymbol+"&limit=365&aggregate=1&e=CCCAGG";
95 | return axios.get(dataEndpoint);
96 | },
97 |
98 | // Method return 24 hourly data points for last day
99 | getCoinDayHistoryData: function(coinSymbol) {
100 | const histogramDataEndpoint = "https://min-api.cryptocompare.com/data/histohour?fsym="+coinSymbol+"&tsym=USD&limit=24&aggregate=1&e=CCCAGG";
101 | return axios.get(histogramDataEndpoint);
102 | },
103 |
104 | getCoinHistoricalPrice: function(fromSymbol, exchange, timeStamp) {
105 | let toSymbol = "BTC,USD,ETH";
106 | const priceHistoricalEndpoint = "https://min-api.cryptocompare.com/data/pricehistorical?fsym="
107 | + fromSymbol +"&tsyms=" + toSymbol + "&exchange=" + exchange + "&ts=" + timeStamp;
108 | return axios.get(priceHistoricalEndpoint);
109 | }
110 | }
111 |
112 | function getCoinMarketCapCoinList()
113 | {
114 | const getCoinMarketCapListEndpoint = "https://api.coinmarketcap.com/v1/ticker/?limit=500";
115 | return axios.get(getCoinMarketCapListEndpoint);
116 | }
117 |
118 | function getCryptoCompareCoinList () {
119 | const getCryptoCompreListEndpoint = "https://min-api.cryptocompare.com/data/all/coinlist";
120 | return axios.get(getCryptoCompreListEndpoint);
121 | }
122 |
123 | function symbolNormalizerMatches(symbol, name) {
124 | if (symbol === "bcc" && name === "bccoin") {
125 | return true;
126 | }
127 | }
--------------------------------------------------------------------------------
/routes/coins/CoinRoutes.js:
--------------------------------------------------------------------------------
1 | // pRoy24 tokenplex
2 |
3 | const ObjectUtils = require('../../utils/ObjectUtils');
4 | const DataFetchAPI = require('../../models/CoinModels');
5 | const DiskStorage = require("../../models/DiskStorage");
6 | const ExchangeModels = require('../../models/ExchangeModels');
7 |
8 | module.exports = {
9 | // Coin Detail Snapshot
10 | getCoinDetailSnapshot: function(req, res, next){
11 | let fromSymbol = req.query.from_symbol;
12 | let toSymbol = req.query.to_symbol;
13 | let coinSymbol = req.query.coin_symbol;
14 | if (ObjectUtils.isEmptyString(fromSymbol) && !ObjectUtils.isEmptyString(coinSymbol)) {
15 | fromSymbol = coinSymbol;
16 | }
17 | if (ObjectUtils.isEmptyString(toSymbol)) {
18 | toSymbol = "USD";
19 | }
20 | if (ObjectUtils.isEmptyString(fromSymbol)) {
21 | res.send ({error: "coin symbol must be specified"});
22 | } else {
23 | DataFetchAPI.getCoinRow(fromSymbol).then(function(coinRowResponse) {
24 | let responseData = {};
25 | responseData[coinRowResponse.data.symbol] = {
26 | "detail": coinRowResponse.data
27 | };
28 | const coinID = coinRowResponse.data.id;
29 | const coinSymbol = coinRowResponse.data.symbol;
30 |
31 | function getCoinObject(objectType) {
32 | if (objectType === "coinSnapshot") {
33 | return DataFetchAPI.getCoinSnapshot(fromSymbol, toSymbol);
34 | }
35 | if (objectType === "coinSocial") {
36 | return DataFetchAPI.getCoinSocialData(coinID);
37 | }
38 | }
39 | function callback(responseData) {
40 | res.send({data: responseData});
41 | }
42 |
43 | const coinDetailsObjects = ["coinSnapshot", "coinSocial"];
44 | let counter = 0;
45 | coinDetailsObjects.forEach((item, index, array) => {
46 | getCoinObject(item).then(function (dataResponse) {
47 | counter++;
48 | responseData[coinSymbol][item] = dataResponse;
49 | if (counter === array.length) {
50 | callback(responseData);
51 | }
52 | });
53 | });
54 | }).catch(function (err) {
55 | res.send({"error": err});
56 | })
57 | }
58 | },
59 |
60 | // Coin List
61 | getCoinList: function(req, res, next) {
62 | let rangeRequest = 100;
63 | if (req.query.range) {
64 | rangeRequest = req.query.range * 100 ;
65 | }
66 | function promiseFullfilled(responseData) {
67 | res.send({data: responseData});
68 | }
69 | DataFetchAPI.getCoinList(rangeRequest).then(function(coinListResponse){
70 | let responseData = coinListResponse.filter(function(item){
71 | return ((item.rank <= rangeRequest) && (item.rank >= rangeRequest - 100));
72 | }).sort(function(a,b){
73 | return a.rank - b.rank;
74 | });
75 | promiseFullfilled(responseData);
76 | });
77 | },
78 |
79 | getCoinDailyData(req, res, next) {
80 | let coinSymbol = req.query.coin_symbol;
81 | DataFetchAPI.getDailyHistoryData(coinSymbol).then(function (historyDataResponse) {
82 | return {data: historyDataResponse}
83 | });
84 | },
85 |
86 | getCoinWeekData(req, res, next) {
87 | let fromSymbol = req.query.from_symbol;
88 | let toSymbol = req.query.to_symbol;
89 | DataFetchAPI.getWeekMinuteHistoryData(fromSymbol, toSymbol).then(function (historyDataResponse) {
90 | res.send({data: historyDataResponse});
91 | });
92 | },
93 |
94 | getCoinYearData(req, res, next) {
95 | let fromSymbol = req.query.from_symbol;
96 | let toSymbol = req.query.to_symbol;
97 | if (ObjectUtils.isEmptyString(toSymbol)) {
98 | toSymbol = "USD";
99 | }
100 |
101 | DataFetchAPI.getCoinYearHistoryData(fromSymbol, toSymbol).then(function(coinHistoryDataResponse){
102 | res.send({data: coinHistoryDataResponse});
103 | });
104 | },
105 |
106 | searchCoinByName(req, res, next) {
107 | let coinNameString = req.query.coin_symbol;
108 | DataFetchAPI.findCoinByName(coinNameString).then(function(coinDetailResponse){
109 | res.send(coinDetailResponse);
110 | });
111 | },
112 |
113 | getCoinDetail(req, res, next) {
114 | let coinSymbol = req.query.coinSymbol;
115 | DataFetchAPI.getCoinSnapshot(coinSymbol);
116 | },
117 |
118 | saveCoinListToCache(req, res, next) {
119 | DataFetchAPI.saveCoinListToCache();
120 | },
121 |
122 | fetchCoinListFromCache(req, res, next) {
123 | DataFetchAPI.getCoinListFromCache();
124 | },
125 |
126 | deleteCoinList(req, res, next) {
127 | let token = req.query.token;
128 | DataFetchAPI.deleteCoinList(token);
129 | res.send({"data": "deleting data now"});
130 | },
131 |
132 | getCoinArbitrage(req, res, next) {
133 | const toSymbol = req.query.to_symbol;
134 | let fromSymbol = req.query.from_symbol;
135 | if (ObjectUtils.isEmptyString(fromSymbol)) {
136 | fromSymbol = "USD";
137 | }
138 | // Search is slow currently, need to add sorted set removing coinList.get for now
139 | DataFetchAPI.getCoinArbitrage(fromSymbol, toSymbol).then(function (coinArbitrageResponse) {
140 | res.send({data: {exchange: coinArbitrageResponse}});
141 | });
142 | },
143 |
144 | getCoinSocialAndHeartbeat(req, res, next) {
145 | const fromSymbol = req.query.coin_symbol;
146 |
147 | if (ObjectUtils.isEmptyString(fromSymbol)) {
148 | res.send ({error: "coin symbol must be specified"});
149 | } else {
150 | DataFetchAPI.getCoinRow(fromSymbol).then(function(coinRowResponse) {
151 | const coinID = coinRowResponse.data.id;
152 | return DataFetchAPI.getCoinSocialData(coinID).then(function(coinSocialResponse){
153 | res.send({data: coinSocialResponse});
154 | });
155 |
156 | }).catch(function (err) {
157 | res.send({"error": err});
158 | })
159 | }
160 | },
161 |
162 | getCoinExchanges(req, res, next) {
163 | let coinID = req.url.split("/coin")[0].split("/")[1].toLowerCase();
164 | ExchangeModels.getMarketsForToken(coinID).then(function(coinResponse){
165 | res.send({data: coinResponse});
166 | });
167 |
168 | }
169 | }
--------------------------------------------------------------------------------
/models/DiskStorage/index.js:
--------------------------------------------------------------------------------
1 | const CoinList = require('./Coin/CoinList');
2 | const CoinUpdate = require('./Coin/CoinUpdate');
3 | const ExchangeSave = require('./Exchange/ExchangeSave');
4 | const ExchangeList = require('./Exchange/ExchangeList');
5 |
6 | const MarketUpdate = require('./Exchange/ExchangeUpdate');
7 | const MarketList = require('./Exchange/ExchangeList');
8 |
9 | module.exports = {
10 | findCoinRow: function(coinSymbol) {
11 | return CoinList.getCoinItem(coinSymbol);
12 | },
13 |
14 | findCoinSnapshot: function(coinSymbol) {
15 | return CoinList.getCoinSnapShot(coinSymbol);
16 | },
17 |
18 | findCoinSocialData: function (coinID) {
19 | return CoinList.getCoinSocialData(coinID);
20 | },
21 |
22 | findCoinList: function(rangeRequest) {
23 | return CoinList.getCoinDataArray(rangeRequest)
24 | },
25 |
26 | findCoinDayHistoryData: function(coinSymbol) {
27 | return CoinList.getCoinDayHistoryData(coinSymbol);
28 | },
29 |
30 | findCoinWeekMinuteHistoryData: function(fromSymbol, toSymbol) {
31 | return CoinList.getCoinWeekHistoryData(fromSymbol, toSymbol);
32 | },
33 |
34 | findCoinYearDayHistoryData: function(fromSymbol, toSymbol) {
35 | return CoinList.getCoinYearDayHistoryData(fromSymbol, toSymbol);
36 | },
37 |
38 | findExchangeList: function() {
39 | return ExchangeList.getExchangeList();
40 | },
41 |
42 | saveCoinSnapshot: function (coinDetailData) {
43 | return CoinUpdate.saveCoinSnapshot(coinDetailData);
44 | },
45 | // Extra details about coin with longer TTL
46 | saveCoinExtraDetails: function(coinDetailData) {
47 | return CoinUpdate.saveCoinExtraDetails(coinDetailData);
48 | },
49 |
50 | saveCoinSocialData: function(coinID, coinSocialData) {
51 | return CoinUpdate.saveCoinSocialData(coinID, coinSocialData);
52 | },
53 |
54 | saveCoinListData: function(coinListData) {
55 | return CoinUpdate.saveCoinListData(coinListData);
56 | },
57 |
58 | saveCoinDayHistoryData: function(coinDayHistoryData) {
59 | return CoinUpdate.saveCoinDayHistoryData(coinDayHistoryData);
60 | },
61 |
62 | saveCoinWeekMinuteHistoryData: function(coinWeekHistoryData, toSymbol) {
63 | return CoinUpdate.saveCoinWeekMinuteHistoryData(coinWeekHistoryData, toSymbol);
64 | },
65 |
66 | saveCoinYearDayHistoryData: function(coinYearHistoryDataObject, toSymbol) {
67 | return CoinUpdate.saveCoinYearDayHistoryData(coinYearHistoryDataObject, toSymbol);
68 | },
69 |
70 | saveExchangeList: function(exchangeListData) {
71 | return ExchangeSave.saveExchangeList(exchangeListData);
72 | },
73 |
74 |
75 | // Search Utils
76 | searchCoin: function(coinString) {
77 | return CoinList.searchCoinByQuery(coinString);
78 | },
79 |
80 | deleteCoinDayHistoryData(coinSymbol) {
81 | return CoinUpdate.deleteCoinDayHistoryData(coinSymbol)
82 | },
83 |
84 | getCoinArbitrage(fromSymbol, toSymbol) {
85 | return CoinList.getCoinArbitrage(fromSymbol, toSymbol);
86 | },
87 |
88 | findRedisCoinList() {
89 |
90 | },
91 |
92 | saveCoinArbitrage(coinDataArray) {
93 | return CoinUpdate.saveCoinArbitrage(coinDataArray);
94 | },
95 |
96 | saveMarketList(marketListArray) {
97 |
98 | },
99 |
100 | saveMarketActiveExchanges(marketListInExchange) {
101 | MarketUpdate.saveMarketActiveExchanges(marketListInExchange);
102 | },
103 |
104 | findExchangesForToken(baseToken) {
105 | return MarketList.findExchangesForToken(baseToken);
106 | },
107 |
108 | createMarketListByToken(marketListResponse) {
109 | return MarketUpdate.saveMarketListByCoin(marketListResponse);
110 | },
111 |
112 | getMarketTrades(baseToken) {
113 | return MarketList.getMarketTradesForToken(baseToken);
114 | },
115 |
116 |
117 |
118 | getMarketsForExchange(exchangeName) {
119 | return ExchangeList.getMarketsForExchange(exchangeName);
120 | },
121 |
122 | saveExchangeMarketList(exchangeList) {
123 |
124 | },
125 |
126 | getExchangeWeekHistory(exchangeName, baseToken, quoteToken) {
127 | return ExchangeList.getExchangeWeekHistory(exchangeName, baseToken, quoteToken);
128 | },
129 |
130 | getExchangeMonthHistory() {
131 | return ExchangeList.getExchangeMonthHistory();
132 | },
133 |
134 | getExchangeYearHistory() {
135 | return ExchangeList.getExchangeYearHistory();
136 | },
137 |
138 | getExchangeOrderbook(exchangeCode, baseToken, quoteToken) {
139 | return ExchangeList.getExchangeOrderbook(exchangeCode, baseToken, quoteToken);
140 | },
141 |
142 |
143 | getExchangeDetails(exchangeName) {
144 | return ExchangeList.getExchangeDetails(exchangeName);
145 | },
146 |
147 |
148 | saveExchangeDetails(exchangeDetails) {
149 | return ExchangeSave.saveExchangeDetails(exchangeDetails);
150 | },
151 |
152 | getDailySampledHistoryData(exchangeCode, baseToken, quoteToken) {
153 | return ExchangeList.getDailySampledHistoryData(exchangeCode, baseToken, quoteToken);
154 | },
155 |
156 | getHourlySampledHistoryData(exchangeCode, baseToken, quoteToken) {
157 | return ExchangeList.getHourlySampledHistoryData(exchangeCode, baseToken, quoteToken);
158 | },
159 |
160 | getMinutelySampledHistoryData(exchangeCode, baseToken, quoteToken) {
161 | return ExchangeList.getMinutelySampledHistoryData(exchangeCode, baseToken, quoteToken);
162 | },
163 |
164 | saveMarketsForExchange(exchangeMarketDetails) {
165 | return ExchangeSave.saveMarketsForExchange(exchangeMarketDetails);
166 | },
167 |
168 | hasExchangeDetailsExpired(exchangeName) {
169 | return ExchangeList.hasExchangeDetailsExpired(exchangeName);
170 | },
171 |
172 | saveMinutelySampledHistoryData(exchangeCode, baseToken, quoteToken, apiWeekHistoryResponse) {
173 | return ExchangeSave.saveMinutelySampledHistoryData(exchangeCode, baseToken, quoteToken, apiWeekHistoryResponse);
174 | },
175 |
176 | saveHourlySampledHistoryData(exchangeCode, baseToken, quoteToken, apiWeekHistoryResponse) {
177 | return ExchangeSave.saveHourlySampledHistoryData(exchangeCode, baseToken, quoteToken, apiWeekHistoryResponse);
178 | },
179 |
180 | saveDailySampledHistoryData(exchangeCode, baseToken, quoteToken, apiWeekHistoryResponse) {
181 | return ExchangeSave.saveDailySampledHistoryData(exchangeCode, baseToken, quoteToken, apiWeekHistoryResponse);
182 | }
183 |
184 |
185 |
186 | }
--------------------------------------------------------------------------------
/utils/APIUtils.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 |
3 | module.exports = {
4 | countryCodeToLatLong:
5 | {"ad":{"lat":"42.5000","long":"1.5000"},"ae":{"lat":"24.0000","long":"54.0000"},"af":{"lat":"33.0000","long":"65.0000"},"ag":{"lat":"17.0500","long":"-61.8000"},"ai":{"lat":"18.2500","long":"-63.1667"},"al":{"lat":"41.0000","long":"20.0000"},"am":{"lat":"40.0000","long":"45.0000"},"an":{"lat":"12.2500","long":"-68.7500"},"ao":{"lat":"-12.5000","long":"18.5000"},"ap":{"lat":"35.0000","long":"105.0000"},"aq":{"lat":"-90.0000","long":"0.0000"},"ar":{"lat":"-34.0000","long":"-64.0000"},"as":{"lat":"-14.3333","long":"-170.0000"},"at":{"lat":"47.3333","long":"13.3333"},"au":{"lat":"-27.0000","long":"133.0000"},"aw":{"lat":"12.5000","long":"-69.9667"},"az":{"lat":"40.5000","long":"47.5000"},"ba":{"lat":"44.0000","long":"18.0000"},"bb":{"lat":"13.1667","long":"-59.5333"},"bd":{"lat":"24.0000","long":"90.0000"},"be":{"lat":"50.8333","long":"4.0000"},"bf":{"lat":"13.0000","long":"-2.0000"},"bg":{"lat":"43.0000","long":"25.0000"},"bh":{"lat":"26.0000","long":"50.5500"},"bi":{"lat":"-3.5000","long":"30.0000"},"bj":{"lat":"9.5000","long":"2.2500"},"bm":{"lat":"32.3333","long":"-64.7500"},"bn":{"lat":"4.5000","long":"114.6667"},"bo":{"lat":"-17.0000","long":"-65.0000"},"br":{"lat":"-10.0000","long":"-55.0000"},"bs":{"lat":"24.2500","long":"-76.0000"},"bt":{"lat":"27.5000","long":"90.5000"},"bv":{"lat":"-54.4333","long":"3.4000"},"bw":{"lat":"-22.0000","long":"24.0000"},"by":{"lat":"53.0000","long":"28.0000"},"bz":{"lat":"17.2500","long":"-88.7500"},"ca":{"lat":"60.0000","long":"-95.0000"},"cc":{"lat":"-12.5000","long":"96.8333"},"cd":{"lat":"0.0000","long":"25.0000"},"cf":{"lat":"7.0000","long":"21.0000"},"cg":{"lat":"-1.0000","long":"15.0000"},"ch":{"lat":"47.0000","long":"8.0000"},"ci":{"lat":"8.0000","long":"-5.0000"},"ck":{"lat":"-21.2333","long":"-159.7667"},"cl":{"lat":"-30.0000","long":"-71.0000"},"cm":{"lat":"6.0000","long":"12.0000"},"cn":{"lat":"35.0000","long":"105.0000"},"co":{"lat":"4.0000","long":"-72.0000"},"cr":{"lat":"10.0000","long":"-84.0000"},"cu":{"lat":"21.5000","long":"-80.0000"},"cv":{"lat":"16.0000","long":"-24.0000"},"cx":{"lat":"-10.5000","long":"105.6667"},"cy":{"lat":"35.0000","long":"33.0000"},"cz":{"lat":"49.7500","long":"15.5000"},"de":{"lat":"51.0000","long":"9.0000"},"dj":{"lat":"11.5000","long":"43.0000"},"dk":{"lat":"56.0000","long":"10.0000"},"dm":{"lat":"15.4167","long":"-61.3333"},"do":{"lat":"19.0000","long":"-70.6667"},"dz":{"lat":"28.0000","long":"3.0000"},"ec":{"lat":"-2.0000","long":"-77.5000"},"ee":{"lat":"59.0000","long":"26.0000"},"eg":{"lat":"27.0000","long":"30.0000"},"eh":{"lat":"24.5000","long":"-13.0000"},"er":{"lat":"15.0000","long":"39.0000"},"es":{"lat":"40.0000","long":"-4.0000"},"et":{"lat":"8.0000","long":"38.0000"},"eu":{"lat":"47.0000","long":"8.0000"},"fi":{"lat":"64.0000","long":"26.0000"},"fj":{"lat":"-18.0000","long":"175.0000"},"fk":{"lat":"-51.7500","long":"-59.0000"},"fm":{"lat":"6.9167","long":"158.2500"},"fo":{"lat":"62.0000","long":"-7.0000"},"fr":{"lat":"46.0000","long":"2.0000"},"ga":{"lat":"-1.0000","long":"11.7500"},"gb":{"lat":"54.0000","long":"-2.0000"},"gd":{"lat":"12.1167","long":"-61.6667"},"ge":{"lat":"42.0000","long":"43.5000"},"gf":{"lat":"4.0000","long":"-53.0000"},"gh":{"lat":"8.0000","long":"-2.0000"},"gi":{"lat":"36.1833","long":"-5.3667"},"gl":{"lat":"72.0000","long":"-40.0000"},"gm":{"lat":"13.4667","long":"-16.5667"},"gn":{"lat":"11.0000","long":"-10.0000"},"gp":{"lat":"16.2500","long":"-61.5833"},"gq":{"lat":"2.0000","long":"10.0000"},"gr":{"lat":"39.0000","long":"22.0000"},"gs":{"lat":"-54.5000","long":"-37.0000"},"gt":{"lat":"15.5000","long":"-90.2500"},"gu":{"lat":"13.4667","long":"144.7833"},"gw":{"lat":"12.0000","long":"-15.0000"},"gy":{"lat":"5.0000","long":"-59.0000"},"hk":{"lat":"22.2500","long":"114.1667"},"hm":{"lat":"-53.1000","long":"72.5167"},"hn":{"lat":"15.0000","long":"-86.5000"},"hr":{"lat":"45.1667","long":"15.5000"},"ht":{"lat":"19.0000","long":"-72.4167"},"hu":{"lat":"47.0000","long":"20.0000"},"id":{"lat":"-5.0000","long":"120.0000"},"ie":{"lat":"53.0000","long":"-8.0000"},"il":{"lat":"31.5000","long":"34.7500"},"in":{"lat":"20.0000","long":"77.0000"},"io":{"lat":"-6.0000","long":"71.5000"},"iq":{"lat":"33.0000","long":"44.0000"},"ir":{"lat":"32.0000","long":"53.0000"},"is":{"lat":"65.0000","long":"-18.0000"},"it":{"lat":"42.8333","long":"12.8333"},"jm":{"lat":"18.2500","long":"-77.5000"},"jo":{"lat":"31.0000","long":"36.0000"},"jp":{"lat":"36.0000","long":"138.0000"},"ke":{"lat":"1.0000","long":"38.0000"},"kg":{"lat":"41.0000","long":"75.0000"},"kh":{"lat":"13.0000","long":"105.0000"},"ki":{"lat":"1.4167","long":"173.0000"},"km":{"lat":"-12.1667","long":"44.2500"},"kn":{"lat":"17.3333","long":"-62.7500"},"kp":{"lat":"40.0000","long":"127.0000"},"kr":{"lat":"37.0000","long":"127.5000"},"kw":{"lat":"29.3375","long":"47.6581"},"ky":{"lat":"19.5000","long":"-80.5000"},"kz":{"lat":"48.0000","long":"68.0000"},"la":{"lat":"18.0000","long":"105.0000"},"lb":{"lat":"33.8333","long":"35.8333"},"lc":{"lat":"13.8833","long":"-61.1333"},"li":{"lat":"47.1667","long":"9.5333"},"lk":{"lat":"7.0000","long":"81.0000"},"lr":{"lat":"6.5000","long":"-9.5000"},"ls":{"lat":"-29.5000","long":"28.5000"},"lt":{"lat":"56.0000","long":"24.0000"},"lu":{"lat":"49.7500","long":"6.1667"},"lv":{"lat":"57.0000","long":"25.0000"},"ly":{"lat":"25.0000","long":"17.0000"},"ma":{"lat":"32.0000","long":"-5.0000"},"mc":{"lat":"43.7333","long":"7.4000"},"md":{"lat":"47.0000","long":"29.0000"},"me":{"lat":"42.0000","long":"19.0000"},"mg":{"lat":"-20.0000","long":"47.0000"},"mh":{"lat":"9.0000","long":"168.0000"},"mk":{"lat":"41.8333","long":"22.0000"},"ml":{"lat":"17.0000","long":"-4.0000"},"mm":{"lat":"22.0000","long":"98.0000"},"mn":{"lat":"46.0000","long":"105.0000"},"mo":{"lat":"22.1667","long":"113.5500"},"mp":{"lat":"15.2000","long":"145.7500"},"mq":{"lat":"14.6667","long":"-61.0000"},"mr":{"lat":"20.0000","long":"-12.0000"},"ms":{"lat":"16.7500","long":"-62.2000"},"mt":{"lat":"35.8333","long":"14.5833"},"mu":{"lat":"-20.2833","long":"57.5500"},"mv":{"lat":"3.2500","long":"73.0000"},"mw":{"lat":"-13.5000","long":"34.0000"},"mx":{"lat":"23.0000","long":"-102.0000"},"my":{"lat":"2.5000","long":"112.5000"},"mz":{"lat":"-18.2500","long":"35.0000"},"na":{"lat":"-22.0000","long":"17.0000"},"nc":{"lat":"-21.5000","long":"165.5000"},"ne":{"lat":"16.0000","long":"8.0000"},"nf":{"lat":"-29.0333","long":"167.9500"},"ng":{"lat":"10.0000","long":"8.0000"},"ni":{"lat":"13.0000","long":"-85.0000"},"nl":{"lat":"52.5000","long":"5.7500"},"no":{"lat":"62.0000","long":"10.0000"},"np":{"lat":"28.0000","long":"84.0000"},"nr":{"lat":"-0.5333","long":"166.9167"},"nu":{"lat":"-19.0333","long":"-169.8667"},"nz":{"lat":"-41.0000","long":"174.0000"},"om":{"lat":"21.0000","long":"57.0000"},"pa":{"lat":"9.0000","long":"-80.0000"},"pe":{"lat":"-10.0000","long":"-76.0000"},"pf":{"lat":"-15.0000","long":"-140.0000"},"pg":{"lat":"-6.0000","long":"147.0000"},"ph":{"lat":"13.0000","long":"122.0000"},"pk":{"lat":"30.0000","long":"70.0000"},"pl":{"lat":"52.0000","long":"20.0000"},"pm":{"lat":"46.8333","long":"-56.3333"},"pr":{"lat":"18.2500","long":"-66.5000"},"ps":{"lat":"32.0000","long":"35.2500"},"pt":{"lat":"39.5000","long":"-8.0000"},"pw":{"lat":"7.5000","long":"134.5000"},"py":{"lat":"-23.0000","long":"-58.0000"},"qa":{"lat":"25.5000","long":"51.2500"},"re":{"lat":"-21.1000","long":"55.6000"},"ro":{"lat":"46.0000","long":"25.0000"},"rs":{"lat":"44.0000","long":"21.0000"},"ru":{"lat":"60.0000","long":"100.0000"},"rw":{"lat":"-2.0000","long":"30.0000"},"sa":{"lat":"25.0000","long":"45.0000"},"sb":{"lat":"-8.0000","long":"159.0000"},"sc":{"lat":"-4.5833","long":"55.6667"},"sd":{"lat":"15.0000","long":"30.0000"},"se":{"lat":"62.0000","long":"15.0000"},"sg":{"lat":"1.3667","long":"103.8000"},"sh":{"lat":"-15.9333","long":"-5.7000"},"si":{"lat":"46.0000","long":"15.0000"},"sj":{"lat":"78.0000","long":"20.0000"},"sk":{"lat":"48.6667","long":"19.5000"},"sl":{"lat":"8.5000","long":"-11.5000"},"sm":{"lat":"43.7667","long":"12.4167"},"sn":{"lat":"14.0000","long":"-14.0000"},"so":{"lat":"10.0000","long":"49.0000"},"sr":{"lat":"4.0000","long":"-56.0000"},"st":{"lat":"1.0000","long":"7.0000"},"sv":{"lat":"13.8333","long":"-88.9167"},"sy":{"lat":"35.0000","long":"38.0000"},"sz":{"lat":"-26.5000","long":"31.5000"},"tc":{"lat":"21.7500","long":"-71.5833"},"td":{"lat":"15.0000","long":"19.0000"},"tf":{"lat":"-43.0000","long":"67.0000"},"tg":{"lat":"8.0000","long":"1.1667"},"th":{"lat":"15.0000","long":"100.0000"},"tj":{"lat":"39.0000","long":"71.0000"},"tk":{"lat":"-9.0000","long":"-172.0000"},"tm":{"lat":"40.0000","long":"60.0000"},"tn":{"lat":"34.0000","long":"9.0000"},"to":{"lat":"-20.0000","long":"-175.0000"},"tr":{"lat":"39.0000","long":"35.0000"},"tt":{"lat":"11.0000","long":"-61.0000"},"tv":{"lat":"-8.0000","long":"178.0000"},"tw":{"lat":"23.5000","long":"121.0000"},"tz":{"lat":"-6.0000","long":"35.0000"},"ua":{"lat":"49.0000","long":"32.0000"},"ug":{"lat":"1.0000","long":"32.0000"},"um":{"lat":"19.2833","long":"166.6000"},"us":{"lat":"38.0000","long":"-97.0000"},"uy":{"lat":"-33.0000","long":"-56.0000"},"uz":{"lat":"41.0000","long":"64.0000"},"va":{"lat":"41.9000","long":"12.4500"},"vc":{"lat":"13.2500","long":"-61.2000"},"ve":{"lat":"8.0000","long":"-66.0000"},"vg":{"lat":"18.5000","long":"-64.5000"},"vi":{"lat":"18.3333","long":"-64.8333"},"vn":{"lat":"16.0000","long":"106.0000"},"vu":{"lat":"-16.0000","long":"167.0000"},"wf":{"lat":"-13.3000","long":"-176.2000"},"ws":{"lat":"-13.5833","long":"-172.3333"},"ye":{"lat":"15.0000","long":"48.0000"},"yt":{"lat":"-12.8333","long":"45.1667"},"za":{"lat":"-29.0000","long":"24.0000"},"zm":{"lat":"-15.0000","long":"30.0000"},"zw":{"lat":"-20.0000","long":"30.0000"}}
6 | }
--------------------------------------------------------------------------------
/models/DiskStorage/Coin/CoinUpdate.js:
--------------------------------------------------------------------------------
1 | // pRoy24 tokenplex
2 |
3 | const cassandra = require('cassandra-driver');
4 | var Constants = require('../../../constants');
5 | const cassandraClient = new cassandra.Client({contactPoints: [Constants.CQL_API_SERVER]});
6 |
7 | module.exports = {
8 | saveCoinSnapshot: function(coinTradeNormalizedData) {
9 | const queries = [];
10 | coinTradeNormalizedData.forEach(function(coinTradeData) {
11 |
12 | delete coinTradeData["lastmarket"];
13 | if (coinTradeData["openday"]) {
14 | delete coinTradeData["openday"];
15 | delete coinTradeData["highday"];
16 | delete coinTradeData["lowday"];
17 | delete coinTradeData["volumeday"];
18 | delete coinTradeData["volumedayto"];
19 | }
20 |
21 | let keys = Object.keys(coinTradeData).map(function(key, idx){
22 | return key;
23 | }).filter(Boolean).join(", ");
24 |
25 | let placeholders = Object.keys(coinTradeData).map(function(key, idx){
26 | return "?";
27 | }).filter(Boolean).join(", ");
28 |
29 | let values = Object.keys(coinTradeData).map(function(key){
30 | return (coinTradeData[key]).toString();
31 | }).filter(Boolean);
32 |
33 |
34 | const query = 'INSERT INTO tokenplex.coin_details (' + keys + ') VALUES (' + placeholders + ') USING TTL 120';
35 | queries.push({
36 | query: query,
37 | params: values
38 | })
39 | });
40 | return cassandraClient.batch(queries, { prepare: true }, function(err, res){
41 | if (err) {
42 | return err;
43 | }
44 | return res;
45 | });
46 | },
47 |
48 | saveCoinSocialData: function(coinID, coinSocialData) {
49 | let Reddit = JSON.stringify(coinSocialData.Reddit);
50 | let Twitter = JSON.stringify(coinSocialData.Twitter);
51 | let Facebook = JSON.stringify(coinSocialData.Facebook);
52 | let Repository = JSON.stringify(coinSocialData.CodeRepository);
53 |
54 | const placeHolders = "?, ?, ?, ?, ?";
55 | let values = [coinID, Reddit, Facebook, Twitter, Repository];
56 | let keyItems = "id, Reddit, Facebook, Twitter, CodeRepository";
57 |
58 | const SOCIAL_DATA_TTL = 3600;
59 | const query = 'INSERT INTO tokenplex.coin_social (' + keyItems + ') VALUES (' + placeHolders + ') USING TTL ' + SOCIAL_DATA_TTL;
60 | const params = values;
61 |
62 | cassandraClient.execute(query, params, {prepare: true}, function (err, response) {
63 | if (err) {
64 | console.log(err);
65 | }
66 | });
67 | return null;
68 | },
69 |
70 | saveCoinListData: function(coinListData) {
71 | let arrays = [], size = 20;
72 | while (coinListData.length > 0)
73 | arrays.push(coinListData.splice(0, size));
74 | arrays.forEach(function (coinListData) {
75 | let queries = [];
76 | coinListData.forEach(function (listItem, idx) {
77 | let values = Object.keys(listItem).map(function (currentKey, currIdx) {
78 | if (currentKey && listItem[currentKey] && currentKey !== "sponsored" && currentKey !== "max_supply") {
79 | return listItem[currentKey];
80 | } else {
81 | return null;
82 | }
83 | }).filter(Boolean);
84 | let placeholders = Object.keys(values).map((a)=>"?").join(", ");
85 | let keyslist = Object.keys(listItem).map(function (currentKey, currIdx) {
86 | if (listItem[currentKey] && currentKey !== "sponsored" && currentKey !== "max_supply") {
87 | return currentKey;
88 | } else {
89 | return null;
90 | }
91 | }).filter(Boolean).join(",");
92 |
93 | const query = 'INSERT INTO tokenplex.coins (' + keyslist + ') VALUES (' + placeholders + ') USING TTL 120';
94 | queries.push({
95 | query: query,
96 | params: values
97 | })
98 | });
99 |
100 | return cassandraClient.batch(queries, { prepare: true });
101 | })
102 | },
103 |
104 | saveCoinDayHistoryData: function (coinHistoryDataList) {
105 | Object.keys(coinHistoryDataList).forEach(function(coinSymbol) {
106 | if (coinHistoryDataList[coinSymbol].length > 0) {
107 | let coinHistoryData = coinHistoryDataList[coinSymbol];
108 | coinHistoryData.forEach(function(dataResponseItem){
109 | const placeHolders = "?, ?, ?";
110 | let values = [coinSymbol, dataResponseItem["high"], dataResponseItem["time"]];
111 | let keyItems = "symbol, high, time";
112 | let ttl = 120;
113 |
114 | const query = 'INSERT INTO tokenplex.daily_history_data (' + keyItems + ') VALUES (' + placeHolders + ') USING TTL ' + ttl;
115 | const params = values;
116 | cassandraClient.execute(query, params, {prepare: true}, function (err, response) {
117 | if (err) {
118 | console.log(err);
119 | }
120 | });
121 | });
122 | }
123 | });
124 | return {data: "started job"};
125 | },
126 |
127 | saveCoinWeekMinuteHistoryData: function(coinHistoryDataList, toSymbol) {
128 | Object.keys(coinHistoryDataList).forEach(function(coinSymbol) {
129 | if (coinHistoryDataList[coinSymbol].length > 0) {
130 | let coinHistoryData = coinHistoryDataList[coinSymbol];
131 | coinHistoryData.forEach(function(dataResponseItem){
132 | const placeHolders = "?, ?, ?, ?, ?, ?, ?, ?, ?";
133 | let values = [coinSymbol, dataResponseItem["high"], dataResponseItem["low"],
134 | dataResponseItem["open"], dataResponseItem["close"], dataResponseItem["time"].toString(),
135 | dataResponseItem["volumefrom"], dataResponseItem["volumeto"], toSymbol];
136 |
137 | let keyItems = "symbol, high, low, open, close, time, volumefrom, volumeto, tosymbol";
138 | // TTL Strategy of 120 seconds for Minute history data for week
139 | let ttl = 120;
140 | const query = 'INSERT INTO tokenplex.week_history_data (' + keyItems + ') VALUES (' + placeHolders + ') USING TTL ' + ttl;
141 | const params = values;
142 | cassandraClient.execute(query, params, {prepare: true}, function (err, response) {
143 | if (err) {
144 | console.log(err);
145 | }
146 | });
147 | });
148 | }
149 | });
150 | return {data: "Coin Week History Data persist started"};
151 | },
152 |
153 | saveCoinYearDayHistoryData: function(coinYearHistoryData, toSymbol) {
154 | Object.keys(coinYearHistoryData).forEach(function(coinSymbol) {
155 | if (coinYearHistoryData[coinSymbol].length > 0) {
156 | let coinHistoryData = coinYearHistoryData[coinSymbol];
157 | coinHistoryData.forEach(function(dataResponseItem){
158 | const placeHolders = "?, ?, ?, ?, ?, ?, ?, ?, ?";
159 | let values = [coinSymbol, dataResponseItem["high"], dataResponseItem["low"],
160 | dataResponseItem["open"], dataResponseItem["close"], dataResponseItem["time"].toString(),
161 | dataResponseItem["volumefrom"], dataResponseItem["volumeto"], toSymbol];
162 |
163 | let keyItems = "symbol, high, low, open, close, time, volumefrom, volumeto, tosymbol";
164 | // TTL Strategy of 2000 seconds for Day history data per year.
165 | let ttl = 2000;
166 |
167 | const query = 'INSERT INTO tokenplex.year_history_data (' + keyItems + ') VALUES (' + placeHolders + ') USING TTL ' + ttl;
168 | const params = values;
169 | cassandraClient.execute(query, params, {prepare: true}, function (err, response) {
170 | if (err) {
171 | console.log(err);
172 | }
173 | });
174 | });
175 | }
176 | });
177 | return {data: "Coin Year History Data persist started"};
178 | },
179 |
180 | saveCoinArbitrage: function(coinArbitrageData) {
181 | const queries = [];
182 | coinArbitrageData.forEach(function(coinTradeData) {
183 |
184 | delete coinTradeData["lastmarket"];
185 | if (coinTradeData["openday"]) {
186 | delete coinTradeData["openday"];
187 | delete coinTradeData["highday"];
188 | delete coinTradeData["lowday"];
189 | delete coinTradeData["volumeday"];
190 | delete coinTradeData["volumedayto"];
191 | }
192 |
193 | let keys = Object.keys(coinTradeData).map(function(key, idx){
194 | return key;
195 | }).filter(Boolean).join(", ");
196 |
197 | let placeholders = Object.keys(coinTradeData).map(function(key, idx){
198 | return "?";
199 | }).filter(Boolean).join(", ");
200 |
201 | let values = Object.keys(coinTradeData).map(function(key){
202 | return (coinTradeData[key]).toString();
203 | }).filter(Boolean);
204 |
205 |
206 | const query = 'INSERT INTO tokenplex.coin_details (' + keys + ') VALUES (' + placeholders + ') USING TTL 120';
207 | queries.push({
208 | query: query,
209 | params: values
210 | });
211 | });
212 | return cassandraClient.batch(queries, { prepare: true }, function(err, res){
213 | if (err) {
214 | return err;
215 | }
216 | return res;
217 | });
218 | },
219 |
220 | deleteCoinDayHistoryData: function(coinSymbol) {
221 | const query = "DELETE FROM tokenplex.daily_history_data WHERE symbol = '" + coinSymbol +"'";
222 | return cassandraClient.execute(query).then(function(response){
223 | return response;
224 | });
225 | },
226 |
227 | saveCoinExtraDetails: function(coinDetails) {
228 | // TODO add method to save coin details
229 | }
230 | }
231 |
232 | function randomRange(l,h){
233 | var range = (h-l);
234 | var random = Math.floor(Math.random()*range);
235 | if (random === 0){random+=1;}
236 | return l+random;
237 | }
--------------------------------------------------------------------------------
/routes/index.js:
--------------------------------------------------------------------------------
1 | // pRoy24 tokenplex
2 |
3 | var express = require('express');
4 | var router = express.Router();
5 | var request = require('request');
6 | var getCoinList = require('../models/GetCoinList');
7 | var DataFetchAPI = require('../models/CoinModels');
8 |
9 | /* GET home page. */
10 | var DBConnection = require('../models/DBModel');
11 | var axios = require('axios');
12 |
13 | const cassandra = require('cassandra-driver');
14 | var Constants = require('../constants');
15 | const cassandraClient = new cassandra.Client({contactPoints: [Constants.CQL_API_SERVER]});
16 |
17 | router.get('/', function(req, res, next) {
18 | res.send({"data": "Welcome to the land to tokens. API Version 0.9"});
19 | });
20 |
21 | router.get('/coin-list', function(req, res, next){
22 | DBConnection.getCassandraClientConnection().then(function(connection){
23 | const query = "SELECT * from tokenplex.coins";
24 | return cassandraClient.execute(query);
25 | }).then(function(coinListDBResponse){
26 | if (coinListDBResponse && coinListDBResponse.rows.length > 0) {
27 | return res.send({data: coinListDBResponse.rows});
28 | } else {
29 | return getCoinList.getCoinMarketCapCoinList().then(function (coinMarketApiResponse) {
30 | coinMarketApiResponse = coinMarketApiResponse.data;
31 | return getCoinList.getCryptoCompareCoinList().then(function (coinListResponse) {
32 | coinListResponse = coinListResponse.data.Data;
33 | let coinListItems = Object.keys(coinListResponse).map(function (key) {
34 | return coinListResponse[key];
35 | });
36 | coinListItems = coinListItems.sort(function (a, b) {
37 | return Number(a.SortOrder) - Number(b.SortOrder);
38 | });
39 | let joinedCoinDataList = [];
40 | for (let a = 0; a < coinMarketApiResponse.length; a++) {
41 | if (joinedCoinDataList.length === coinMarketApiResponse.length) {
42 | break;
43 | }
44 | for (let b = 0; b < coinListItems.length; b++) {
45 | if (coinMarketApiResponse[a].symbol.toLowerCase() === coinListItems[b].Name.toLowerCase()) {
46 | // Normalize response to store in cassandra DB
47 | const normalizedCMApiResponse =
48 | Object.assign(...Object.keys(coinMarketApiResponse[a]).map(function (cmResponseKey) {
49 | let temp = {};
50 | if (cmResponseKey === "24h_volume_usd") {
51 | temp["daily_volume_usd"] = coinMarketApiResponse[a]["24h_volume_usd"];
52 | } else {
53 | temp[cmResponseKey.toLowerCase()] = coinMarketApiResponse[a][cmResponseKey];
54 | }
55 | return temp;
56 | }));
57 |
58 | const normalizedCLApiResponse =
59 | Object.assign(...Object.keys(coinListItems[b]).map(function (clResponseKey) {
60 | let temp = {};
61 | temp[clResponseKey.toLowerCase()] = coinListItems[b][clResponseKey];
62 | return temp;
63 | }));
64 | joinedCoinDataList.push(Object.assign({}, normalizedCMApiResponse, normalizedCLApiResponse));
65 | break;
66 | }
67 | }
68 | }
69 | let shortenedCoinList = joinedCoinDataList.slice(0, 50);
70 | let queries = [];
71 | shortenedCoinList.forEach(function (listItem, idx) {
72 | let values = Object.keys(listItem).map(function (currentKey, currIdx) {
73 | if (currentKey && listItem[currentKey]) {
74 | return listItem[currentKey];
75 | } else {
76 | return null;
77 | }
78 | }).filter(Boolean);
79 | let placeholders = Object.keys(values).map((a)=>"?").join(", ");
80 | let keyslist = Object.keys(listItem).map(function (currentKey, currIdx) {
81 | if (listItem[currentKey]) {
82 | return currentKey;
83 | } else {
84 | return null;
85 | }
86 | }).filter(Boolean).join(",");
87 |
88 | const query = 'INSERT INTO tokenplex.coins (' + keyslist + ') VALUES (' + placeholders + ') USING TTL 00';
89 | queries.push({
90 | query: query,
91 | params: values
92 | })
93 | });
94 | res.send({data: shortenedCoinList});
95 | return cassandraClient.batch(queries, { prepare: true })
96 | .then(result => console.log('Data updated on cluster'));
97 | }).catch((e)=>console.log(e));
98 | });
99 | }
100 | })
101 | });
102 |
103 | router.get('/daily-coin-history', function(req, res, next){
104 | let coinListQuery = req.query.coin_symbol;
105 | cassandraClient.execute("SELECT * from tokenplex.daily_history_data where symbol=" + coinListQuery, function (err, result) {
106 | if (result && result.rows.length > 0) {
107 | let responseData = {};
108 | responseData[coinListQuery] = result.rows;
109 | res.send({data: responseData});
110 | } else {
111 | getCoinList.getCoinDayHistogram(coinListQuery).then(function (historyDataResponse) {
112 | const coinDataResponse = historyDataResponse.data.Data;
113 | coinDataResponse.forEach(function(dataResponseItem){
114 | const placeHolders = "?, ?, ?, ?, ?, ?, ?, ?";
115 | const keys = "time ,high ,low ,open ,volumefrom , volumeto ,close, symbol";
116 | let values = [];
117 | Object.keys(dataResponseItem).forEach(function(key){
118 | values.push(dataResponseItem[key]);
119 | });
120 | values.push(coinListQuery);
121 | let ttl = Math.floor(Math.random() * 300) + 200;
122 | const query = 'INSERT INTO tokenplex.daily_history_data (' + keys + ') VALUES (' + placeHolders + ') USING TTL ' +ttl;
123 | const params = values;
124 |
125 | cassandraClient.execute(query, params, {prepare: true}, function (err, response) {
126 | if (err) {
127 | console.log(err);
128 | }
129 | });
130 | });
131 | let responseData = {};
132 | responseData[coinListQuery] = coinDataResponse;
133 | res.send({data: responseData});
134 | });
135 | }
136 | });
137 | });
138 |
139 | router.get('/coin-history-data', function(req, res, next){
140 | let coinListQuery = req.query.coin_symbol;
141 | cassandraClient.execute("SELECT * from tokenplex.coin_history_data where symbol=" + coinListQuery, function (err, result) {
142 | if (result && result.rows.length > 0) {
143 | let responseData = {};
144 | responseData[coinListQuery] = result.rows;
145 | res.send({data: responseData});
146 | } else {
147 | getCoinList.getCoinMinuteData(coinListQuery).then(function (historyDataResponse) {
148 | const coinDataResponse = historyDataResponse.data.Data;
149 | coinDataResponse.forEach(function(dataResponseItem){
150 | const placeHolders = "?, ?, ?, ?, ?, ?, ?, ?";
151 | const keys = "time ,high ,low ,open ,volumefrom , volumeto ,close, symbol";
152 | let values = [];
153 | Object.keys(dataResponseItem).forEach(function(key){
154 | values.push(dataResponseItem[key]);
155 | });
156 | values.push(coinListQuery);
157 | let ttl = Math.floor(Math.random() * 300) + 200;
158 | const query = 'INSERT INTO tokenplex.coin_history_data (' + keys + ') VALUES (' + placeHolders + ') USING TTL ' +ttl;
159 | const params = values;
160 |
161 | cassandraClient.execute(query, params, {prepare: true}, function (err, response) {
162 | if (err) {
163 | console.log(err);
164 | }
165 | });
166 | });
167 | let responseData = {};
168 | responseData[coinListQuery] = coinDataResponse;
169 | res.send({data: responseData});
170 | });
171 | }
172 | });
173 | });
174 |
175 | router.get('/coin-snapshot', function(req, res, next){
176 | let coinSnapShotQuery = req.query.coin_symbol;
177 | cassandraClient.execute("SELECT * FROM tokenplex.coin_details WHERE fromsymbol='"+coinSnapShotQuery+"'", function (err, result) {
178 | if (result && result.rows.length > 0) {
179 | res.send({data: {symbol: coinSnapShotQuery, exchangeData: result.rows}});
180 | } else {
181 | return getCoinList.getCoinSnapShot(coinSnapShotQuery).then(function (historyDataResponse) {
182 | const ccMarketData = [historyDataResponse.data.Data['AggregatedData']];
183 | const exchangeMarketData = historyDataResponse.data.Data['Exchanges'];
184 |
185 | return DataFetchAPI.mergeExchangeList(exchangeMarketData).then(function(exchangeListResponse){
186 | const coinTradeData = exchangeListResponse.data.concat(ccMarketData);
187 | let coinTradeNormalizedData = coinTradeData.map(function(tradeDataItem){
188 | return Object.assign(...Object.keys(tradeDataItem).map(function(keyItem){
189 | let obj = {};
190 | obj[keyItem.toLowerCase()] = tradeDataItem[keyItem];
191 | return obj;
192 | }))
193 | });
194 | res.send({data: {symbol: coinSnapShotQuery, exchangeData: coinTradeNormalizedData}});
195 | const queries = [];
196 | coinTradeNormalizedData.forEach(function(coinTradeData){
197 | let keys = Object.keys(coinTradeData).map(function(key){
198 | if (key === "lastmarket") {
199 | return null;
200 | }
201 | return key;
202 | }).filter(Boolean).join(", ");
203 | let placeholders = Object.keys(coinTradeData).map(function(key){
204 | if (key === "lastmarket") {
205 | return null;
206 | }
207 | return "?";
208 | }).filter(Boolean).join(", ");
209 | let values = Object.keys(coinTradeData).map(function(key){
210 | if (key === "lastmarket") {
211 | return null;
212 | }
213 | return coinTradeData[key]
214 | }).filter(Boolean);
215 | let ttl = 400;
216 | const query = 'INSERT INTO tokenplex.coin_details (' + keys + ') VALUES (' + placeholders + ') USING TTL ' +ttl;
217 | queries.push({
218 | query: query,
219 | params: values
220 | })
221 | });
222 | return cassandraClient.batch(queries, { prepare: true })
223 | .then(result => console.log('Data updated on cluster'));
224 | });
225 | }).catch(function (e) {
226 | console.log(e);
227 | });
228 | }
229 | });
230 | });
231 |
232 | module.exports = router;
233 |
--------------------------------------------------------------------------------
/models/APIStorage/Exchange/ExchangeList.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 | var ccxt = require ('ccxt')
3 |
4 |
5 | let ObjectUtils = require('../../../utils/ObjectUtils');
6 | const markets = require('./ExchangeConstants');
7 | const MarketUtils = require('../../../utils/MarketUtils');
8 | const logger = require('../../../logs/logger');
9 |
10 | module.exports = {
11 | // Method returns the last of markets for a given exchange
12 | getMarketList: function (exchangeName) {
13 | return getExchangeObject(exchangeName).then(function(currentExchange){
14 | return currentExchange.loadMarkets().then(function (exchangeMarketList) {
15 | return getExchangeTicker(currentExchange).then(function (currentExchangeTickerResponse) {
16 | let tickerNormalizedResponse = [];
17 | currentExchangeTickerResponse.forEach(function(responseObject){
18 | if (responseObject.base && responseObject.quote) {
19 | tickerNormalizedResponse.push(MarketUtils.normalizeMarketListForExchange(responseObject));
20 | }
21 | });
22 | return tickerNormalizedResponse;
23 | });
24 | });
25 | });
26 | },
27 |
28 | // Method returns the active exchanges
29 | getMarketActiveExchanges(currentExchange, exchangeName) {
30 | try {
31 | return currentExchange.fetchMarkets().then(function (exchangeMarket) {
32 | let exchangeRows = [];
33 | let timeStamp = Date.now();
34 | Object.keys(exchangeMarket).forEach(function (marketKey) {
35 | exchangeRows.push({
36 | "base": exchangeMarket[marketKey].base, "quote": exchangeMarket[marketKey].quote,
37 | "symbol": exchangeMarket[marketKey].symbol, "market": exchangeName,
38 | "timestamp": timeStamp
39 | })
40 | });
41 | let normalizedExchangeRows = MarketUtils.mergeQuotesForBaseInExchange(exchangeRows);
42 | return ({data: normalizedExchangeRows});
43 | }).catch(function (ex) {
44 | return ({data: [], error: ex});
45 | });
46 | } catch (e) {
47 |
48 | }
49 | },
50 |
51 | getMarketExchangeByToken() {
52 | let exchangeNormalizedArray = [];
53 |
54 | function marketCallback(responseData) {
55 | return responseData;
56 | }
57 |
58 | ccxt.exchanges.forEach(function (exchangeName, exIdx, exchangeArr) {
59 | try {
60 | let currentExchange = markets.getExchangeInstance(exchangeName);
61 | currentExchange.fetchMarkets().then(function (exchangeMarket) {
62 | let exchangeRows = [];
63 | let timeStamp = Date.now();
64 | Object.keys(exchangeMarket).forEach(function (marketKey) {
65 | exchangeRows.push({
66 | "base": exchangeMarket[marketKey].base, "quote": exchangeMarket[marketKey].quote,
67 | "symbol": exchangeMarket[marketKey].symbol, "market": exchangeName,
68 | "timestamp": timeStamp
69 | })
70 | });
71 | exchangeNormalizedArray = exchangeNormalizedArray.concat(MarketUtils.mergeQuotesForBaseInExchange(exchangeRows));
72 | // console.log(exchangeNormalizedArray.length);
73 | if (exIdx === exchangeArr.length - 1) {
74 | marketCallback(MarketUtils.groupCoinByMarketMaps(exchangeNormalizedArray));
75 | }
76 | }).catch(function (fetchMarketException) {
77 | logger.log({"level": "error", message: fetchMarketException});
78 | // marketCallback({error: fetchMarketException});
79 | });
80 | }
81 | catch (e) {
82 | logger.log({"level": "error", "message": e});
83 | }
84 | });
85 |
86 | setTimeout(function () {
87 | return MarketUtils.groupCoinByMarketMaps(exchangeNormalizedArray);
88 | }, 6000);
89 | },
90 |
91 | getMarketTrades: function (baseToken, quoteToken) {
92 |
93 | },
94 |
95 | getExchangeWeekHistory: function (exchangeCode, baseToken, quoteToken) {
96 | let currentExchange = markets.getExchangeInstance(exchangeCode);
97 | return currentExchange.loadMarkets().then(function (loadMarketResponse) {
98 | let marketList = Object.keys(loadMarketResponse).map(function (item) {
99 | return loadMarketResponse[item];
100 | }).find(function (item) {
101 | return item.base === baseToken && item.quote === quoteToken;
102 | });
103 | let marketSymbol = marketList.symbol;
104 | return currentExchange.fetchOHLCV(marketSymbol, "1m").then(function (ohclvResponse) {
105 | return ohclvResponse;
106 | });
107 | }).catch(function (err) {
108 | throw err;
109 | });
110 | },
111 |
112 | getMinutelySampledHistoryData: function(exchangeCode, baseToken, quoteToken) {
113 | return getExchangeObject(exchangeCode).then(function(currentExchange){
114 | return currentExchange.loadMarkets().then(function (loadMarketResponse) {
115 | let marketList = Object.keys(loadMarketResponse).map(function (item) {
116 | return loadMarketResponse[item];
117 | }).find(function (item) {
118 | return item.base === baseToken && item.quote === quoteToken;
119 | });
120 | let marketSymbol = marketList.symbol;
121 | return currentExchange.fetchOHLCV(marketSymbol, "1m").then(function (ohclvResponse) {
122 | return ohclvResponse;
123 | });
124 | }).catch(function (err) {
125 | return [];
126 | });
127 | });
128 | },
129 |
130 | getHourlySampledHistoryData: function() {
131 |
132 | },
133 |
134 | getSampledHistoryData: function(exchangeCode, baseToken, quoteToken, rate) {
135 | return getExchangeObject(exchangeCode).then(function(currentExchange){
136 | return currentExchange.loadMarkets().then(function (loadMarketResponse) {
137 | let marketList = Object.keys(loadMarketResponse).map(function (item) {
138 | return loadMarketResponse[item];
139 | }).find(function (item) {
140 | return item.base === baseToken && item.quote === quoteToken;
141 | });
142 | let marketSymbol = marketList.symbol;
143 | if (currentExchange.hasFetchOHLCV && marketSymbol) {
144 | return currentExchange.fetchOHLCV(marketSymbol, rate).then(function (ohclvResponse) {
145 | return ohclvResponse;
146 | });
147 | } else {
148 | return new Promise(resolve => {
149 | setTimeout(() => {
150 | resolve([]);
151 | }, 100);
152 | });
153 | }
154 | }).catch(function (err) {
155 | return [];
156 | });
157 | });
158 | },
159 |
160 | getExchangeOrderbook: function (exchangeCode, baseToken, quoteToken) {
161 | return getExchangeObject(exchangeCode).then(function(currentExchange){
162 | return currentExchange.loadMarkets().then(function (loadMarketResponse) {
163 | let marketList = Object.keys(loadMarketResponse).map(function (item) {
164 | return loadMarketResponse[item];
165 | }).find(function (item) {
166 | return item.base === baseToken && item.quote === quoteToken;
167 | });
168 | let marketSymbol = marketList.symbol;
169 | return currentExchange.fetchOrderBook(marketSymbol).then(function (orderBookResponse) {
170 | return orderBookResponse;
171 | });
172 | });
173 | }).catch(function(err){
174 | return [];
175 | });
176 | },
177 |
178 | findExchangeDetails: function (exchangeName) {
179 | let currentExchange = markets.getExchangeInstance(exchangeName);
180 | if (ObjectUtils.isNonEmptyObject(currentExchange)) {
181 | return getExchangeTicker(currentExchange).then(function (currentExchangeTickerResponse) {
182 | let marketDetail = {};
183 | if (!ObjectUtils.isEmptyString(exchangeName)) {
184 | marketDetail[exchangeName] = MarketUtils.getExchangeNameToDetailsArray(currentExchangeTickerResponse);
185 | }
186 | return marketDetail;
187 | }).catch(function(e){
188 | return {};
189 | });
190 | } else {
191 | return new Promise(resolve => {
192 | setTimeout(() => {
193 | resolve({});
194 | }, 1);
195 | });
196 | }
197 | },
198 |
199 | findExchangesForToken: function(baseToken) {
200 | ccxt.exchanges.forEach(function(exchangeName){
201 | let currentExchange = markets.getExchangeInstance(exchangeName);
202 | currentExchange.fetchMarkets().then(function (exchangeMarket) {
203 |
204 | });
205 | });
206 | return new Promise(resolve => {
207 | setTimeout(() => {
208 | let normalizedExchangeList = [];
209 | resolve(normalizedExchangeList);
210 | }, 100);
211 | });
212 | }
213 | }
214 |
215 | function getExchangeTicker(currentExchange) {
216 | return currentExchange.loadMarkets().then(function (exchangeMarketList) {
217 | if (currentExchange.hasFetchTickers) {
218 | return currentExchange.fetchTickers().then(function(tickerResponse){
219 | let tickerNormalizedResponse = [];
220 | Object.keys(tickerResponse).forEach(function(tickerKey){
221 | if (ObjectUtils.isNonEmptyString(tickerKey)) {
222 | let responseMarket = exchangeMarketList[tickerKey];
223 | let mergedMarketResponse = Object.assign({}, responseMarket, tickerResponse[tickerKey], {exchangeName: currentExchange.name});
224 | tickerNormalizedResponse.push(MarketUtils.normalizeMarketListForExchange(mergedMarketResponse));
225 | }
226 | });
227 | return tickerNormalizedResponse;
228 | }).catch(function(err){
229 | return [];
230 | });
231 | } else if (currentExchange.hasFetchTicker) {
232 | // If exchange does not provide a fetchTickers endpoint
233 | // then we fetch individual ticker data with timeout = exchange.rateLimit
234 |
235 | let rateLimit = 1000;
236 | if (Number(currentExchange.rateLimit) > 0) {
237 | rateLimit = Number(currentExchange.rateLimit);
238 | }
239 |
240 | let actions = Object.keys(exchangeMarketList).map(function(item){
241 | return fetchMarketTicker(exchangeMarketList, currentExchange, item, rateLimit);
242 | })
243 |
244 | var results = Promise.all(actions); // pass array of promises
245 | return results.then(function(response){
246 | return response;
247 | }).catch(function(err){
248 | return err;
249 | });
250 | }
251 | });
252 | }
253 |
254 | function fetchMarketTicker(exchangeMarketList, currentExchange, marketSymbol, rateLimit) {
255 | return currentExchange.fetchTicker(marketSymbol).then(function(tickerResponse) {
256 | let mergedMarketResponse = Object.assign({}, exchangeMarketList[marketSymbol], tickerResponse, {exchangeName: currentExchange.name});
257 | return mergedMarketResponse;
258 | }).then(function(mergedMarketResponse){
259 | return new Promise(resolve => {
260 | setTimeout(() => {
261 | resolve(mergedMarketResponse);
262 | }, rateLimit);
263 | });
264 | }).catch(function(err){
265 | return [];
266 | });
267 | }
268 |
269 | function getExchangeObject(exchangeName) {
270 | return new Promise(resolve => {
271 | let exchangeObject = markets.getExchangeInstance(exchangeName);
272 | setTimeout(() => {
273 | resolve(exchangeObject);
274 | }, 300);
275 | });
276 | }
--------------------------------------------------------------------------------
/models/CoinModels.js:
--------------------------------------------------------------------------------
1 | // pRoy24 tokenplex
2 |
3 | let _ = require('lodash');
4 | const DiskStorage = require('./DiskStorage'),
5 | APIStorage = require('./APIStorage');
6 | CoinUtils = require('./DiskStorage/Coin/util');
7 | CacheStorage = require('./CacheStorage/');
8 | ObjectUtils = require('../utils/ObjectUtils');
9 |
10 | module.exports = {
11 | getCoinRow: function(coinSymbol) {
12 | return CacheStorage.findCoinRow(coinSymbol).then(function (response) {
13 | if (response && response.data && Object.keys(response.data).length > 0) {
14 | return response;
15 | } else {
16 | return APIStorage.findCoinRow(coinSymbol).then(function(apiCoinResponse){
17 | return ({data: apiCoinResponse.data});
18 | })
19 | }
20 | })
21 | },
22 |
23 | getCoinArbitrage: function (fromSymbol, toSymbol) {
24 | return DiskStorage.getCoinArbitrage(fromSymbol, toSymbol).then(function(coinArbitrageResponse){
25 | if (coinArbitrageResponse.data.rows && ObjectUtils.isNonEmptyArray(coinArbitrageResponse.data.rows)) {
26 | return coinArbitrageResponse.data.rows;
27 | } else {
28 | return APIStorage.getCoinArbitrage(fromSymbol, toSymbol).then(function(apiArbitrageResponse){
29 |
30 | if (toSymbol !== "BTC" && (!apiArbitrageResponse || apiArbitrageResponse.data.Data === null
31 | || Object.keys(apiArbitrageResponse.data.Data).length === 0)) {
32 | return APIStorage.findCoinSnapshot(fromSymbol, "BTC").then(function (apiCoinArbitrageResponseBTC) {
33 | let normalizedAPISnapshot = CoinUtils.normalizeCoinSnapShotData(apiCoinArbitrageResponseBTC);
34 | DiskStorage.saveCoinArbitrage(normalizedAPISnapshot);
35 | return normalizedAPISnapshot;
36 | });
37 | } else
38 | {
39 | let normalizedAPISnapshot = CoinUtils.normalizeCoinSnapShotData(apiArbitrageResponse);
40 | DiskStorage.saveCoinSnapshot(normalizedAPISnapshot, toSymbol);
41 | return normalizedAPISnapshot;
42 | }
43 | });
44 | }
45 | });
46 | },
47 |
48 | getCoinSnapshot: function(fromSymbol, toSymbol) {
49 | return DiskStorage.findCoinSnapshot(fromSymbol, toSymbol).then(function(response){
50 | if (response && Object.keys(response).length > 0) {
51 | return response;
52 | } else {
53 | return APIStorage.findCoinSnapshot(fromSymbol, toSymbol).then(function(apiCoinSnapshotResponseUSD){
54 | if (!apiCoinSnapshotResponseUSD || apiCoinSnapshotResponseUSD.data.Data === null
55 | || Object.keys(apiCoinSnapshotResponseUSD.data.Data).length === 0) {
56 | return APIStorage.findCoinSnapshot(fromSymbol, "BTC").then(function(apiCoinSnapshotResponseBTC){
57 | let normalizedAPISnapshot = CoinUtils.normalizeCoinSnapShotData(apiCoinSnapshotResponseBTC);
58 | DiskStorage.saveCoinSnapshot(normalizedAPISnapshot);
59 | return normalizedAPISnapshot;
60 | });
61 | } else {
62 | let normalizedAPISnapshot = CoinUtils.normalizeCoinSnapShotData(apiCoinSnapshotResponseUSD);
63 | DiskStorage.saveCoinSnapshot(normalizedAPISnapshot);
64 | return normalizedAPISnapshot;
65 | }
66 | });
67 | }
68 | });
69 | },
70 |
71 | getCoinFullSnapshot: function(coinID) {
72 | return DiskStorage.findCoinSnapshot(coinID).then(function(response){
73 | if (response && Object.keys(response).length > 0) {
74 | return response;
75 | } else {
76 | return APIStorage.findCoinSnapshot(coinID).then(function(apiCoinSnapshotResponse){
77 | return apiCoinSnapshotResponse;
78 | });
79 | }
80 | });
81 | },
82 |
83 | getMarketList: function(coinID) {
84 | return DiskStorage.findCoinSnapshot(coinID).then(function(response){
85 | if (response && Object.keys(response).length > 0) {
86 | return response;
87 | } else {
88 | return APIStorage.findCoinSnapshot(coinID).then(function(apiCoinSnapshotResponse){
89 | DiskStorage.saveCoinSnapshot(apiCoinSnapshotResponse);
90 | return apiCoinSnapshotResponse;
91 | })
92 | }
93 | })
94 | },
95 |
96 | getCoinSocialData: function (coinID) {
97 | return DiskStorage.findCoinSocialData(coinID).then(function(response){
98 | if (response && response.data.length > 0) {
99 | let items = {};
100 | Object.keys(response.data[0]).forEach(function(key){
101 | if (key === "coderepository") {
102 | items["CodeRepository"] = JSON.parse(response.data[0][key]);
103 | } else {
104 | items[_.capitalize(key)] = JSON.parse(response.data[0][key]);
105 | }
106 | });
107 | return items;
108 | } else {
109 | return APIStorage.findCoinSocialData(coinID).then(function(apiCoinSocialResponse){
110 | const coinSocialResponse = apiCoinSocialResponse.data.Data;
111 | DiskStorage.saveCoinSocialData(coinID, coinSocialResponse);
112 | return coinSocialResponse;
113 | })
114 | }
115 | });
116 | },
117 |
118 | getAPICoinList: function() {
119 | return APIStorage.findCoinList().then(function(apiCoinSnapshotResponse){
120 | const coinListResponse = apiCoinSnapshotResponse.data;
121 | return coinListResponse;
122 | })
123 | },
124 |
125 |
126 | saveCoinListToCache: function() {
127 | return APIStorage.findCoinList().then(function(apiCoinSnapshotResponse){
128 | const coinListResponse = apiCoinSnapshotResponse.data;
129 | CacheStorage.saveCoinList(coinListResponse);
130 | return coinListResponse;
131 | })
132 | },
133 |
134 | getCoinListFromCache: function () {
135 | return CacheStorage.getCoinList();
136 | },
137 |
138 | getCoinList: function(rangeRequest) {
139 | return CacheStorage.getCoinList(rangeRequest).then(function(response){
140 | if (response && response.data && response.data.length > 0) {
141 | return response.data;
142 | } else {
143 | return APIStorage.findCoinList().then(function(apiCoinSnapshotResponse){
144 | const coinListResponse = apiCoinSnapshotResponse.data;
145 | return coinListResponse;
146 | })
147 | }
148 | });
149 | },
150 |
151 | deleteCoinList: function(token) {
152 | CacheStorage.deleteCoinList(token);
153 | },
154 |
155 | getDailyHistoryData: function(coinSymbol) {
156 | return DiskStorage.findCoinDayHistoryData(coinSymbol).then(function(response){
157 | if (response && response.data.length > 0) {
158 | return response.data;
159 | } else {
160 | return APIStorage.findCoinDayHistoryData(coinSymbol).then(function(apiCoinDayHistoryDataResponse){
161 | const coinAPIResponse = apiCoinDayHistoryDataResponse.data.Data;
162 | return coinAPIResponse;
163 | }).catch(function(e){
164 | return {error: e};
165 | });
166 | }
167 | });
168 | },
169 |
170 | getWeekMinuteHistoryData: function(fromSymbol, toSymbol) {
171 | return DiskStorage.findCoinWeekMinuteHistoryData(fromSymbol, toSymbol).then(function (response) {
172 | if (response && response.data.rows.length > 0) {
173 | return response.data.rows;
174 | } else {
175 | return APIStorage.findCoinWeekMinuteHistoryData(fromSymbol, toSymbol).then(function(apiCoinDayHistoryDataResponse){
176 | const coinAPIResponse = apiCoinDayHistoryDataResponse.data.Data;
177 | let response = {};
178 | response[fromSymbol] = coinAPIResponse;
179 | DiskStorage.saveCoinWeekMinuteHistoryData(response, toSymbol);
180 | return coinAPIResponse;
181 | }).catch(function(e){
182 | return {}
183 | });
184 | }
185 | });
186 | },
187 |
188 | getExchangeList: function() {
189 | return DiskStorage.findExchangeList().then(function(diskStorageResponse){
190 | if (diskStorageResponse && diskStorageResponse.data.length > 0) {
191 | return diskStorageResponse.data;
192 | } else {
193 | return APIStorage.findExchangeList().then(function(apiStorageResponse){
194 | let apiExchangeListResponse = apiStorageResponse.data.data;
195 | return {data: apiExchangeListResponse};
196 | });
197 | }
198 | })
199 | },
200 |
201 | mergeExchangeList: function(exchangeMarketData) {
202 | return DiskStorage.findExchangeList().then(function(diskStorageResponse){
203 | if (diskStorageResponse && diskStorageResponse.data && diskStorageResponse.data.length > 0) {
204 | return diskStorageResponse.data;
205 | } else {
206 | return APIStorage.findExchangeList().then(function(apiStorageResponse){
207 | let apiExchangeListResponse = apiStorageResponse.data.data;
208 | return {data: mergeList(exchangeMarketData, apiExchangeListResponse)};
209 | });
210 | }
211 | })
212 | },
213 |
214 | findCoinByName: function(coinSearchString) {
215 | return CacheStorage.searchCoin(coinSearchString).then(function(coinSearchResponse){
216 | if (coinSearchResponse && coinSearchResponse.data.length > 0) {
217 | return {data: coinSearchResponse.data}
218 | } else {
219 | return APIStorage.findCoinList().then(function(apiCoinSnapshotResponse){
220 | const coinListResponse = apiCoinSnapshotResponse.data;
221 | let coinSearchMatchesList = coinListResponse.filter(function(coin){
222 | return (new RegExp(coinSearchString.toLowerCase()).test(coin.fullname.toLowerCase()));
223 | });
224 | return {data: coinSearchMatchesList}
225 | });
226 | }
227 | });
228 | },
229 |
230 | findCoinPriceAtTimeStamp: function(fromSymbol, exchange, timeStamp ) {
231 | return APIStorage.getCoinHistoricalPrice(fromSymbol, exchange, timeStamp)
232 | .then(function(coinHistoryResponse){
233 | let responseObject = coinHistoryResponse.data;
234 | let coinPrice = responseObject[Object.keys(responseObject)[0]];
235 | return {data: coinPrice};
236 | })
237 | },
238 |
239 | getCoinYearHistoryData: function(fromSymbol, toSymbol) {
240 | return DiskStorage.findCoinYearDayHistoryData(fromSymbol, toSymbol).then(function (response) {
241 | if (response && response.data.length > 0) {
242 | return response.data;
243 | } else {
244 | return APIStorage.findCoinYearDayHistoryData(fromSymbol, toSymbol).then(function(apiCoinDayHistoryDataResponse){
245 | const coinAPIResponse = apiCoinDayHistoryDataResponse.data.Data;
246 | let response = {};
247 | response[fromSymbol] = coinAPIResponse;
248 | DiskStorage.saveCoinYearDayHistoryData(response, toSymbol);
249 | return coinAPIResponse;
250 | }).catch(function(e){
251 | console.log(e);
252 | return {error: e};
253 | });
254 | }
255 | });
256 | }
257 | }
258 |
259 | function mergeList(exchangeMarketData, exchangeList) {
260 | let mergedList = [];
261 | for (let a= 0 ;a < exchangeMarketData.length; a++) {
262 | let objectFound = false;
263 | for( let b =0; b < exchangeList.length; b++) {
264 | if ((exchangeMarketData[a].MARKET.toLowerCase().trim() === exchangeList[b].exch_name.toLowerCase().trim()) ||
265 | exchangeMarketData[a].MARKET.toLowerCase().trim() === exchangeList[b].exch_code.toLowerCase().trim()) {
266 | mergedList.push(Object.assign({}, exchangeMarketData[a], exchangeList[b]));
267 | objectFound = true;
268 | break;
269 | }
270 | }
271 | if (!objectFound) {
272 | mergedList.push(exchangeMarketData[a]);
273 | }
274 | }
275 | return mergedList;
276 | }
277 |
278 |
279 |
280 |
281 |
--------------------------------------------------------------------------------
/models/ExchangeModels.js:
--------------------------------------------------------------------------------
1 | // pRoy24 TokenPlex
2 | const APIStorage = require('./APIStorage');
3 | const DiskStorage = require('./DiskStorage');
4 | const ccxt = require('ccxt');
5 |
6 | const markets = require('./APIStorage/Exchange/ExchangeConstants');
7 | const ObjectUtils = require('../utils/ObjectUtils');
8 | const logger = require('../logs/logger');
9 | var MarketUtils = require('../utils/MarketUtils');
10 |
11 | module.exports = {
12 | getExchangeDetailsList: function() {
13 | var actions = ccxt.exchanges.map(getExchangeDetails);
14 | var results = Promise.all(actions); // pass array of promises
15 | return results.then(function(response){
16 | return response;
17 | });
18 | },
19 |
20 | /**
21 | * Method returns all the markets for the exchange keyed by base
22 | * eg. ["eth" :[{base: "1000", "quote": "1000", ...}, {}, ...], "btc": {}]
23 | * @param exchangeName
24 | */
25 | getMarketsForExchange: function(exchangeName) {
26 | return DiskStorage.getExchangeDetails(exchangeName).then(function(exchangeMarketList){
27 | if (ObjectUtils.isNonEmptyObject(exchangeMarketList)) {
28 | // Check if refresh required. Current Expiry Values support 10 Second TTL
29 | if (DiskStorage.hasExchangeDetailsExpired(exchangeName)) {
30 | APIStorage.getMarketsForExchange(exchangeName).then(function(exchangeMarketAPIList) {
31 | let exchangeDetailObject = {};
32 | let normalizedExchangeList = MarketUtils.getExchangeListKeyedByBaseToken(exchangeMarketAPIList);
33 | exchangeDetailObject[exchangeName] = normalizedExchangeList;
34 | DiskStorage.saveExchangeDetails(exchangeDetailObject);
35 | });
36 | }
37 | return exchangeMarketList[Object.keys(exchangeMarketList)[0]];
38 | } else {
39 | return APIStorage.getMarketsForExchange(exchangeName).then(function(exchangeMarketAPIList){
40 | let exchangeDetailObject = {};
41 | let normalizedExchangeList = MarketUtils.getExchangeListKeyedByBaseToken(exchangeMarketAPIList);
42 | exchangeDetailObject[exchangeName] = normalizedExchangeList;
43 | DiskStorage.saveExchangeDetails(exchangeDetailObject);
44 | let returnVal = MarketUtils.getExchangeListKeyedByBaseToken(exchangeMarketAPIList);
45 | return returnVal;
46 | });
47 | }
48 | });
49 | },
50 |
51 |
52 |
53 | listExchangeMetadata: function() {
54 | var actions = ccxt.exchanges.map(getExchangeMetadata);
55 | var results = Promise.all(actions); // pass array of promises
56 | return results.then(function(response){
57 | return response;
58 | }).catch(function(err){
59 | return err;
60 | });
61 | },
62 |
63 |
64 | getMarketListByToken: function() {
65 | let exchangeList = [];
66 | ccxt.exchanges.forEach(function(exchange, exIdx){
67 | try {
68 | let currentExchange = markets.getExchangeInstance(exchange);
69 | if (ObjectUtils.isNonEmptyObject(currentExchange)) {
70 | return APIStorage.getMarketActiveExchanges(currentExchange, exchange).then(function (marketsListInExchange) {
71 | exchangeList = exchangeList.concat(marketsListInExchange.data);
72 | // return exchangeList;
73 | }).catch(function (err) {
74 | logger.log({"level": "error", "message": err});
75 | });
76 | } else {
77 | // return [];
78 | }
79 | } catch (e) {
80 | logger.log({"level": "error", "message": e});
81 | }
82 | });
83 | return new Promise(resolve => {
84 | setTimeout(() => {
85 | let normalizedExchangeList = MarketUtils.groupCoinByMarketMaps(exchangeList);
86 | resolve(normalizedExchangeList);
87 | }, 12000);
88 | });
89 | },
90 |
91 | getMarketsForToken: function(baseToken) {
92 | return DiskStorage.findExchangesForToken(baseToken).then(function(marketTokenResponse){
93 |
94 | if (marketTokenResponse && marketTokenResponse.rows.length > 0) {
95 | return marketTokenResponse;
96 | } else {
97 | return APIStorage.findExchangesForToken(baseToken).then(function(apiTokenExchangeResponse){
98 | return apiTokenExchangeResponse;
99 | });
100 | }
101 | });
102 | },
103 |
104 |
105 | getMarketTrades: function(baseToken, quoteToken) {
106 | return DiskStorage.getMarketTrades(baseToken, quoteToken).then(function(marketTradeResponse){
107 | if (ObjectUtils.isNonEmptyArray(marketTradeResponse)) {
108 | return marketTradeResponse;
109 | } else {
110 | getMarketsForToken(baseToken).then(function(tokenMarketList){
111 | Object.keys(tokenMarketList.market).forEach(function(marketName){
112 | let currentMarketItem = markets.getExchangeInstance(marketName);
113 | currentMarketItem.loadMarkets().then(function(loadMarketResponse){
114 | currentMarketItem.fetchTickers().then(function(fetchTickerResponse){
115 | Object.keys(fetchTickerResponse).forEach(function(tickerCode){
116 | if (currentMarketItem.hasFetchTrades) {
117 | currentMarketItem.fetchTrades(tickerCode).then(function(tradeResponse){
118 |
119 | });
120 | }
121 | });
122 | });
123 | }).catch(function(err){
124 |
125 | });
126 | });
127 | });
128 | }
129 | });
130 | },
131 |
132 | getExchangeWeekHistory: function(exchangeCode, baseToken, quoteToken) {
133 | return DiskStorage.getExchangeWeekHistory(exchangeCode, baseToken, quoteToken).then(function(exchangeWeekHistoryResponse){
134 | if (ObjectUtils.isNonEmptyArray(exchangeWeekHistoryResponse.rows)) {
135 | return exchangeWeekHistoryResponse.rows;
136 | } else {
137 | return APIStorage.getExchangeWeekHistory(exchangeCode, baseToken, quoteToken).then(function(apiWeekHistoryResponse){
138 | return apiWeekHistoryResponse;
139 | });
140 | }
141 | });
142 | },
143 |
144 | getExchangeMonthHistory: function(exchangeCode, baseToken, quoteToken) {
145 | DiskStorage.getExchangeMonthHistory(exchangeCode, baseToken, quoteToken).then(function(exchangeWeekHistoryResponse){
146 | if (ObjectUtils.isNonEmptyArray(exchangeWeekHistoryResponse)) {
147 | return exchangeWeekHistoryResponse;
148 | } else {
149 | APIStorage.getExchangeMonthHistory(exchangeCode, baseToken, quoteToken).then(function(apiWeekHistoryResponse){
150 | return apiWeekHistoryResponse;
151 | });
152 | }
153 | });
154 | },
155 |
156 | getExchangeYearHistory: function(exchangeCode, baseToken, quoteToken) {
157 | DiskStorage.getExchangeWeekHistory(exchangeCode, baseToken, quoteToken).then(function(exchangeWeekHistoryResponse){
158 | if (ObjectUtils.isNonEmptyArray(exchangeWeekHistoryResponse)) {
159 | return exchangeWeekHistoryResponse;
160 | } else {
161 | APIStorage.getExchangeWeekHistory(exchangeCode, baseToken, quoteToken).then(function(apiWeekHistoryResponse){
162 | return apiWeekHistoryResponse;
163 | });
164 | }
165 | });
166 | },
167 |
168 | getExchangeOrderbook: function(exchangeCode, baseToken, quoteToken) {
169 | return DiskStorage.getExchangeOrderbook(exchangeCode, baseToken, quoteToken).then(function(diskOrderbookResponse){
170 | if (ObjectUtils.isNonEmptyArray(diskOrderbookResponse.rows)) {
171 | return diskOrderbookResponse.rows;
172 | } else {
173 | return APIStorage.getExchangeOrderbook(exchangeCode, baseToken, quoteToken).then(function(exchangeOrderbookResponse){
174 | return exchangeOrderbookResponse;
175 | });
176 | }
177 | })
178 | },
179 |
180 | getExchangeHistoryData: function(exchangeCode, baseToken, quoteToken, sampling) {
181 | if (sampling === "1h") {
182 | return getHourlySampledHistoryData(exchangeCode, baseToken, quoteToken);
183 | } else if (sampling === "1m") {
184 | return getMinutelySampledHistoryData(exchangeCode, baseToken, quoteToken);
185 | } else if (sampling === "1d") {
186 | return getDailySampledHistoryData(exchangeCode, baseToken, quoteToken);
187 | } else {
188 | return new Promise(resolve => {
189 | setTimeout(() => {
190 | resolve( []);
191 | }, 10);
192 | });
193 | }
194 | },
195 |
196 |
197 | }
198 |
199 | function getMarketsForToken(baseToken) {
200 | return DiskStorage.findMarketsForToken(baseToken).then(function(marketTokenResponse){
201 | return marketTokenResponse.data;
202 | });
203 | }
204 |
205 | function getExchangeDetails(exchangeName) {
206 | return DiskStorage.getExchangeDetails(exchangeName).then(function (diskExchangeReponse) {
207 | if (diskExchangeReponse && Object.keys(diskExchangeReponse).length > 0) {
208 | return diskExchangeReponse;
209 | } else {
210 | return APIStorage.getExchangeDetails(exchangeName).then(function (apiExchangeResponse) {
211 | if (apiExchangeResponse[exchangeName].length > 0) {
212 | DiskStorage.saveExchangeDetails(apiExchangeResponse);
213 | }
214 | return apiExchangeResponse;
215 | });
216 | }
217 | }).catch(function (err) {
218 | return {};
219 | });
220 | }
221 |
222 | function getExchangeMetadata(exchangeName) {
223 | return new Promise(resolve => {
224 | let exchangeDetails = {};
225 | try {
226 | exchangeDetails = markets.getExchangeInstance(exchangeName);
227 | } catch (e) {
228 |
229 | }
230 | setTimeout(() => {
231 | if (ObjectUtils.isNonEmptyObject(exchangeDetails)) {
232 | resolve(Object.assign({}, MarketUtils.getMarketMetadata(exchangeDetails), {code: exchangeName}));
233 | } else {
234 | resolve({});
235 | }
236 | }, 100);
237 | });
238 | }
239 |
240 | function getHourlySampledHistoryData(exchangeCode, baseToken, quoteToken) {
241 | return DiskStorage.getHourlySampledHistoryData(exchangeCode, baseToken, quoteToken).then(function(historyDataResponse){
242 | if (ObjectUtils.isNonEmptyArray(historyDataResponse)) {
243 | return historyDataResponse;
244 | } else {
245 | return APIStorage.getSampledHistoryData(exchangeCode, baseToken, quoteToken, "1h").then(function(apiWeekHistoryResponse){
246 | DiskStorage.saveHourlySampledHistoryData(exchangeCode, baseToken, quoteToken, apiWeekHistoryResponse);
247 | return apiWeekHistoryResponse;
248 | });
249 | }
250 | });
251 | }
252 |
253 | function getMinutelySampledHistoryData(exchangeCode, baseToken, quoteToken) {
254 | return DiskStorage.getMinutelySampledHistoryData(exchangeCode, baseToken, quoteToken).then(function(historyDataResponse){
255 | if (ObjectUtils.isNonEmptyArray(historyDataResponse)) {
256 | return historyDataResponse;
257 | } else {
258 | return APIStorage.getSampledHistoryData(exchangeCode, baseToken, quoteToken, "1m").then(function(apiWeekHistoryResponse){
259 | DiskStorage.saveMinutelySampledHistoryData(exchangeCode, baseToken, quoteToken, apiWeekHistoryResponse);
260 | return apiWeekHistoryResponse;
261 | });
262 | }
263 | });
264 | }
265 |
266 | function getDailySampledHistoryData(exchangeCode, baseToken, quoteToken) {
267 | return DiskStorage.getDailySampledHistoryData(exchangeCode, baseToken, quoteToken).then(function(historyDataResponse){
268 | if (ObjectUtils.isNonEmptyObject(historyDataResponse)) {
269 | return historyDataResponse;
270 | } else {
271 | return APIStorage.getSampledHistoryData(exchangeCode, baseToken, quoteToken, "1d").then(function(apiWeekHistoryResponse){
272 | DiskStorage.saveDailySampledHistoryData(exchangeCode, baseToken, quoteToken, apiWeekHistoryResponse);
273 | return apiWeekHistoryResponse;
274 | });
275 | }
276 | });
277 | }
278 |
279 | function getExchangeObject(exchageName) {
280 | return new Promise(resolve => {
281 | let exchangeObject = markets.getExchangeInstance(exchageName);
282 | setTimeout(() => {
283 | resolve(exchangeObject);
284 | }, 300);
285 | });
286 | }
--------------------------------------------------------------------------------
/routes/schema/CreateTable.js:
--------------------------------------------------------------------------------
1 | // pRoy24 tokenplex
2 |
3 | let express = require('express');
4 | var DBConnection = require('../../models/DBModel');
5 | const cassandra = require('cassandra-driver');
6 | var Constants = require('../../constants');
7 | const cassandraClient = new cassandra.Client({contactPoints: [Constants.CQL_API_SERVER]});
8 |
9 | const logger = require('../../logs/logger');
10 |
11 | module.exports = {
12 | // Create Coin List table, used to render top level views
13 | createCoinListTable: function(req, res) {
14 | DBConnection.getCassandraClientConnection()
15 | .then(function () {
16 | const query = "CREATE KEYSPACE IF NOT EXISTS tokenplex WITH replication =" +
17 | "{'class': 'SimpleStrategy', 'replication_factor': '1' }";
18 | return cassandraClient.execute(query);
19 | }).then(function(){
20 | const deleteQuery = "DROP TABLE IF EXISTS tokenplex.coins";
21 | return cassandraClient.execute(deleteQuery)
22 | }).then(function(deleteTableResponse){
23 | const Create_Coin_Table = "CREATE TABLE IF NOT EXISTS tokenplex.coins" +
24 | " (TimeStamp varchar," +
25 | "id varchar," +
26 | "name varchar," +
27 | "symbol varchar," +
28 | "rank varchar," +
29 | "price_usd varchar," +
30 | "price_btc varchar," +
31 | "daily_volume_usd varchar," +
32 | "market_cap_usd varchar," +
33 | "available_supply varchar," +
34 | "total_supply varchar," +
35 | "percent_change_1h varchar," +
36 | "percent_change_24h varchar," +
37 | "percent_change_7d varchar," +
38 | "last_updated varchar," +
39 | "url varchar," +
40 | "imageurl varchar," +
41 | "coinname varchar," +
42 | "fullname varchar," +
43 | "algorithm varchar," +
44 | "prooftype varchar," +
45 | "fullypremined varchar," +
46 | "totalcoinsupply varchar," +
47 | "preminedvalue varchar," +
48 | "totalcoinsfreefloat varchar," +
49 | "sortorder varchar, " +
50 | "max_supply varchar, " +
51 | "sponsored varchar, " +
52 | "PRIMARY KEY(symbol))";
53 | return cassandraClient.execute(Create_Coin_Table);
54 | })
55 | .then(function(createTableResponse){
56 | /* let sdsiQuery = "CREATE CUSTOM INDEX fn_prefix ON tokenplex.coins (fullname)" +
57 | " USING 'org.apache.cassandra.index.sasi.SASIIndex' WITH OPTIONS = { " +
58 | "'mode': 'CONTAINS'," +
59 | "'analyzer_class': 'org.apache.cassandra.index.sasi.analyzer.NonTokenizingAnalyzer'," +
60 | "'case_sensitive': 'false'"+
61 | "}";
62 |
63 | cassandraClient.execute(sdsiQuery);*/
64 | return res.send({data: createTableResponse});
65 | })
66 | .catch(function (err) {
67 | console.error('There was an error', err);
68 | return cassandraClient.shutdown();
69 | });
70 | },
71 |
72 | // Create Coin 24 hour history table, consists of hourly data points.
73 | createDayHistoryTable: function(req, res, next) {
74 | const CREATE_DAILY_HISTORY_TABLE = "CREATE TABLE IF NOT EXISTS tokenplex.daily_history_data" +
75 | "( symbol varchar," +
76 | " time timestamp," +
77 | " high float," +
78 | " low float," +
79 | " open float," +
80 | " volumefrom float," +
81 | " volumeto float," +
82 | " close float, PRIMARY KEY(symbol, time))";
83 | const DELETE_DAILY_HISTORY_TABLE = "DROP TABLE IF EXISTS tokenplex.daily_history_data";
84 |
85 | cassandraClient.connect()
86 | .then(function () {
87 | return cassandraClient.execute(DELETE_DAILY_HISTORY_TABLE).then(function () {
88 | return cassandraClient.execute(CREATE_DAILY_HISTORY_TABLE)
89 | })
90 | .then(function (createTableResponse) {
91 | res.send({data: createTableResponse});
92 |
93 | }).catch(function (err) {
94 | res.send({"error": err});
95 | });
96 | });
97 | },
98 |
99 | // Create Coin 7 day history table, consists of hourly data points.
100 | createCoinWeekHistoryTable: function(req, res, next) {
101 | const CREATE_WEEK_HISTORY_TABLE = "CREATE TABLE IF NOT EXISTS tokenplex.week_history_data" +
102 | "(symbol varchar," +
103 | "tosymbol varchar," +
104 | "time timestamp," +
105 | "high float," +
106 | "low float," +
107 | "open float," +
108 | "close float," +
109 | "volumefrom float," +
110 | "volumeto float," +
111 | "close float, PRIMARY KEY(symbol, time))";
112 | const DELETE_WEEK_HISTORY_TABLE = "DROP TABLE IF EXISTS tokenplex.week_history_data";
113 |
114 | cassandraClient.connect()
115 | .then(function () {
116 | return cassandraClient.execute(DELETE_WEEK_HISTORY_TABLE).then(function () {
117 | return cassandraClient.execute(CREATE_WEEK_HISTORY_TABLE)
118 | })
119 | .then(function (createTableResponse) {
120 | res.send({data: createTableResponse});
121 |
122 | }).catch(function (err) {
123 | res.send({"error": err});
124 | });
125 | });
126 | },
127 |
128 | // Create Coin 1 year history table, consists of daily data points.
129 | createCoinYearlyHistoryTable: function(req, res, next) {
130 | const CREATE_ALL_TIME_HISTORY_TABLE = "CREATE TABLE IF NOT EXISTS tokenplex.year_history_data" +
131 | "(symbol varchar," +
132 | "tosymbol varchar,"+
133 | " time varchar," +
134 | "high float," +
135 | "low float," +
136 | "open float," +
137 | "close float," +
138 | "volumefrom float," +
139 | "volumeto float," +
140 | "close float, PRIMARY KEY(symbol, tosymbol, time))";
141 | const DELETE_ALL_TIME_HISTORY_TABLE = "DROP TABLE IF EXISTS tokenplex.year_history_data";
142 | cassandraClient.connect()
143 | .then(function () {
144 | return cassandraClient.execute(DELETE_ALL_TIME_HISTORY_TABLE).then(function () {
145 | return cassandraClient.execute(CREATE_ALL_TIME_HISTORY_TABLE)
146 | })
147 | .then(function (createTableResponse) {
148 | logger.log({"level": "info", "message": "All time history data table created"})
149 | res.send({data: createTableResponse});
150 | return createTableResponse;
151 | }).catch(function (err) {
152 | logger.log({"level": "error", "message": JSON.stringify(err)})
153 | return err;
154 | });
155 | });
156 | },
157 |
158 | // Create Coin All Time history table
159 | createAllTimeHistoryTable: function(req, res, next) {
160 | const CREATE_ALL_TIME_HISTORY_TABLE = "CREATE TABLE IF NOT EXISTS tokenplex.coin_all_time_history_data" +
161 | "(symbol varchar," +
162 | " time timestamp," +
163 | "high float," +
164 | "low float," +
165 | "open float," +
166 | "volumefrom float," +
167 | "volumeto float," +
168 | "close float, PRIMARY KEY(symbol, time))";
169 | const DELETE_ALL_TIME_HISTORY_TABLE = "DROP TABLE IF EXISTS tokenplex.coin_all_time_history_data";
170 | cassandraClient.connect()
171 | .then(function () {
172 | return cassandraClient.execute(DELETE_ALL_TIME_HISTORY_TABLE).then(function () {
173 | return cassandraClient.execute(CREATE_ALL_TIME_HISTORY_TABLE)
174 | })
175 | .then(function (createTableResponse) {
176 | res.send({data: createTableResponse});
177 | return cassandraClient.metadata.getTable('tokenplex', 'coin_all_time_history_data');
178 |
179 | }).catch(function (err) {
180 | res.send({"error": err});
181 | });
182 | });
183 | },
184 |
185 | // Create Coin Snapshot table
186 | createCoinSnapshotTable: function(req, res, next) {
187 | const CREATE_COIN_SNAPSHOT_TABLE = "CREATE TABLE IF NOT EXISTS tokenplex.coin_details" +
188 | "(type varchar," +
189 | "exchange varchar," +
190 | "open24hour varchar,"+
191 | "fromsymbol varchar," +
192 | "tosymbol varchar," +
193 | "flags varchar," +
194 | "price varchar," +
195 | "lastupdate varchar," +
196 | "lastvolume varchar," +
197 | "lastvolumeto varchar," +
198 | "lasttradeid varchar," +
199 | "volume24hour varchar," +
200 | "volume24hourto varchar," +
201 | "openhourto varchar," +
202 | "high24hour varchar," +
203 | "low24hour varchar," +
204 | "PRIMARY KEY(fromsymbol, tosymbol, exchange))";
205 | const DELETE_COIN_SNAPSHOT_TABLE = "DROP TABLE IF EXISTS tokenplex.coin_details";
206 | DBConnection.getCassandraClientConnection()
207 | .then(function () {
208 | return cassandraClient.execute(DELETE_COIN_SNAPSHOT_TABLE).then(function () {
209 | return cassandraClient.execute(CREATE_COIN_SNAPSHOT_TABLE)
210 | })
211 | .then(function (createTableResponse) {
212 | res.send({data: createTableResponse});
213 |
214 | }).catch(function (err) {
215 | res.send({"error": err});
216 | });
217 | });
218 | },
219 |
220 | createExchangeTable: function(req, res) {
221 | const Delete_Exchange_Table = "DROP TABLE IF EXISTS tokenplex.exchanges";
222 | const Create_Exchange_Table = "CREATE TABLE IF NOT EXISTS tokenplex.exchanges" +
223 | " (TimeStamp TIMESTAMP," +
224 | "exch_id varchar," +
225 | "exch_name varchar," +
226 | "exch_code varchar," +
227 | "exch_fee varchar," +
228 | "exch_trade_enabled varchar," +
229 | "exch_balance_enabled varchar," +
230 | "exch_url varchar," +
231 | "PRIMARY KEY(exch_id))";
232 |
233 | cassandraClient.execute(Delete_Exchange_Table).then(function(deleteTableResponse){
234 |
235 | return cassandraClient.execute(Create_Exchange_Table);
236 | })
237 | .then(function(createTableResponse){
238 | return res.send({data: createTableResponse});
239 | })
240 | .catch(function (err) {
241 | console.log(err);
242 | res.send({error:err});
243 | return err;
244 | });
245 | },
246 |
247 | createCoinSocialTable: function(req, res) {
248 | const Delete_Social_Table = "DROP TABLE IF EXISTS tokenplex.coin_social";
249 | const Create_Social_Table = "CREATE TABLE IF NOT EXISTS tokenplex.coin_social" +
250 | " (TimeStamp TIMESTAMP," +
251 | " id varchar," +
252 | " Reddit text," +
253 | " Facebook text," +
254 | " Twitter text," +
255 | " CodeRepository Text, PRIMARY KEY(id))";
256 |
257 | cassandraClient.execute(Delete_Social_Table).then(function(deleteTableResponse){
258 | return cassandraClient.execute(Create_Social_Table);
259 | })
260 | .then(function(createTableResponse){
261 | res.send({data: createTableResponse});
262 | })
263 | .catch(function (err) {
264 | console.log(err);
265 | return cassandraClient.shutdown();
266 | });
267 | },
268 |
269 | createAllTables: function(req, res) {
270 | createKeySpace().then(function(createKeySpaceResponse) {
271 | logger.log({"level": "info", "message": "create table request submitted"});
272 | createCoinDailyHistoryTable();
273 | createCoinWeeklyHistoryTable();
274 | createCoinYearlyHistoryTable();
275 | createCoinSocialTable();
276 | createCoinSnapshotTable();
277 | createExchangeMarketDetailTable();
278 | createTokenExchangeListTable();
279 | createExchangeOHLCVTable();
280 |
281 | });
282 | res.send({"data": "Create Table Request submitted"});
283 | }
284 | }
285 |
286 | function createKeySpace() {
287 | return DBConnection.getCassandraClientConnection()
288 | .then(function () {
289 | const query = "CREATE KEYSPACE IF NOT EXISTS tokenplex WITH replication =" +
290 | "{'class': 'SimpleStrategy', 'replication_factor': '1' }";
291 | return cassandraClient.execute(query).then(function(response){
292 | return response;
293 | });
294 | }).catch(function(err){
295 | logger.log({"level": "error", "message": JSON.stringify(err)});
296 | return err;
297 | });
298 | }
299 |
300 | function createCoinSnapshotTable() {
301 | const CREATE_COIN_SNAPSHOT_TABLE = "CREATE TABLE IF NOT EXISTS tokenplex.coin_details" +
302 | "(type varchar," +
303 | "exchange varchar," +
304 | "open24hour varchar,"+
305 | "fromsymbol varchar," +
306 | "tosymbol varchar," +
307 | "flags varchar," +
308 | "price varchar," +
309 | "lastupdate varchar," +
310 | "lastvolume varchar," +
311 | "lastvolumeto varchar," +
312 | "lasttradeid varchar," +
313 | "volume24hour varchar," +
314 | "volume24hourto varchar," +
315 | "openhourto varchar," +
316 | "high24hour varchar," +
317 | "low24hour varchar," +
318 | "PRIMARY KEY(fromsymbol, tosymbol, exchange))";
319 | const DELETE_COIN_SNAPSHOT_TABLE = "DROP TABLE IF EXISTS tokenplex.coin_details";
320 | return cassandraClient.execute(DELETE_COIN_SNAPSHOT_TABLE).then(function () {
321 | return cassandraClient.execute(CREATE_COIN_SNAPSHOT_TABLE)
322 | })
323 | .then(function (createTableResponse) {
324 | logger.log({"level": "info", "details": "created coin snapshot table"});
325 | return (createTableResponse);
326 | }).catch(function (err) {
327 | return (err);
328 | });
329 | }
330 |
331 | function createCoinSocialTable() {
332 | const Delete_Social_Table = "DROP TABLE IF EXISTS tokenplex.coin_social";
333 | const Create_Social_Table = "CREATE TABLE IF NOT EXISTS tokenplex.coin_social" +
334 | " (TimeStamp TIMESTAMP," +
335 | " id varchar," +
336 | " Reddit text," +
337 | " Facebook text," +
338 | " Twitter text," +
339 | " CodeRepository Text, PRIMARY KEY(id))";
340 |
341 | return cassandraClient.execute(Delete_Social_Table).then(function(){
342 | return cassandraClient.execute(Create_Social_Table);
343 | })
344 | .then(function(createTableResponse){
345 | logger.log({
346 | "level": "info", "message": "Coin Social table created"
347 | });
348 | return createTableResponse;
349 | })
350 | .catch(function (err) {
351 | logger.log({"level": "error", "message": JSON.stringify(err)})
352 | return err;
353 | });
354 | }
355 |
356 | function createCoinDailyHistoryTable() {
357 | const CREATE_DAILY_HISTORY_TABLE = "CREATE TABLE IF NOT EXISTS tokenplex.coin_daily_history_data" +
358 | "( symbol varchar," +
359 | " time timestamp," +
360 | " high float," +
361 | " low float," +
362 | " open float," +
363 | " volumefrom float," +
364 | " volumeto float," +
365 | " close float, PRIMARY KEY(symbol, time))";
366 | const DELETE_DAILY_HISTORY_TABLE = "DROP TABLE IF EXISTS tokenplex.coin_daily_history_data";
367 | return cassandraClient.execute(DELETE_DAILY_HISTORY_TABLE).then(function () {
368 | return cassandraClient.execute(CREATE_DAILY_HISTORY_TABLE)
369 | }).then(function (createTableResponse) {
370 | logger.log({
371 | "level": "info", "message": "Daily History Table created"
372 | });
373 | return({data: createTableResponse});
374 | }).catch(function (err) {
375 | logger.log({"level": "error", "message": JSON.stringify(err)})
376 | return {error: err};
377 | });
378 | }
379 |
380 | function createCoinWeeklyHistoryTable() {
381 | const CREATE_WEEK_HISTORY_TABLE = "CREATE TABLE IF NOT EXISTS tokenplex.week_history_data" +
382 | "(symbol varchar," +
383 | " time varchar," +
384 | "tosymbol varchar," +
385 | "high float," +
386 | "low float," +
387 | "open float," +
388 | "volumefrom float," +
389 | "volumeto float," +
390 | "close float, PRIMARY KEY(symbol, tosymbol, time))";
391 | const DELETE_WEEK_HISTORY_TABLE = "DROP TABLE IF EXISTS tokenplex.week_history_data";
392 | return cassandraClient.execute(DELETE_WEEK_HISTORY_TABLE).then(function () {
393 | return cassandraClient.execute(CREATE_WEEK_HISTORY_TABLE)
394 | }).then(function (createTableResponse) {
395 | logger.log({"level": "info", "message": "Weekly history table created week_history_data"});
396 | return createTableResponse;
397 | }).catch(function (err) {
398 | logger.log({"level": "error", "message": JSON.stringify(err)});
399 | return err;
400 | });
401 | }
402 |
403 | function createCoinYearlyHistoryTable() {
404 | const CREATE_YEAR_HISTORY_TABLE = "CREATE TABLE IF NOT EXISTS tokenplex.year_history_data" +
405 | "(symbol varchar," +
406 | "tosymbol varchar," +
407 | "time varchar," +
408 | "high float," +
409 | "low float," +
410 | "open float," +
411 | "volumefrom float," +
412 | "volumeto float," +
413 | "close float, PRIMARY KEY(symbol, tosymbol, time))";
414 | const DELETE_YEAR_HISTORY_TABLE = "DROP TABLE IF EXISTS tokenplex.year_history_data";
415 |
416 | cassandraClient.connect()
417 | .then(function () {
418 | return cassandraClient.execute(DELETE_YEAR_HISTORY_TABLE).then(function () {
419 | return cassandraClient.execute(CREATE_YEAR_HISTORY_TABLE)
420 | })
421 | .then(function (createTableResponse) {
422 | logger.log({"level": "info", "message": "Yearly history data table created year_history_data"})
423 | return createTableResponse;
424 | }).catch(function (err) {
425 | logger.log({"level": "error", "message": JSON.stringify(err)})
426 | return err;
427 | });
428 | });
429 | }
430 |
431 | function createExchangeMarketDetailTable() {
432 | const createMarketsListQuery = "CREATE TABLE IF NOT EXISTS tokenplex.market_detail" +
433 | "(exchange varchar," +
434 | "detail text," +
435 | "lastupdate timestamp," +
436 | " PRIMARY KEY (exchange))";
437 | const deleteQuery = "DROP TABLE IF EXISTS tokenplex.market_detail";
438 | cassandraClient.connect()
439 | .then(function () {
440 | return cassandraClient.execute(deleteQuery).then(function (delQueryResponse) {
441 | return cassandraClient.execute(createMarketsListQuery).then(function (createTableResponse) {
442 | logger.log({"level": "info", "message": "markets_detail table created"});
443 | });
444 | })
445 | }).catch(function(ex){
446 | logger.log({"level": "error", "message": ex});
447 | });
448 | }
449 |
450 | function createTokenExchangeListTable() {
451 | const createMarketsListQuery = "CREATE TABLE IF NOT EXISTS tokenplex.token_exchange_list" +
452 | "(market map>>," +
453 | "base varchar," +
454 | "timestamp timestamp," +
455 | "quoteId varchar, PRIMARY KEY (base))";
456 | const deleteQuery = "DROP TABLE IF EXISTS tokenplex.markets_list";
457 | cassandraClient.connect()
458 | .then(function () {
459 | return cassandraClient.execute(deleteQuery).then(function (delQueryResponse) {
460 | return cassandraClient.execute(createMarketsListQuery).then(function (createTableResponse) {
461 | logger.log({"level": "info", "message": "markets_list table created"});
462 | });
463 | })
464 | }).catch(function(err){
465 | logger.log({"level": "error", "message": err});
466 | })
467 |
468 | }
469 |
470 | function createExchangeOHLCVTable() {
471 | const createDaySampleOLHCVQuery = "CREATE TABLE IF NOT EXISTS tokenplex.exchange_day_sample_history"+
472 | "(base varchar," +
473 | "quote varchar," +
474 | "detail text," +
475 | "exchange varchar,"+
476 | "PRIMARY KEY (exchange, base, quote))";
477 |
478 | const createHourSampleOLHCVQuery = "CREATE TABLE IF NOT EXISTS tokenplex.exchange_hour_sample_history"+
479 | "(base varchar," +
480 | "quote varchar," +
481 | "detail text," +
482 | "exchange varchar,"+
483 | "PRIMARY KEY (exchange, base, quote))";
484 |
485 | const createMinSampleOLHCVQuery = "CREATE TABLE IF NOT EXISTS tokenplex.exchange_min_sample_history"+
486 | "(base varchar," +
487 | "quote varchar," +
488 | "detail text," +
489 | "exchange varchar,"+
490 | "PRIMARY KEY (exchange, base, quote))";
491 |
492 | const deleteMinSampleQuery = "DROP TABLE IF EXISTS tokenplex.exchange_min_sample_history";
493 | const deleteHourSampleQuery = "DROP TABLE IF EXISTS tokenplex.exchange_hour_sample_history";
494 | const deleteDaySampleQuery = "DROP TABLE IF EXISTS tokenplex.exchange_day_sample_history";
495 |
496 | cassandraClient.connect()
497 | .then(function () {
498 | cassandraClient.execute(deleteMinSampleQuery).then(function (response) {
499 | return response;
500 | }).then(function (response) {
501 | cassandraClient.execute(deleteHourSampleQuery).then(function (response) {
502 | return response;
503 | }).then(function () {
504 | cassandraClient.execute(deleteDaySampleQuery).then(function (response) {
505 | return response;
506 | }).then(function () {
507 | cassandraClient.execute(createMinSampleOLHCVQuery).then(function (response) {
508 | return response;
509 | }).then(function (response) {
510 | cassandraClient.execute(createHourSampleOLHCVQuery).then(function (response) {
511 | return response;
512 | }).then(function () {
513 | cassandraClient.execute(createDaySampleOLHCVQuery).then(function (response) {
514 | logger.log({level: "info", "message": "created exchange ohlcv tables"});
515 | return response;
516 | })
517 | })
518 | })
519 | })
520 | })
521 | })
522 | }).catch(function(err){
523 | logger.log({"level": "error", "message": err});
524 | });
525 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
--------------------------------------------------------------------------------