diff --git a/chart/chart.js b/chart/chart.js index f2bbe83..096eef8 100644 --- a/chart/chart.js +++ b/chart/chart.js @@ -13,13 +13,16 @@ const logger = require('../shared/logger/src/main.js'); * @returns {Promise} */ async function gen_chart(from_currency, conv_currency, start_date, end_date) { - const data = await pool.query('SELECT date, rate FROM currency WHERE ' + - '(date BETWEEN $3 AND $4) AND from_currency = $1 AND conv_currency = $2 ORDER BY date ', [ - from_currency.toUpperCase(), - conv_currency.toUpperCase(), - start_date, - end_date - ]); + const data = await pool.query( + 'SELECT date, rate FROM currency WHERE ' + + '(date BETWEEN $3 AND $4) AND from_currency = $1 AND conv_currency = $2 ORDER BY date ', + [ + from_currency.toUpperCase(), + conv_currency.toUpperCase(), + start_date, + end_date, + ], + ); if (!data['rows'][0]) return 'Missing data'; @@ -31,63 +34,71 @@ async function gen_chart(from_currency, conv_currency, start_date, end_date) { rate.push(data.rows[i].rate); } - const chart = ChartJSImage().chart({ - type: 'line', - options: { - title: { - display: true, - text: `${from_currency} / ${conv_currency}`, - }, - scales: [ - { - yAxes: [ - { - ticks: { - beginAtZero: false, - } - } - ] - } - ] - }, - data: { - labels: date, - datasets: [ - { - borderColor: rate[0] < rate[rate.length-1] ? 'rgb(24, 218, 39)' : 'rgb(243, 85, 50)', - backgroundColor: rate[0] < rate[rate.length-1] ? 'rgb(36, 175, 47)' : 'rgb(218, 56, 24)', - data: rate, - borderWidth: 2, + const chart = ChartJSImage() + .chart({ + type: 'line', + options: { + title: { + display: true, + text: `${from_currency} / ${conv_currency}`, }, - ], - }, - scales: { - xAxes: [ - { - scaleLabel: { - display: true, - labelString: 'Day' - }, - }, - ], - yAxes: [ - { - stacked: false, - scaleLabel: { - display: true, - labelString: 'Rate' + scales: [ + { + yAxes: [ + { + ticks: { + beginAtZero: false, + }, + }, + ], }, - }, - ], - }, - }).width(1000).height(1000); + ], + }, + data: { + labels: date, + datasets: [ + { + borderColor: + rate[0] < rate[rate.length - 1] + ? 'rgb(24, 218, 39)' + : 'rgb(243, 85, 50)', + backgroundColor: + rate[0] < rate[rate.length - 1] + ? 'rgb(36, 175, 47)' + : 'rgb(218, 56, 24)', + data: rate, + borderWidth: 2, + }, + ], + }, + scales: { + xAxes: [ + { + scaleLabel: { + display: true, + labelString: 'Day', + }, + }, + ], + yAxes: [ + { + stacked: false, + scaleLabel: { + display: true, + labelString: 'Rate', + }, + }, + ], + }, + }) + .width(1000) + .height(1000); logger.debug(chart.toURL()); return chart.toURL(); } - /** * Saving a graph to a file * @param {String} url - URL (or Buffer) to the chart @@ -95,18 +106,21 @@ async function gen_chart(from_currency, conv_currency, start_date, end_date) { */ function save_chart(url, filename) { if (!fs.existsSync('../charts')) fs.mkdirSync('../charts'); - if (!url.startsWith('https://')) throw new Error('The passed parameter is not a URL'); + if (!url.startsWith('https://')) + throw new Error('The passed parameter is not a URL'); - logger.info(`The schedule has been saved. The path of the graph 'chart/${filename}'`); - axios({url, responseType: 'stream',}).then( - response => + logger.info( + `The schedule has been saved. The path of the graph 'chart/${filename}'`, + ); + axios({ url, responseType: 'stream' }).then( + (response) => new Promise((resolve, reject) => { response.data .pipe(fs.createWriteStream(`../charts/${filename}`)) .on('finish', () => resolve()) - .on('error', e => reject(e)); - }) + .on('error', (e) => reject(e)); + }), ); } -module.exports = { gen_chart, save_chart }; \ No newline at end of file +module.exports = { gen_chart, save_chart }; diff --git a/chart/main.js b/chart/main.js index a82cd17..2891f37 100644 --- a/chart/main.js +++ b/chart/main.js @@ -12,7 +12,8 @@ function main() { for (const file of files) { try { - const fileSize = fs.statSync(`../charts/${file}`)['size'] / 1024; + const fileSize = + fs.statSync(`../charts/${file}`)['size'] / 1024; folderSize += fileSize; if (folderSize > config['currency']['chart']['max_size']) { @@ -20,7 +21,9 @@ function main() { fs.unlinkSync(`../charts/${files[i]}`); } } - } catch { return; } + } catch { + return; + } } }); } @@ -29,4 +32,4 @@ schedule.scheduleJob('9 20 * * *', async function () { main(); }); -main(); \ No newline at end of file +main(); diff --git a/collect-currency/main.js b/collect-currency/main.js index 243529e..af828f5 100644 --- a/collect-currency/main.js +++ b/collect-currency/main.js @@ -8,7 +8,8 @@ const save_crypto = require('./save_crypto'); async function main() { const config_schedule = config['currency']['collecting']['schedule']; if (!config_schedule) throw new Error('The crontab schedule is not set'); - if (!cron.isValidCron(config_schedule, {alias: true})) throw new Error('The crontab is invalid'); + if (!cron.isValidCron(config_schedule, { alias: true })) + throw new Error('The crontab is invalid'); await save_fiat(); await save_crypto(); @@ -21,4 +22,4 @@ async function main() { main(); -module.exports = {main}; \ No newline at end of file +module.exports = { main }; diff --git a/collect-currency/save_crypto.js b/collect-currency/save_crypto.js index b7facf9..68296b4 100644 --- a/collect-currency/save_crypto.js +++ b/collect-currency/save_crypto.js @@ -17,52 +17,62 @@ function save_crypto() { return; } - logger.info(`Active coinapi key: ${coinapiKeys[apiKeyIndex]} (${coinapiKeys.length-1} / ${apiKeyIndex})`); + logger.info( + `Active coinapi key: ${coinapiKeys[apiKeyIndex]} (${coinapiKeys.length - 1} / ${apiKeyIndex})`, + ); - config['currency']['crypto'].forEach( - (value) => config['currency']['crypto'].forEach((pair) => { + config['currency']['crypto'].forEach((value) => + config['currency']['crypto'].forEach((pair) => { if (value === pair) return; - axios.get(`https://rest.coinapi.io/v1/exchangerate/${value}/${pair}`, - { - timeout: 3000, - headers: { - 'X-CoinAPI-Key': coinapiKeys[apiKeyIndex], - } - }).then(async (res) => { - + axios + .get( + `https://rest.coinapi.io/v1/exchangerate/${value}/${pair}`, + { + timeout: 3000, + headers: { + 'X-CoinAPI-Key': coinapiKeys[apiKeyIndex], + }, + }, + ) + .then(async (res) => { const data = res.data; const point = data['rate'].toString().indexOf('.') + 4; logger.debug(JSON.stringify(data)); - const db = await pool.query('SELECT * FROM currency WHERE from_currency = $1 AND conv_currency = $2 AND date = $3', + const db = await pool.query( + 'SELECT * FROM currency WHERE from_currency = $1 AND conv_currency = $2 AND date = $3', [ value, pair, - new Date(data['time']).toLocaleDateString() - ]); + new Date(data['time']).toLocaleDateString(), + ], + ); if (db['rows'][0]) return; - await pool.query(`INSERT INTO currency (from_currency, conv_currency, rate, date) + await pool.query( + `INSERT INTO currency (from_currency, conv_currency, rate, date) VALUES ($1, $2, $3, $4)`, - [ - value, - pair, - data['rate'].toString().slice(0, point), - new Date(data['time']).toLocaleDateString() - ]); - - }).catch((err) => { - if (err.response?.data.detail) logger.error(err.response.data.detail); - if (err.response?.data.status === 429) { - logger.info('CoinAPI rate limited, rotating token'); - rotate_key(coinapiKeys); - depth--; - save_crypto(); - } - }); - }) + [ + value, + pair, + data['rate'].toString().slice(0, point), + new Date(data['time']).toLocaleDateString(), + ], + ); + }) + .catch((err) => { + if (err.response?.data.detail) + logger.error(err.response.data.detail); + if (err.response?.data.status === 429) { + logger.info('CoinAPI rate limited, rotating token'); + rotate_key(coinapiKeys); + depth--; + save_crypto(); + } + }); + }), ); } @@ -76,4 +86,4 @@ function rotate_key(list) { apiKeyIndex = list.indexOf(coinapiKeys[apiKeyIndex]) + 1; } -module.exports = save_crypto; \ No newline at end of file +module.exports = save_crypto; diff --git a/collect-currency/save_fiat.js b/collect-currency/save_fiat.js index 8aa5772..95eea33 100644 --- a/collect-currency/save_fiat.js +++ b/collect-currency/save_fiat.js @@ -10,43 +10,57 @@ const logger = require('../shared/logger/src/main.js'); async function save_fiat() { if (!config['currency']['collecting']['fiat']) return; - config['currency']['fiat'].forEach( - (value) => config['currency']['fiat'].forEach(async (pair) => { - if(value === pair) return; - await axios.get( - `https://duckduckgo.com/js/spice/currency/1/${value}/${pair}`, - { - timeout: 3000, - }).then(async (res) => { + config['currency']['fiat'].forEach((value) => + config['currency']['fiat'].forEach(async (pair) => { + if (value === pair) return; + await axios + .get( + `https://duckduckgo.com/js/spice/currency/1/${value}/${pair}`, + { + timeout: 3000, + }, + ) + .then(async (res) => { const regExp = new RegExp('\\(\\s*(.*)\\s*\\);$', 'mg'); - const data = JSON.parse(Array.from(res.data.matchAll(regExp))[0][1]); + const data = JSON.parse( + Array.from(res.data.matchAll(regExp))[0][1], + ); delete data['terms']; delete data['privacy']; logger.debug(JSON.stringify(data)); - const point = data['to'][0]['mid'].toString().indexOf('.') + 4; + const point = + data['to'][0]['mid'].toString().indexOf('.') + 4; - const db = await pool.query('SELECT * FROM currency WHERE ' + - 'from_currency = $1 AND conv_currency = $2 AND date = $3', - [ value, pair, new Date(data['timestamp']).toLocaleDateString() ]); + const db = await pool.query( + 'SELECT * FROM currency WHERE ' + + 'from_currency = $1 AND conv_currency = $2 AND date = $3', + [ + value, + pair, + new Date(data['timestamp']).toLocaleDateString(), + ], + ); if (db['rows'][0]) return; - await pool.query(`INSERT INTO currency (from_currency, conv_currency, rate, date) VALUES ($1, $2, $3, $4)`, + await pool.query( + `INSERT INTO currency (from_currency, conv_currency, rate, date) VALUES ($1, $2, $3, $4)`, [ value, pair, data['to'][0]['mid'].toString().slice(0, point), - new Date(data['timestamp']).toLocaleDateString() - ]); - }).catch((err) => { - logger.error(err); - setTimeout(save_fiat, err.config.timeout); - }); - - }) + new Date(data['timestamp']).toLocaleDateString(), + ], + ); + }) + .catch((err) => { + logger.error(err); + setTimeout(save_fiat, err.config.timeout); + }); + }), ); } -module.exports = save_fiat; \ No newline at end of file +module.exports = save_fiat; diff --git a/server/main.js b/server/main.js index bbdcc74..9295a9f 100644 --- a/server/main.js +++ b/server/main.js @@ -4,43 +4,26 @@ const fs = require('fs'); const fastify = require('fastify')({ logger: config['server']['log']['print'] ? logger : false, - ...config['server']['ssl']['work'] ? { - https: { - key: fs.readFileSync(config['server']['ssl']['private_key'], 'utf8'), - cert: fs.readFileSync(config['server']['ssl']['cert'], 'utf8'), - } - } : false + ...(config['server']['ssl']['work'] + ? { + https: { + key: fs.readFileSync( + config['server']['ssl']['private_key'], + 'utf8', + ), + cert: fs.readFileSync( + config['server']['ssl']['cert'], + 'utf8', + ), + }, + } + : false), }); const rate = require('../shared/database/src/main.js'); const chart = require('../chart/chart.js'); -fastify.get('/api/getRate/', async function (req, res){ - const query = req.query; - if (!query['from_currency'] || !query['conv_currency']) { - return res.status(400).send({ - status: 400, - message: 'The from_currency and conv_currency fields are required' - }); - } - - - if (query['date']) return rate.getDay(query['from_currency'], query['conv_currency'], query['date']); - else if (query['start_date'] && query['end_date']) return rate.getPeriod( - query['from_currency'], - query['conv_currency'], - query['start_date'], - query['end_date'] - ); - else return res.status(400).send({ - status: 400, - message: 'The date or period field is incorrect. ' + - 'There must be fields \'date\' or \'start_date\' and \'end_date\'. ' + - 'Read more in the documentation' - }); -}); - -fastify.get('/api/getChart/', async function (req, res){ +fastify.get('/api/getRate/', async function (req, res) { const query = req.query; if (!query['from_currency'] || !query['conv_currency']) { return res.status(400).send({ @@ -48,10 +31,43 @@ fastify.get('/api/getChart/', async function (req, res){ message: 'The from_currency and conv_currency fields are required', }); } - if (!query['start_date'] || !query['end_date']) return res.status(400).send({ - status: 400, - message: 'start_date and end_date is required', - }); + + if (query['date']) + return rate.getDay( + query['from_currency'], + query['conv_currency'], + query['date'], + ); + else if (query['start_date'] && query['end_date']) + return rate.getPeriod( + query['from_currency'], + query['conv_currency'], + query['start_date'], + query['end_date'], + ); + else + return res.status(400).send({ + status: 400, + message: + 'The date or period field is incorrect. ' + + "There must be fields 'date' or 'start_date' and 'end_date'. " + + 'Read more in the documentation', + }); +}); + +fastify.get('/api/getChart/', async function (req, res) { + const query = req.query; + if (!query['from_currency'] || !query['conv_currency']) { + return res.status(400).send({ + status: 400, + message: 'The from_currency and conv_currency fields are required', + }); + } + if (!query['start_date'] || !query['end_date']) + return res.status(400).send({ + status: 400, + message: 'start_date and end_date is required', + }); const charts = await chart.gen_chart( query['from_currency'], @@ -60,11 +76,12 @@ fastify.get('/api/getChart/', async function (req, res){ query['end_date'], ); - if (config['currency']['chart']['save']) chart.save_chart( - charts, - `${query['from_currency']} ${query['conv_currency']} ` + - `(${query['start_date']} - ${query['end_date']}).png` - ); + if (config['currency']['chart']['save']) + chart.save_chart( + charts, + `${query['from_currency']} ${query['conv_currency']} ` + + `(${query['start_date']} - ${query['end_date']}).png`, + ); return res.status(200).send({ status: 200, @@ -72,12 +89,15 @@ fastify.get('/api/getChart/', async function (req, res){ }); }); -fastify.listen({ +fastify.listen( + { port: 3000, host: config['server']['host'] ? config['server']['host'] : 'localhost', - }, (err) => { - if (err) { - fastify.log.error(err); - process.exit(1); - } -}); \ No newline at end of file + }, + (err) => { + if (err) { + fastify.log.error(err); + process.exit(1); + } + }, +); diff --git a/shared/config/src/main.js b/shared/config/src/main.js index 0da7678..97b7b20 100644 --- a/shared/config/src/main.js +++ b/shared/config/src/main.js @@ -7,5 +7,4 @@ const config = () => { return yaml.parse(fs.readFileSync('../config.yaml', 'utf-8')); }; - -module.exports = config; \ No newline at end of file +module.exports = config; diff --git a/shared/database/src/main.js b/shared/database/src/main.js index b8e00cf..cbfeae7 100644 --- a/shared/database/src/main.js +++ b/shared/database/src/main.js @@ -10,20 +10,22 @@ const logger = require('../../logger/src/main.js'); */ async function getDay(from_currency, conv_currency, date) { - if (!from_currency || !conv_currency) return new Error('fromCurrency and convCurrency are required'); + if (!from_currency || !conv_currency) + return new Error('fromCurrency and convCurrency are required'); else if (!date) return new Error('date is required'); - const data = await pool.query('SELECT from_currency, conv_currency, date, rate FROM currency ' + - 'WHERE from_currency = $1 AND conv_currency = $2 AND date = $3', [ - from_currency.toUpperCase(), - conv_currency.toUpperCase(), - date - ]); + const data = await pool.query( + 'SELECT from_currency, conv_currency, date, rate FROM currency ' + + 'WHERE from_currency = $1 AND conv_currency = $2 AND date = $3', + [from_currency.toUpperCase(), conv_currency.toUpperCase(), date], + ); if (!data) return new Error('Missing data'); const set_date = data['rows'][0]['date']; - data['rows'][0]['date'] = new Date(set_date.setDate(set_date.getDate() + 1)); + data['rows'][0]['date'] = new Date( + set_date.setDate(set_date.getDate() + 1), + ); logger.debug(data['rows'][0]); @@ -40,16 +42,21 @@ async function getDay(from_currency, conv_currency, date) { */ async function getPeriod(from_currency, conv_currency, start_date, end_date) { - if (!from_currency || !conv_currency) return new Error('from_currency and conv_currency are required'); - else if(!start_date || !end_date) return new Error('start_date and end_date are required'); + if (!from_currency || !conv_currency) + return new Error('from_currency and conv_currency are required'); + else if (!start_date || !end_date) + return new Error('start_date and end_date are required'); - const data = await pool.query('SELECT * FROM currency WHERE ' + - '(date BETWEEN $3 AND $4) AND from_currency = $1 AND conv_currency = $2 ORDER BY date', [ + const data = await pool.query( + 'SELECT * FROM currency WHERE ' + + '(date BETWEEN $3 AND $4) AND from_currency = $1 AND conv_currency = $2 ORDER BY date', + [ from_currency.toUpperCase(), conv_currency.toUpperCase(), start_date, - end_date - ]); + end_date, + ], + ); if (!data) return new Error('Missing data'); @@ -63,4 +70,4 @@ async function getPeriod(from_currency, conv_currency, start_date, end_date) { return data['rows']; } -module.exports = { getDay, getPeriod }; \ No newline at end of file +module.exports = { getDay, getPeriod }; diff --git a/shared/database/src/postgresql.js b/shared/database/src/postgresql.js index d928aba..a7d9262 100644 --- a/shared/database/src/postgresql.js +++ b/shared/database/src/postgresql.js @@ -1,13 +1,12 @@ -const pg = require("pg"); -const config = require("../../config/src/main.js")(); +const pg = require('pg'); +const config = require('../../config/src/main.js')(); const pool = new pg.Pool({ - user: config['database']['user'], + user: config['database']['user'], password: config['database']['password'], host: config['database']['host'], port: config['database']['port'], - database: config['database']['name'] + database: config['database']['name'], }); - -module.exports = pool; \ No newline at end of file +module.exports = pool; diff --git a/shared/logger/src/main.js b/shared/logger/src/main.js index caf15f6..85184b3 100644 --- a/shared/logger/src/main.js +++ b/shared/logger/src/main.js @@ -11,37 +11,46 @@ function getCallerFile() { const err = new Error(); let currentFile; - Error.prepareStackTrace = function (err, stack) { return stack; }; + Error.prepareStackTrace = function (err, stack) { + return stack; + }; currentFile = err.stack.shift().getFileName(); while (err.stack.length) { callerFile = err.stack.shift().getFileName(); if (currentFile !== callerFile) break; } - } catch { return; } + } catch { + return; + } Error.prepareStackTrace = originalFunc; return callerFile ? path.basename(callerFile) : 'unknown'; } -const logger = pino({ - level: config['server']['log']['level'] ? config['server']['log']['level'] : 'info', - prettifier: pretty, - prettify: true, - messageKey: 'msg', - timestampKey: 'time', -}, pretty({ - ignore: 'pid,hostname', - messageFormat: '{msg}', -})); +const logger = pino( + { + level: config['server']['log']['level'] + ? config['server']['log']['level'] + : 'info', + prettifier: pretty, + prettify: true, + messageKey: 'msg', + timestampKey: 'time', + }, + pretty({ + ignore: 'pid,hostname', + messageFormat: '{msg}', + }), +); function wrapLogger(logger) { const levels = ['fatal', 'error', 'warn', 'info', 'debug', 'trace']; const wrappedLogger = {}; - levels.forEach(level => { + levels.forEach((level) => { wrappedLogger[level] = function (msg, ...args) { const callerFile = getCallerFile(); const msgWithFilename = `[${callerFile}] ${msg}`;