mirror of
https://github.com/fullstack-cash/bch-api.git
synced 2026-09-22 09:12:05 -07:00
Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb0fba6924 | ||
|
|
ed6c612be9 | ||
|
|
7cafb11c0d | ||
|
|
dce665b174 | ||
|
|
43c1ee9b69 | ||
|
|
4b95fa127c | ||
|
|
9969ccafac | ||
|
|
5f1e7d88bb | ||
|
|
1a115a0012 | ||
|
|
442b802b79 | ||
|
|
9e895bb0bd | ||
|
|
0a0644d05c | ||
|
|
1a57177e4a | ||
|
|
039348aa24 | ||
|
|
2c147d518e | ||
|
|
fb78033a67 | ||
|
|
33d1c74143 | ||
|
|
7b927da53b | ||
|
|
74c8ceb962 | ||
|
|
83a5db5ac7 | ||
|
|
a1366bf46f |
@@ -18,6 +18,7 @@ USER safeuser
|
||||
WORKDIR /home/safeuser
|
||||
RUN git clone https://github.com/Permissionless-Software-Foundation/bch-api
|
||||
WORKDIR /home/safeuser/bch-api
|
||||
RUN git checkout basic-auth-only
|
||||
RUN npm install --silent
|
||||
|
||||
# Generate documentation
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
The rate limits below were originially coded with the idea of charging on a
|
||||
per-resource basis. However, that was confusing to end users trying to purchase
|
||||
a subscription. So everything was simplied to two tiers: paid and anonymous
|
||||
|
||||
CT 3/4/21: I increased the total points from 1,000 to 100,000 to prevent systems
|
||||
with Basic Authentication from hitting internal rate limits when calling
|
||||
hydrateUtxos().
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
@@ -19,7 +23,9 @@ const jwt = require('jsonwebtoken')
|
||||
const wlogger = require('../util/winston-logging')
|
||||
const config = require('../../config')
|
||||
|
||||
const ANON_LIMITS = config.anonRateLimit
|
||||
// Hard coding limits since basic-authentiation is assumed to be the primary access.
|
||||
const ANON_LIMITS = 333
|
||||
|
||||
const WHITELIST_RATE_LIMIT = config.whitelistRateLimit
|
||||
const WHITELIST_DOMAINS = config.whitelistDomains
|
||||
const INTERNAL_RATE_LIMIT = 1
|
||||
@@ -142,7 +148,7 @@ class RateLimits {
|
||||
let rateLimit = ANON_LIMITS
|
||||
|
||||
// Only evaluate the JWT token if the user is not using Basic Authentication.
|
||||
if (!req.locals.proLimit) {
|
||||
if (!req.locals.proLimit && !req.body.usrObj.proLimit) {
|
||||
// Code here for the rate limiter is adapted from this example:
|
||||
// https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#authorized-and-not-authorized-users
|
||||
try {
|
||||
@@ -195,7 +201,7 @@ class RateLimits {
|
||||
`User ${key} consuming ${pointsToConsume} point for resource ${resource}.`
|
||||
)
|
||||
|
||||
rateLimit = Math.floor(1000 / pointsToConsume)
|
||||
rateLimit = Math.floor(100000 / pointsToConsume)
|
||||
|
||||
// Update the key so that rate limits track both the user and the resource.
|
||||
key = `${key}-${resource}`
|
||||
|
||||
@@ -27,11 +27,15 @@ class Price {
|
||||
this.coinexPriceUrl =
|
||||
'https://api.coinex.com/v1/market/ticker?market=bchausdt'
|
||||
|
||||
this.bchCoinexPriceUrl =
|
||||
'https://api.coinex.com/v1/market/ticker?market=bchusdt'
|
||||
|
||||
this.router = express.Router()
|
||||
this.router.get('/', _this.root)
|
||||
this.router.get('/usd', _this.getUSD)
|
||||
this.router.get('/rates', _this.getBCHRate)
|
||||
this.router.get('/bchausd', _this.getBCHAUSD)
|
||||
this.router.get('/bchusd', _this.getBCHUSD)
|
||||
}
|
||||
|
||||
// DRY error handler.
|
||||
@@ -150,6 +154,40 @@ class Price {
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /price/bchusd Get the USD price of BCH
|
||||
* @apiName Get the USD price of BCH
|
||||
* @apiGroup Price
|
||||
* @apiDescription Get the USD price of BCH from Coinex.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -X GET "https://api.fullstack.cash/v4/price/bchusd" -H "accept: application/json"
|
||||
*
|
||||
*/
|
||||
async getBCHUSD (req, res, next) {
|
||||
try {
|
||||
// Request options
|
||||
const opt = {
|
||||
method: 'get',
|
||||
baseURL: _this.bchCoinexPriceUrl,
|
||||
timeout: 15000
|
||||
}
|
||||
|
||||
const response = await axios.request(opt)
|
||||
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
|
||||
|
||||
const price = Number(response.data.data.ticker.last)
|
||||
|
||||
return res.json({ usd: price })
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
wlogger.error('Error in price.js/getBCHUSD().', err)
|
||||
|
||||
return _this.errorHandler(err, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Price
|
||||
|
||||
+14
-5
@@ -36,9 +36,13 @@ util.inspect.defaultOptions = { depth: 5 }
|
||||
// Determine the Access password for a private instance of SLPDB.
|
||||
// https://gist.github.com/christroutner/fc717ca704dec3dded8b52fae387eab2
|
||||
// Password for General Purpose (GP) SLPDB.
|
||||
const SLPDB_PASS_GP = process.env.SLPDB_PASS_GP ? process.env.SLPDB_PASS_GP : 'BITBOX'
|
||||
const SLPDB_PASS_GP = process.env.SLPDB_PASS_GP
|
||||
? process.env.SLPDB_PASS_GP
|
||||
: 'BITBOX'
|
||||
// Password for Whitelist (WL) SLPDB.
|
||||
const SLPDB_PASS_WL = process.env.SLPDB_PASS_WL ? process.env.SLPDB_PASS_WL : 'BITBOX'
|
||||
const SLPDB_PASS_WL = process.env.SLPDB_PASS_WL
|
||||
? process.env.SLPDB_PASS_WL
|
||||
: 'BITBOX'
|
||||
|
||||
// const rawtransactions = require('./full-node/rawtransactions')
|
||||
const RawTransactions = require('./full-node/rawtransactions')
|
||||
@@ -46,7 +50,7 @@ const rawTransactions = new RawTransactions()
|
||||
|
||||
// Setup REST and TREST URLs used by slpjs
|
||||
// Dev note: this allows for unit tests to mock the URL.
|
||||
if (!process.env.REST_URL) process.env.REST_URL = 'https://bchn.fullstack.cash/v4/'
|
||||
if (!process.env.REST_URL) { process.env.REST_URL = 'https://bchn.fullstack.cash/v4/' }
|
||||
if (!process.env.TREST_URL) {
|
||||
process.env.TREST_URL = 'https://testnet.fullstack.cash/v4/'
|
||||
}
|
||||
@@ -1213,10 +1217,13 @@ class Slp {
|
||||
msg: ''
|
||||
}
|
||||
|
||||
const path = `${process.env.SLP_API_URL}slp/validate/${txid}`
|
||||
// console.log(`validate2Single path: ${path}`)
|
||||
|
||||
// Request options
|
||||
const opt = {
|
||||
method: 'get',
|
||||
baseURL: `${process.env.SLP_API_URL}slp/validate/${txid}`,
|
||||
baseURL: path,
|
||||
timeout: 10000 // Exit after 10 seconds.
|
||||
}
|
||||
const tokenRes = await _this.axios.request(opt)
|
||||
@@ -2117,7 +2124,9 @@ class Slp {
|
||||
// console.log(`theseUtxos: ${JSON.stringify(theseUtxos, null, 2)}`)
|
||||
|
||||
// Get SLP token details.
|
||||
const details = await _this.bchjs.SLP.Utils.tokenUtxoDetailsWL(theseUtxos)
|
||||
const details = await _this.bchjs.SLP.Utils.tokenUtxoDetailsWL(
|
||||
theseUtxos
|
||||
)
|
||||
// console.log('details : ', details)
|
||||
|
||||
// Replace the original UTXO data with the hydrated data.
|
||||
|
||||
@@ -39,6 +39,14 @@ describe('#price', () => {
|
||||
const result = await price.getBCHAUSD(req, res)
|
||||
console.log(`result: ${util.inspect(result)}`)
|
||||
|
||||
assert.isNumber(result.usd)
|
||||
})
|
||||
})
|
||||
describe('#getBCHUSD', () => {
|
||||
it('should get the USD price of BCH', async () => {
|
||||
const result = await price.getBCHUSD(req, res)
|
||||
console.log(`result: ${util.inspect(result)}`)
|
||||
|
||||
assert.isNumber(result.usd)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -277,4 +277,70 @@ describe('#PriceRouter', () => {
|
||||
assert.isNumber(result.usd)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getBCHUSD', () => {
|
||||
// const getNetworkInfo = controlRoute.testableComponents.getNetworkInfo
|
||||
|
||||
it('should throw 500 when network issues', async () => {
|
||||
uut.bchCoinexPriceUrl = 'http://fakeurl/api/'
|
||||
|
||||
await uut.getBCHUSD(req, res)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
|
||||
assert.isAbove(
|
||||
res.statusCode,
|
||||
499,
|
||||
'HTTP status code 500 or greater expected.'
|
||||
)
|
||||
// console.log(res)
|
||||
assert.include(
|
||||
res.output.error,
|
||||
'Network error: Could not communicate with full node or other external service'
|
||||
)
|
||||
})
|
||||
|
||||
it('returns proper error when downstream service stalls', async () => {
|
||||
// Mock the timeout error.
|
||||
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' })
|
||||
|
||||
const result = await uut.getBCHUSD(req, res)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
|
||||
assert.include(
|
||||
result.error,
|
||||
'Could not communicate with full node',
|
||||
'Error message expected'
|
||||
)
|
||||
})
|
||||
|
||||
it('returns proper error when downstream service is down', async () => {
|
||||
// Mock the timeout error.
|
||||
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' })
|
||||
|
||||
const result = await uut.getBCHUSD(req, res)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
|
||||
assert.include(
|
||||
result.error,
|
||||
'Could not communicate with full node',
|
||||
'Error message expected'
|
||||
)
|
||||
})
|
||||
|
||||
it('should get the USD price of BCH', async () => {
|
||||
// Mock the RPC call for unit tests.
|
||||
if (process.env.TEST === 'unit') {
|
||||
sandbox
|
||||
.stub(uut.axios, 'request')
|
||||
.resolves({ data: mockData.mockCoinexFeed })
|
||||
}
|
||||
|
||||
const result = await uut.getBCHUSD(req, res)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
|
||||
assert.isNumber(result.usd)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user