INIT
This commit is contained in:
commit
fb63f4e3c2
21
.eslintrc
Normal file
21
.eslintrc
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"plugins": [
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
|
"extends": ["prettier"],
|
||||||
|
"rules": {
|
||||||
|
'prettier/prettier': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
singleQuote: true,
|
||||||
|
printWidth: 140,
|
||||||
|
trailingComma: 'all'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"env":{
|
||||||
|
"es6": true
|
||||||
|
},
|
||||||
|
"globals": { "fetch": false }
|
||||||
|
}
|
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
admin.macaroon
|
||||||
|
tls.cert
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
node_modules/
|
||||||
|
# misc
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# OSX# OSX
|
||||||
|
#
|
||||||
|
.DS_Store
|
205
class/User.js
Normal file
205
class/User.js
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
var crypto = require('crypto');
|
||||||
|
import { BigNumber } from 'bignumber.js';
|
||||||
|
|
||||||
|
export class User {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Redis} redis
|
||||||
|
*/
|
||||||
|
constructor(redis, bitcoindrpc) {
|
||||||
|
this._redis = redis;
|
||||||
|
this._bitcoindrpc = bitcoindrpc;
|
||||||
|
this._userid = false;
|
||||||
|
this._login = false;
|
||||||
|
this._password = false;
|
||||||
|
this._balance = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserId() {
|
||||||
|
return this._userid;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLogin() {
|
||||||
|
return this._login;
|
||||||
|
}
|
||||||
|
getPassword() {
|
||||||
|
return this._password;
|
||||||
|
}
|
||||||
|
getAccessToken() {
|
||||||
|
return this._acess_token;
|
||||||
|
}
|
||||||
|
getRefreshToken() {
|
||||||
|
return this._refresh_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadByAuthorization(authorization) {
|
||||||
|
let access_token = authorization.replace('Bearer ', '');
|
||||||
|
let userid = await this._redis.get('userid_for_' + access_token);
|
||||||
|
|
||||||
|
if (userid) {
|
||||||
|
this._userid = userid;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadByRefreshToken(refresh_token) {
|
||||||
|
let userid = await this._redis.get('userid_for_' + refresh_token);
|
||||||
|
if (userid) {
|
||||||
|
this._userid = userid;
|
||||||
|
await this._generateTokens();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async create() {
|
||||||
|
let buffer = crypto.randomBytes(20);
|
||||||
|
let login = buffer.toString('hex');
|
||||||
|
|
||||||
|
buffer = crypto.randomBytes(20);
|
||||||
|
let password = buffer.toString('hex');
|
||||||
|
|
||||||
|
buffer = crypto.randomBytes(48);
|
||||||
|
let userid = buffer.toString('hex');
|
||||||
|
this._login = login;
|
||||||
|
this._password = password;
|
||||||
|
this._userid = userid;
|
||||||
|
await this._saveUserToDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadByLoginAndPassword(login, password) {
|
||||||
|
let userid = await this._redis.get('user_' + login + '_' + this._hash(password));
|
||||||
|
|
||||||
|
if (userid) {
|
||||||
|
this._userid = userid;
|
||||||
|
this._login = login;
|
||||||
|
this._password = password;
|
||||||
|
await this._generateTokens();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAddress() {
|
||||||
|
return await this._redis.get('bitcoin_address_for_' + this._userid);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBalance() {
|
||||||
|
return (await this._redis.get('balance_for_' + this._userid)) * 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveBalance(balance) {
|
||||||
|
return await this._redis.set('balance_for_' + this._userid, balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
async savePaidLndInvoice(doc) {
|
||||||
|
return await this._redis.rpush('txs_for_' + this._userid, JSON.stringify(doc));
|
||||||
|
}
|
||||||
|
|
||||||
|
async addAddress(address) {
|
||||||
|
await this._redis.set('bitcoin_address_for_' + this._userid, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User's onchain txs that are >= 3 confs
|
||||||
|
*
|
||||||
|
* @returns {Promise<Array>}
|
||||||
|
*/
|
||||||
|
async getTxs() {
|
||||||
|
let addr = await this.getAddress();
|
||||||
|
let txs = await this._bitcoindrpc.request('listtransactions', [addr, 100500, 0, true]);
|
||||||
|
txs = txs.result;
|
||||||
|
let result = [];
|
||||||
|
for (let tx of txs) {
|
||||||
|
if (tx.confirmations >= 3) {
|
||||||
|
tx.type = 'bitcoind_tx';
|
||||||
|
result.push(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let range = await this._redis.lrange('txs_for_' + this._userid, 0, -1);
|
||||||
|
for (let invoice of range) {
|
||||||
|
invoice = JSON.parse(invoice);
|
||||||
|
invoice.type = 'paid_invoice';
|
||||||
|
invoice.fee = parseInt(invoice.payment_route.total_fees_msat / 1000);
|
||||||
|
invoice.value = parseInt((invoice.payment_route.total_fees_msat + invoice.payment_route.total_amt_msat) / 1000);
|
||||||
|
result.push(invoice);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returning onchain txs for user's address that are less than 3 confs
|
||||||
|
*
|
||||||
|
* @returns {Promise<Array>}
|
||||||
|
*/
|
||||||
|
async getPendingTxs() {
|
||||||
|
let addr = await this.getAddress();
|
||||||
|
let txs = await this._bitcoindrpc.request('listtransactions', [addr, 100500, 0, true]);
|
||||||
|
txs = txs.result;
|
||||||
|
let result = [];
|
||||||
|
for (let tx of txs) {
|
||||||
|
if (tx.confirmations < 3) {
|
||||||
|
result.push(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _generateTokens() {
|
||||||
|
let buffer = crypto.randomBytes(20);
|
||||||
|
this._acess_token = buffer.toString('hex');
|
||||||
|
|
||||||
|
buffer = crypto.randomBytes(20);
|
||||||
|
this._refresh_token = buffer.toString('hex');
|
||||||
|
|
||||||
|
await this._redis.set('userid_for_' + this._acess_token, this._userid);
|
||||||
|
await this._redis.set('userid_for_' + this._refresh_token, this._userid);
|
||||||
|
await this._redis.set('access_token_for_' + this._userid, this._acess_token);
|
||||||
|
await this._redis.set('refresh_token_for_' + this._userid, this._refresh_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _saveUserToDatabase() {
|
||||||
|
let key;
|
||||||
|
await this._redis.set((key = 'user_' + this._login + '_' + this._hash(this._password)), this._userid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all onchain txs for user's address, and compares them to
|
||||||
|
* already imported txids (stored in database); Ones that are not imported -
|
||||||
|
* get their balance added to user's balance, and its txid added to 'imported' list.
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async accountForPosibleTxids() {
|
||||||
|
let imported_txids = await this._redis.lrange('imported_txids_for_' + this._userid, 0, -1);
|
||||||
|
console.log(':::::::::::imported_txids', imported_txids);
|
||||||
|
let onchain_txs = await this.getTxs();
|
||||||
|
for (let tx of onchain_txs) {
|
||||||
|
if (tx.type !== 'bitcoind_tx') continue;
|
||||||
|
let already_imported = false;
|
||||||
|
for (let imported_txid of imported_txids) {
|
||||||
|
if (tx.txid === imported_txid) already_imported = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!already_imported && tx.category === 'receive') {
|
||||||
|
let userBalance = await this.getBalance();
|
||||||
|
userBalance += new BigNumber(tx.amount).multipliedBy(100000000).toNumber();
|
||||||
|
await this.saveBalance(userBalance);
|
||||||
|
await this._redis.rpush('imported_txids_for_' + this._userid, tx.txid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_hash(string) {
|
||||||
|
return crypto
|
||||||
|
.createHash('sha256')
|
||||||
|
.update(string)
|
||||||
|
.digest()
|
||||||
|
.toString('hex');
|
||||||
|
}
|
||||||
|
}
|
1
class/index.js
Normal file
1
class/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './User';
|
22
config.js
Normal file
22
config.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
let config = {
|
||||||
|
bitcoind: {
|
||||||
|
rpc: 'http://login:password@1.1.1.1:8332',
|
||||||
|
},
|
||||||
|
redis: {
|
||||||
|
port: 12914,
|
||||||
|
host: '1.1.1.1',
|
||||||
|
family: 4,
|
||||||
|
password: 'password',
|
||||||
|
db: 0,
|
||||||
|
},
|
||||||
|
lnd: {
|
||||||
|
url: '1.1.1.1:10009',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.env.CONFIG) {
|
||||||
|
console.log('using config from env');
|
||||||
|
config = JSON.parse(process.env.CONFIG)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = config;
|
280
controllers/api.js
Normal file
280
controllers/api.js
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
import { User } from '../class/User';
|
||||||
|
const config = require('../config');
|
||||||
|
let express = require('express');
|
||||||
|
let router = express.Router();
|
||||||
|
let assert = require('assert');
|
||||||
|
console.log('using config', JSON.stringify(config));
|
||||||
|
|
||||||
|
// setup redis
|
||||||
|
var Redis = require('ioredis');
|
||||||
|
var redis = new Redis(config.redis);
|
||||||
|
redis.monitor(function(err, monitor) {
|
||||||
|
monitor.on('monitor', function(time, args, source, database) {
|
||||||
|
console.log('---MONITOR', args);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// setup bitcoind rpc
|
||||||
|
let jayson = require('jayson/promise');
|
||||||
|
let url = require('url');
|
||||||
|
let rpc = url.parse(config.bitcoind.rpc);
|
||||||
|
rpc.timeout = 5000;
|
||||||
|
let bitcoinclient = jayson.client.http(rpc);
|
||||||
|
|
||||||
|
// setup lnd rpc
|
||||||
|
var fs = require('fs');
|
||||||
|
var grpc = require('grpc');
|
||||||
|
var lnrpc = grpc.load('rpc.proto').lnrpc;
|
||||||
|
process.env.GRPC_SSL_CIPHER_SUITES = 'HIGH+ECDSA';
|
||||||
|
var lndCert = fs.readFileSync('tls.cert');
|
||||||
|
var sslCreds = grpc.credentials.createSsl(lndCert);
|
||||||
|
var macaroonCreds = grpc.credentials.createFromMetadataGenerator(function(args, callback) {
|
||||||
|
var macaroon = fs.readFileSync('admin.macaroon').toString('hex');
|
||||||
|
var metadata = new grpc.Metadata();
|
||||||
|
metadata.add('macaroon', macaroon);
|
||||||
|
callback(null, metadata);
|
||||||
|
});
|
||||||
|
var creds = grpc.credentials.combineChannelCredentials(sslCreds, macaroonCreds);
|
||||||
|
var lightning = new lnrpc.Lightning(config.lnd.url, creds);
|
||||||
|
|
||||||
|
// ###################### SMOKE TESTS ########################
|
||||||
|
|
||||||
|
bitcoinclient.request('getblockchaininfo', false, function(err, info) {
|
||||||
|
if (info && info.result && info.result.blocks) {
|
||||||
|
if (info.result.blocks < 550000) {
|
||||||
|
console.error('bitcoind is not caught up');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('bitcoind failure');
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
lightning.getInfo({}, function(err, info) {
|
||||||
|
if (err) {
|
||||||
|
console.error('lnd failure');
|
||||||
|
process.exit(3);
|
||||||
|
}
|
||||||
|
if (info) {
|
||||||
|
if (!info.synced_to_chain) {
|
||||||
|
console.error('lnd not synced');
|
||||||
|
process.exit(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
redis.info(function(err, info) {
|
||||||
|
if (err || !info) {
|
||||||
|
console.error('redis failure');
|
||||||
|
process.exit(5);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ######################## ROUTES ########################
|
||||||
|
|
||||||
|
router.post('/create', async function(req, res) {
|
||||||
|
assert.ok(req.body.partnerid);
|
||||||
|
assert.ok(req.body.partnerid === 'bluewallet');
|
||||||
|
assert.ok(req.body.accounttype);
|
||||||
|
|
||||||
|
let u = new User(redis);
|
||||||
|
await u.create();
|
||||||
|
res.send({ login: u.getLogin(), password: u.getPassword() });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/auth', async function(req, res) {
|
||||||
|
assert.ok((req.body.login && req.body.password) || req.body.refresh_token);
|
||||||
|
|
||||||
|
let u = new User(redis);
|
||||||
|
|
||||||
|
if (req.body.refresh_token) {
|
||||||
|
// need to refresh token
|
||||||
|
if (await u.loadByRefreshToken(req.body.refresh_token)) {
|
||||||
|
res.send({ refresh_token: u.getRefreshToken(), access_token: u.getAccessToken() });
|
||||||
|
} else {
|
||||||
|
return errorBadAuth(res);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// need to authorize user
|
||||||
|
let result = await u.loadByLoginAndPassword(req.body.login, req.body.password);
|
||||||
|
if (result) res.send({ refresh_token: u.getRefreshToken(), access_token: u.getAccessToken() });
|
||||||
|
else errorBadAuth(res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/payinvoice', async function(req, res) {
|
||||||
|
let u = new User(redis);
|
||||||
|
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
||||||
|
return errorBadAuth(res);
|
||||||
|
}
|
||||||
|
assert.ok(req.body.invoice);
|
||||||
|
|
||||||
|
let userBalance = await u.getBalance();
|
||||||
|
|
||||||
|
lightning.decodePayReq({ pay_req: req.body.invoice }, function(err, info) {
|
||||||
|
if (err) return errorNotAValidInvoice(res);
|
||||||
|
|
||||||
|
if (userBalance > info.num_satoshis) {
|
||||||
|
// got enough balance
|
||||||
|
var call = lightning.sendPayment();
|
||||||
|
call.on('data', function(payment) {
|
||||||
|
// payment callback
|
||||||
|
if (payment && payment.payment_route && payment.payment_route.total_amt_msat) {
|
||||||
|
userBalance -= parseInt((payment.payment_route.total_fees_msat + payment.payment_route.total_amt_msat) / 1000);
|
||||||
|
u.saveBalance(userBalance);
|
||||||
|
payment.description = info.description;
|
||||||
|
payment.pay_req = req.body.invoice;
|
||||||
|
payment.decoded = info;
|
||||||
|
u.savePaidLndInvoice(payment);
|
||||||
|
res.send(payment);
|
||||||
|
} else {
|
||||||
|
// payment failed
|
||||||
|
return errorLnd(res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let inv = { payment_request: req.body.invoice };
|
||||||
|
call.write(inv);
|
||||||
|
} else {
|
||||||
|
return errorNotEnougBalance(res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/getbtc', async function(req, res) {
|
||||||
|
let u = new User(redis);
|
||||||
|
await u.loadByAuthorization(req.headers.authorization);
|
||||||
|
|
||||||
|
if (!u.getUserId()) {
|
||||||
|
return errorBadAuth(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
let address = await u.getAddress();
|
||||||
|
if (!address) {
|
||||||
|
lightning.newAddress({ type: 0 }, async function(err, response) {
|
||||||
|
if (err) return errorLnd(res);
|
||||||
|
await u.addAddress(response.address);
|
||||||
|
res.send([{ address: response.address }]);
|
||||||
|
bitcoinclient.request('importaddress', [response.address, response.address, false]);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.send([{ address }]);
|
||||||
|
bitcoinclient.request('importaddress', [address, address, false]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/balance', async function(req, res) {
|
||||||
|
let u = new User(redis, bitcoinclient);
|
||||||
|
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
||||||
|
return errorBadAuth(res);
|
||||||
|
}
|
||||||
|
await u.accountForPosibleTxids();
|
||||||
|
let balance = await u.getBalance();
|
||||||
|
res.send({ BTC: { AvailableBalance: balance } });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/getinfo', async function(req, res) {
|
||||||
|
let u = new User(redis);
|
||||||
|
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
||||||
|
return errorBadAuth(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
lightning.getInfo({}, function(err, info) {
|
||||||
|
if (err) return errorLnd(res);
|
||||||
|
res.send(info);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/gettxs', async function(req, res) {
|
||||||
|
let u = new User(redis, bitcoinclient);
|
||||||
|
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
||||||
|
return errorBadAuth(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
let txs = await u.getTxs();
|
||||||
|
res.send(txs);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/getpending', async function(req, res) {
|
||||||
|
let u = new User(redis, bitcoinclient);
|
||||||
|
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
||||||
|
return errorBadAuth(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
let txs = await u.getPendingTxs();
|
||||||
|
res.send(txs);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/decodeinvoice', async function(req, res) {
|
||||||
|
let u = new User(redis, bitcoinclient);
|
||||||
|
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
||||||
|
return errorBadAuth(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!req.query.invoice) return errorGeneralServerError(res);
|
||||||
|
|
||||||
|
lightning.decodePayReq({ pay_req: req.query.invoice }, function(err, info) {
|
||||||
|
if (err) return errorNotAValidInvoice(res);
|
||||||
|
res.send(info);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/checkrouteinvoice', async function(req, res) {
|
||||||
|
let u = new User(redis, bitcoinclient);
|
||||||
|
if (!(await u.loadByAuthorization(req.headers.authorization))) {
|
||||||
|
return errorBadAuth(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!req.query.invoice) return errorGeneralServerError(res);
|
||||||
|
|
||||||
|
// at the momment does nothing.
|
||||||
|
// TODO: decode and query actual route to destination
|
||||||
|
lightning.decodePayReq({ pay_req: req.query.invoice }, function(err, info) {
|
||||||
|
if (err) return errorNotAValidInvoice(res);
|
||||||
|
res.send(info);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
||||||
|
// ################# HELPERS ###########################
|
||||||
|
|
||||||
|
function errorBadAuth(res) {
|
||||||
|
return res.send({
|
||||||
|
error: true,
|
||||||
|
code: 1,
|
||||||
|
message: 'bad auth',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorNotEnougBalance(res) {
|
||||||
|
return res.send({
|
||||||
|
error: true,
|
||||||
|
code: 2,
|
||||||
|
message: 'not enough balance',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorNotAValidInvoice(res) {
|
||||||
|
return res.send({
|
||||||
|
error: true,
|
||||||
|
code: 4,
|
||||||
|
message: 'not a valid invoice',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorLnd(res) {
|
||||||
|
return res.send({
|
||||||
|
error: true,
|
||||||
|
code: 7,
|
||||||
|
message: 'LND failue',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorGeneralServerError(res) {
|
||||||
|
return res.send({
|
||||||
|
error: true,
|
||||||
|
code: 6,
|
||||||
|
message: 'Server fault',
|
||||||
|
});
|
||||||
|
}
|
24
doc/schema.md
Normal file
24
doc/schema.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
User storage schema
|
||||||
|
===================
|
||||||
|
|
||||||
|
###key - value
|
||||||
|
|
||||||
|
####with TTL:
|
||||||
|
|
||||||
|
* userid_for_{access_token} = {userid}
|
||||||
|
* access_token_for_{userid} = {access_token}
|
||||||
|
* userid_for_{refresh_token} = {userid}
|
||||||
|
* refresh_token_for_{userid} = {access_token}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
####Forever:
|
||||||
|
|
||||||
|
* user_{login}_{password_hash} = {userid}
|
||||||
|
* bitcoin_address_for_{userid} = {address}
|
||||||
|
* balance_for_{userid} = {int}
|
||||||
|
* txs_for_{userid} = [] `serialized paid lnd invoices in a list`
|
||||||
|
* imported_txids_for_{userid} = [] `list of txids processed for this user`
|
||||||
|
|
||||||
|
|
||||||
|
|
37
index.js
Normal file
37
index.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||||
|
let express = require('express');
|
||||||
|
let morgan = require('morgan');
|
||||||
|
let uuid = require('node-uuid');
|
||||||
|
let logger = require('./utils/logger');
|
||||||
|
|
||||||
|
morgan.token('id', function getId(req) {
|
||||||
|
return req.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
let app = express();
|
||||||
|
|
||||||
|
app.use(function(req, res, next) {
|
||||||
|
req.id = uuid.v4();
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
morgan(
|
||||||
|
':id :remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
app.set('trust proxy', 'loopback');
|
||||||
|
|
||||||
|
let bodyParser = require('body-parser');
|
||||||
|
let config = require('./config');
|
||||||
|
|
||||||
|
app.use(bodyParser.urlencoded({ extended: false })); // parse application/x-www-form-urlencoded
|
||||||
|
app.use(bodyParser.json(null)); // parse application/json
|
||||||
|
|
||||||
|
app.use(require('./controllers/api'));
|
||||||
|
|
||||||
|
let server = app.listen(process.env.PORT || 3000, function() {
|
||||||
|
logger.log('BOOTING UP', 'Listening on port ' + (process.env.PORT || 3000));
|
||||||
|
});
|
||||||
|
module.exports = server;
|
5733
package-lock.json
generated
Normal file
5733
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
package.json
Normal file
38
package.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "LndHub",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"dev": "nodemon node_modules/.bin/babel-node index.js",
|
||||||
|
"start": "node node_modules/.bin/babel-node index.js",
|
||||||
|
"lint": "./node_modules/.bin/eslint ./ --fix"
|
||||||
|
},
|
||||||
|
"author": "Igor Korsakov <overtorment@gmail.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"babel": "^6.23.0",
|
||||||
|
"babel-cli": "^6.26.0",
|
||||||
|
"babel-eslint": "^10.0.1",
|
||||||
|
"babel-polyfill": "^6.26.0",
|
||||||
|
"babel-preset-env": "^1.7.0",
|
||||||
|
"babel-preset-es2015": "^6.24.1",
|
||||||
|
"babel-register": "^6.26.0",
|
||||||
|
"bignumber.js": "^8.0.1",
|
||||||
|
"bitcoinjs-lib": "^4.0.2",
|
||||||
|
"eslint": "^5.9.0",
|
||||||
|
"eslint-config-prettier": "^3.3.0",
|
||||||
|
"eslint-plugin-prettier": "^3.0.0",
|
||||||
|
"express": "^4.16.4",
|
||||||
|
"grpc": "^1.17.0-pre1",
|
||||||
|
"ioredis": "^4.2.0",
|
||||||
|
"jayson": "^2.1.0",
|
||||||
|
"morgan": "^1.9.1",
|
||||||
|
"node-uuid": "^1.4.8",
|
||||||
|
"prettier": "^1.15.3",
|
||||||
|
"request": "^2.88.0",
|
||||||
|
"request-promise": "^4.2.2",
|
||||||
|
"winston": "^3.1.0"
|
||||||
|
}
|
||||||
|
}
|
71
utils/logger.js
Normal file
71
utils/logger.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/* + + + + + + + + + + + + + + + + + + + + +
|
||||||
|
* Logger
|
||||||
|
* -----------
|
||||||
|
* a winston instance wrapper
|
||||||
|
*
|
||||||
|
* Author: Michael Samonte
|
||||||
|
*
|
||||||
|
+ + + + + + + + + + + + + + + + + + + + + */
|
||||||
|
let fs = require('fs');
|
||||||
|
let winston = require('winston');
|
||||||
|
let createLogger = winston.createLogger;
|
||||||
|
let format = winston.format;
|
||||||
|
let transports = winston.transports;
|
||||||
|
|
||||||
|
/* + + + + + + + + + + + + + + + + + + + + +
|
||||||
|
// Start
|
||||||
|
+ + + + + + + + + + + + + + + + + + + + + */
|
||||||
|
const { combine, timestamp, printf } = format;
|
||||||
|
const logFormat = printf(info => {
|
||||||
|
return `${info.timestamp} : ${info.level}: [${info.label}] : ${info.message}`;
|
||||||
|
});
|
||||||
|
const logger = createLogger({
|
||||||
|
level: 'info',
|
||||||
|
format: combine(timestamp(), logFormat),
|
||||||
|
transports: [
|
||||||
|
new transports.File({
|
||||||
|
filename: './logs/error.log',
|
||||||
|
level: 'error',
|
||||||
|
}),
|
||||||
|
new transports.File({
|
||||||
|
filename: './logs/out.log',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create logs folder if it does not exist
|
||||||
|
*/
|
||||||
|
if (!fs.existsSync('logs')) {
|
||||||
|
fs.mkdirSync('logs');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} label group label
|
||||||
|
* @param {string} message log message
|
||||||
|
*/
|
||||||
|
function log(label, message) {
|
||||||
|
console.log(new Date(), label, message);
|
||||||
|
logger.log({
|
||||||
|
level: 'info',
|
||||||
|
label: label,
|
||||||
|
message: JSON.stringify(message),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: we can do additional reporting here
|
||||||
|
* @param {string} label group label
|
||||||
|
* @param {string} message log message
|
||||||
|
*/
|
||||||
|
function error(label, message) {
|
||||||
|
console.error(new Date(), label, message);
|
||||||
|
logger.log({
|
||||||
|
level: 'error',
|
||||||
|
label: label,
|
||||||
|
message: JSON.stringify(message),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.log = log;
|
||||||
|
exports.error = error;
|
Loading…
x
Reference in New Issue
Block a user