INIT
This commit is contained in:
commit
fb63f4e3c2
|
@ -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 }
|
||||
}
|
|
@ -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
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './User';
|
|
@ -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;
|
|
@ -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',
|
||||
});
|
||||
}
|
|
@ -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`
|
||||
|
||||
|
||||
|
|
@ -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;
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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…
Reference in New Issue