diff --git a/bin/report b/bin/report index 95d0476..7d87e69 100755 --- a/bin/report +++ b/bin/report @@ -1,5 +1,5 @@ #!/usr/bin/env node -const { generateReport } = require('../dist/report/Report'); +const { reportCli } = require('../dist/report/Report'); const { argv } = require('yargs').options({ dbpath: { @@ -18,4 +18,4 @@ delete argv.$0; delete argv.help; delete argv.version; -generateReport(argv); +reportCli(argv); diff --git a/lib/BoltzMiddleware.ts b/lib/BoltzMiddleware.ts index 926ed5f..b91de54 100644 --- a/lib/BoltzMiddleware.ts +++ b/lib/BoltzMiddleware.ts @@ -5,6 +5,7 @@ import Logger from './Logger'; import Database from './db/Database'; import Service from './service/Service'; import BoltzClient from './boltz/BoltzClient'; +import BackupScheduler from './backup/BackupScheduler'; import NotificationProvider from './notifications/NotificationProvider'; class BoltzMiddleware { @@ -15,6 +16,7 @@ class BoltzMiddleware { private boltzClient: BoltzClient; private service: Service; + private backup: BackupScheduler; private notifications: NotificationProvider; private api: Api; @@ -35,10 +37,19 @@ class BoltzMiddleware { this.config.currencies, ); + this.backup = new BackupScheduler( + this.logger, + this.config.dbpath, + this.config.backup, + this.service.swapRepository, + this.service.reverseSwapRepository, + ); + this.notifications = new NotificationProvider( this.logger, this.service, this.boltzClient, + this.backup, this.config.notification, this.config.currencies, ); @@ -47,11 +58,15 @@ class BoltzMiddleware { } public start = async () => { - await this.db.init(), - await this.boltzClient.connect(), + await Promise.all([ + this.db.init(), + this.boltzClient.connect(), + ]); - await this.service.init(this.config.pairs); - await this.notifications.init(); + await Promise.all([ + this.service.init(this.config.pairs), + this.notifications.init(), + ]); await this.api.init(); } diff --git a/lib/Config.ts b/lib/Config.ts index 5899bbd..e1b9765 100644 --- a/lib/Config.ts +++ b/lib/Config.ts @@ -6,6 +6,7 @@ import { ApiConfig } from './api/Api'; import { PairConfig } from './service/Service'; import { CurrencyConfig } from './consts/Types'; import { BoltzConfig } from './boltz/BoltzClient'; +import { BackupConfig } from './backup/BackupScheduler'; import { getServiceDir, deepMerge, resolveHome } from './Utils'; import { NotificationConfig } from './notifications/NotificationProvider'; @@ -20,6 +21,7 @@ class Config { public boltz: BoltzConfig; public notification: NotificationConfig; + public backup: BackupConfig; public currencies: CurrencyConfig[]; @@ -31,7 +33,7 @@ class Config { private configpath: string; constructor() { - const { configpath, logpath, dbpath } = this.getDataDirPaths(this.defaultDataDir); + const { configpath, logpath, dbpath, backup } = this.getDataDirPaths(this.defaultDataDir); this.configpath = configpath; @@ -61,6 +63,17 @@ class Config { interval: 1, }; + this.backup = { + email: '', + privatekeypath: backup.privatekeypath, + + bucketname: '', + + interval: '0 0 * * *', + + backenddbpath: '', + }; + this.currencies = [ { symbol: 'BTC', @@ -123,6 +136,7 @@ class Config { 'logpath', 'dbpath', 'boltz.certpath', + 'backup.privatekeypath', ]; pathsToResolve.forEach((key) => { @@ -165,6 +179,9 @@ class Config { configpath: path.join(dataDir, 'boltz.conf'), logpath: path.join(dataDir, 'boltz.log'), dbpath: path.join(dataDir, 'boltz.db'), + backup: { + privatekeypath: path.join(dataDir, 'backupPrivatekey.pem'), + }, }; } } diff --git a/lib/backup/BackupScheduler.ts b/lib/backup/BackupScheduler.ts new file mode 100644 index 0000000..0a396cc --- /dev/null +++ b/lib/backup/BackupScheduler.ts @@ -0,0 +1,111 @@ +import { readFileSync } from 'fs'; +import { scheduleJob } from 'node-schedule'; +import { Storage, Bucket } from '@google-cloud/storage'; +import Logger from '../Logger'; +import { generateReport } from '../report/Report'; +import SwapRepository from '../service/SwapRepository'; +import ReverseSwapRepository from '../service/ReverseSwapRepository'; + +type BackupConfig = { + email: string; + privatekeypath: string; + + bucketname: string; + + // The interval has to be a cron schedule expression + interval: string; + + // If set, the database of the backend will also be backed up + backenddbpath: string; +}; + +class BackupScheduler { + private bucket?: Bucket; + + constructor( + private logger: Logger, + private dbpath: string, + private config: BackupConfig, + private swapRepository: SwapRepository, + private reverseSwapRepository: ReverseSwapRepository) { + + if ( + config.email === '' || + config.privatekeypath === '' || + config.bucketname === '' + ) { + logger.warn('Disabled backups because of incomplete configuration'); + return; + } + + const storage = new Storage({ + credentials: { + client_email: config.email, + private_key: readFileSync(config.privatekeypath, 'utf-8'), + }, + }); + + this.bucket = storage.bucket(config.bucketname); + + scheduleJob(this.config.interval, async (date) => { + await this.uploadDatabases(date); + await this.uploadReport(); + }); + } + + public uploadDatabases = async (date: Date) => { + if (!this.bucket) { + throw 'Backups are disabled because of incomplete configuration'; + } + + const dateString = this.getDate(date); + this.logger.silly(`Doing database backup at: ${dateString}`); + + await this.uploadFile(this.dbpath, dateString, true); + + if (this.config.backenddbpath !== '') { + await this.uploadFile(this.config.backenddbpath, dateString, false); + } + } + + public uploadReport = async () => { + if (!this.bucket) { + return; + } + + const file = this.bucket.file('report.csv'); + const data = await generateReport(this.swapRepository, this.reverseSwapRepository); + + await file.save(data); + + this.logger.debug('Uploaded report'); + } + + private uploadFile = async (fileName: string, date: string, isMiddleware: boolean) => { + try { + await this.bucket!.upload(fileName, { + destination: `${isMiddleware ? 'middleware' : 'backend'}/database-${date}.db`, + }); + + this.logger.debug(`Uploaded file ${fileName}`); + } catch (error) { + this.logger.warn(`Could not upload file: ${error}`); + throw error; + } + } + + private getDate = (date: Date) => { + return `${date.getFullYear()}${this.addLeadingZeros(date.getMonth())}${this.addLeadingZeros(date.getDay())}` + + `-${this.addLeadingZeros(date.getHours())}${this.addLeadingZeros(date.getMinutes())}`; + } + + /** + * Adds a leading 0 to the provided number if it is smalled than 10 + */ + private addLeadingZeros = (number: number) => { + return `${number}`.padStart(2, '0'); + } +} + +export default BackupScheduler; +export { BackupConfig }; diff --git a/lib/notifications/CommandHandler.ts b/lib/notifications/CommandHandler.ts index 6f5df6e..500409a 100644 --- a/lib/notifications/CommandHandler.ts +++ b/lib/notifications/CommandHandler.ts @@ -6,42 +6,20 @@ import BoltzClient from '../boltz/BoltzClient'; import { OutputType } from '../proto/boltzrpc_pb'; import Swap from '../db/models/Swap'; import ReverseSwap from '../db/models/ReverseSwap'; -import { SwapUpdateEvent } from '../consts/Enums'; -import SwapRepository from '../service/SwapRepository'; -import ReverseSwapRepository from '../service/ReverseSwapRepository'; +import { getSuccessfulTrades } from '../report/Report'; +import BackupScheduler from '../backup/BackupScheduler'; import { satoshisToCoins, parseBalances, getFeeSymbol, stringify } from '../Utils'; -/** - * Gets all successful (reverse) swaps - */ -export const getSuccessfulTrades = async (swapRepository: SwapRepository, reverseSwapRepository: ReverseSwapRepository): - Promise<{ swaps: Swap[], reverseSwaps: ReverseSwap[] }> => { - - const [swaps, reverseSwaps] = await Promise.all([ - swapRepository.getSwaps({ - status: { - [Op.eq]: SwapUpdateEvent.InvoicePaid, - }, - }), - reverseSwapRepository.getReverseSwaps({ - status: { - [Op.eq]: SwapUpdateEvent.InvoiceSettled, - }, - }), - ]); - - return { - swaps, - reverseSwaps, - }; -}; - enum Command { Help = 'help', + // Commands that retrieve information GetFees = 'getfees', SwapInfo = 'swapinfo', GetBalance = 'getbalance', + + // Commands that generate a value or trigger a function + Backup = 'backup', NewAddress = 'newaddress', ToggleReverseSwaps = 'togglereverse', } @@ -56,17 +34,20 @@ class CommandHandler { constructor( private logger: Logger, + private discord: DiscordClient, private service: Service, private boltz: BoltzClient, - private discord: DiscordClient) { + private backupScheduler: BackupScheduler) { this.commands = new Map([ [Command.Help, { description: 'gets a list of all available commands', executor: this.help }], [Command.GetFees, { description: 'gets the accumulated fees', executor: this.getFees }], - [Command.NewAddress, { description: 'generates a new address for a currency', executor: this.newAddress }], [Command.GetBalance, { description: 'gets the balance of the wallet and channels', executor: this.getBalance }], [Command.SwapInfo, { description: 'gets all available information about a (reverse) swap', executor: this.swapInfo }], + + [Command.Backup, { description: 'uploads a backup of the databases', executor: this.backup }], + [Command.NewAddress, { description: 'generates a new address for a currency', executor: this.newAddress }], [Command.ToggleReverseSwaps, { description: 'enables or disables reverse swaps', executor: this.toggleReverseSwaps }], ]); @@ -91,10 +72,20 @@ class CommandHandler { }); } - /** + /* * Command executors */ + private help = async () => { + let message = 'Commands:\n'; + + this.commands.forEach((info, command) => { + message += `\n**${command}**: ${info.description}`; + }); + + await this.discord.sendMessage(message); + } + private getBalance = async () => { const balances = await parseBalances(await this.boltz.getBalance()); @@ -191,17 +182,17 @@ class CommandHandler { await this.discord.sendMessage(`${this.service.allowReverseSwaps ? 'Enabled' : 'Disabled'} reverse swaps`); } - private help = async () => { - let message = 'Commands:\n'; - - this.commands.forEach((info, command) => { - message += `\n**${command}**: ${info.description}`; - }); + private backup = async () => { + try { + await this.backupScheduler.uploadDatabases(new Date()); - await this.discord.sendMessage(message); + await this.discord.sendMessage('Uploaded backup of databases'); + } catch (error) { + await this.discord.sendMessage(`Could not upload backup: ${error}`); + } } - /** + /* * Helper functions */ @@ -220,7 +211,7 @@ class CommandHandler { const fees = new Map(); const getFeeFromSwapMap = (array: Swap[] | ReverseSwap[], isReverse: boolean) => { - array.forEach((swap) => { + array.forEach((swap: Swap | ReverseSwap) => { const feeSymbol = getFeeSymbol(swap.pair, swap.orderSide, isReverse); const fee = fees.get(feeSymbol); diff --git a/lib/notifications/NotificationProvider.ts b/lib/notifications/NotificationProvider.ts index f640a96..2658ab4 100644 --- a/lib/notifications/NotificationProvider.ts +++ b/lib/notifications/NotificationProvider.ts @@ -8,6 +8,7 @@ import { OutputType, OrderSide } from '../proto/boltzrpc_pb'; import BoltzClient, { ConnectionStatus } from '../boltz/BoltzClient'; import Swap from '../db/models/Swap'; import ReverseSwap from '../db/models/ReverseSwap'; +import BackupScheduler from '../backup/BackupScheduler'; import { minutesToMilliseconds, satoshisToCoins, splitPairId, parseBalances, getFeeSymbol } from '../Utils'; type NotificationConfig = { @@ -36,6 +37,7 @@ class NotificationProvider { private logger: Logger, private service: Service, private boltz: BoltzClient, + private backup: BackupScheduler, private config: NotificationConfig, private currencies: CurrencyConfig[]) { @@ -51,9 +53,10 @@ class NotificationProvider { new CommandHandler( this.logger, + this.discord, this.service, this.boltz, - this.discord, + this.backup, ); } diff --git a/lib/report/Report.ts b/lib/report/Report.ts index 3673fd0..23e627d 100644 --- a/lib/report/Report.ts +++ b/lib/report/Report.ts @@ -1,13 +1,14 @@ import fs from 'fs'; +import { Op } from 'sequelize'; import { Arguments } from 'yargs'; import Logger from '../Logger'; +import Swap from '../db/models/Swap'; import Database from '../db/Database'; +import { SwapUpdateEvent } from '../consts/Enums'; +import ReverseSwap from '../db/models/ReverseSwap'; import SwapRepository from '../service/SwapRepository'; import ReverseSwapRepository from '../service/ReverseSwapRepository'; -import Swap from '../db/models/Swap'; -import ReverseSwap from '../db/models/ReverseSwap'; import { getFeeSymbol, resolveHome, satoshisToCoins } from '../Utils'; -import { getSuccessfulTrades } from '../notifications/CommandHandler'; type Entry = { date: Date; @@ -19,17 +20,24 @@ type Entry = { feeCurrency: string; }; -export const generateReport = async (argv: Arguments) => { +export const reportCli = async (argv: Arguments) => { // Get the path to the database from the command line arguments or - // use the default one if none was specified + // use a default one if none was specified const dbPath = argv.dbpath || '~/.boltz-middleware/boltz.db'; const db = new Database(Logger.disabledLogger, resolveHome(dbPath)); await db.init(); - const swapRepository = new SwapRepository(); - const reverseSwapRepository = new ReverseSwapRepository(); + const csv = await generateReport(new SwapRepository(), new ReverseSwapRepository()); + + if (argv.reportpath) { + fs.writeFileSync(resolveHome(argv.reportpath), csv); + } else { + console.log(csv); + } +}; +export const generateReport = async (swapRepository: SwapRepository, reverseSwapRepository: ReverseSwapRepository) => { const { swaps, reverseSwaps } = await getSuccessfulTrades(swapRepository, reverseSwapRepository); const entries = swapsToEntries(swaps, reverseSwaps); @@ -37,13 +45,7 @@ export const generateReport = async (argv: Arguments) => { return a.date.getTime() - b.date.getTime(); }); - const csv = arrayToCsv(entries); - - if (argv.reportpath) { - fs.writeFileSync(resolveHome(argv.reportpath), csv); - } else { - console.log(csv); - } + return arrayToCsv(entries); }; const swapsToEntries = (swaps: Swap[], reverseSwaps: ReverseSwap[]) => { @@ -69,6 +71,31 @@ const swapsToEntries = (swaps: Swap[], reverseSwaps: ReverseSwap[]) => { return entries; }; +/** + * Gets all successful (reverse) swaps + */ +export const getSuccessfulTrades = async (swapRepository: SwapRepository, reverseSwapRepository: ReverseSwapRepository): + Promise<{ swaps: Swap[], reverseSwaps: ReverseSwap[] }> => { + + const [swaps, reverseSwaps] = await Promise.all([ + swapRepository.getSwaps({ + status: { + [Op.eq]: SwapUpdateEvent.InvoicePaid, + }, + }), + reverseSwapRepository.getReverseSwaps({ + status: { + [Op.eq]: SwapUpdateEvent.InvoiceSettled, + }, + }), + ]); + + return { + swaps, + reverseSwaps, + }; +}; + const getSwapType = (orderSide: number, isReverse: boolean) => { if ((orderSide === 0 && !isReverse) || (orderSide !== 0 && isReverse)) { return 'Lightning/Chain'; diff --git a/package-lock.json b/package-lock.json index efbcc93..f4644e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -84,6 +84,92 @@ "tslib": "^1.8.1" } }, + "@google-cloud/common": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.32.1.tgz", + "integrity": "sha512-bLdPzFvvBMtVkwsoBtygE9oUm3yrNmPa71gvOgucYI/GqvNP2tb6RYsDHPq98kvignhcgHGDI5wyNgxaCo8bKQ==", + "requires": { + "@google-cloud/projectify": "^0.3.3", + "@google-cloud/promisify": "^0.4.0", + "@types/request": "^2.48.1", + "arrify": "^2.0.0", + "duplexify": "^3.6.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "google-auth-library": "^3.1.1", + "pify": "^4.0.1", + "retry-request": "^4.0.0", + "teeny-request": "^3.11.3" + }, + "dependencies": { + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + } + } + }, + "@google-cloud/paginator": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-0.2.0.tgz", + "integrity": "sha512-2ZSARojHDhkLvQ+CS32K+iUhBsWg3AEw+uxtqblA7xoCABDyhpj99FPp35xy6A+XlzMhOSrHHaxFE+t6ZTQq0w==", + "requires": { + "arrify": "^1.0.1", + "extend": "^3.0.1", + "split-array-stream": "^2.0.0", + "stream-events": "^1.0.4" + } + }, + "@google-cloud/projectify": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-0.3.3.tgz", + "integrity": "sha512-7522YHQ4IhaafgSunsFF15nG0TGVmxgXidy9cITMe+256RgqfcrfWphiMufW+Ou4kqagW/u3yxwbzVEW3dk2Uw==" + }, + "@google-cloud/promisify": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-0.4.0.tgz", + "integrity": "sha512-4yAHDC52TEMCNcMzVC8WlqnKKKq+Ssi2lXoUg9zWWkZ6U6tq9ZBRYLHHCRdfU+EU9YJsVmivwGcKYCjRGjnf4Q==" + }, + "@google-cloud/storage": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-2.5.0.tgz", + "integrity": "sha512-q1mwB6RUebIahbA3eriRs8DbG2Ij81Ynb9k8hMqTPkmbd8/S6Z0d6hVvfPmnyvX9Ej13IcmEYIbymuq/RBLghA==", + "requires": { + "@google-cloud/common": "^0.32.0", + "@google-cloud/paginator": "^0.2.0", + "@google-cloud/promisify": "^0.4.0", + "arrify": "^1.0.0", + "async": "^2.0.1", + "compressible": "^2.0.12", + "concat-stream": "^2.0.0", + "date-and-time": "^0.6.3", + "duplexify": "^3.5.0", + "extend": "^3.0.0", + "gcs-resumable-upload": "^1.0.0", + "hash-stream-validation": "^0.2.1", + "mime": "^2.2.0", + "mime-types": "^2.0.8", + "onetime": "^5.1.0", + "pumpify": "^1.5.1", + "snakeize": "^0.1.0", + "stream-events": "^1.0.1", + "teeny-request": "^3.11.3", + "through2": "^3.0.0", + "xdg-basedir": "^3.0.0" + }, + "dependencies": { + "mime": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.2.tgz", + "integrity": "sha512-zJBfZDkwRu+j3Pdd2aHsR5GfH2jIWhmL1ZzBoc+X+3JEti2hbArWcyJ+1laC1D2/U/W1a/+Cegj0/OnEU2ybjg==" + } + } + }, "@types/bluebird": { "version": "3.5.26", "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.26.tgz", @@ -108,6 +194,11 @@ "@types/node": "*" } }, + "@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" + }, "@types/chai": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz", @@ -153,6 +244,14 @@ "@types/range-parser": "*" } }, + "@types/form-data": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", + "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", + "requires": { + "@types/node": "*" + } + }, "@types/mime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", @@ -166,9 +265,18 @@ "dev": true }, "@types/node": { - "version": "11.13.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.5.tgz", - "integrity": "sha512-/OMMBnjVtDuwX1tg2pkYVSqRIDSmNTnvVvmvP/2xiMAAWf4a5+JozrApCrO4WCAILmXVxfNoQ3E+0HJbNpFVGg==" + "version": "11.13.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.6.tgz", + "integrity": "sha512-Xoo/EBzEe8HxTSwaZNLZjaW6M6tA/+GmD3/DZ6uo8qSaolE/9Oarko0oV1fVfrLqOz0tx0nXJB4rdD5c+vixLw==" + }, + "@types/node-schedule": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/node-schedule/-/node-schedule-1.2.3.tgz", + "integrity": "sha512-pmuGe7xlx4hnUnOeGk2G46xxGKesF8reddLPiQu0tEIOQePMjDezjaOXFLfRPFBDWm03Aj8FoqPTuFxQt9LDaw==", + "dev": true, + "requires": { + "@types/node": "*" + } }, "@types/range-parser": { "version": "1.2.3", @@ -176,6 +284,17 @@ "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", "dev": true }, + "@types/request": { + "version": "2.48.1", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz", + "integrity": "sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg==", + "requires": { + "@types/caseless": "*", + "@types/form-data": "*", + "@types/node": "*", + "@types/tough-cookie": "*" + } + }, "@types/serve-static": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz", @@ -186,6 +305,11 @@ "@types/mime": "*" } }, + "@types/tough-cookie": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", + "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==" + }, "@types/yargs": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.0.tgz", @@ -206,6 +330,14 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, "accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", @@ -215,6 +347,14 @@ "negotiator": "0.6.1" } }, + "agent-base": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "requires": { + "es6-promisify": "^5.0.0" + } + }, "ajv": { "version": "6.9.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.2.tgz", @@ -652,6 +792,11 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + }, "ascli": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ascli/-/ascli-1.0.1.tgz", @@ -769,6 +914,11 @@ "safe-buffer": "^5.0.1" } }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -787,6 +937,11 @@ "resolved": "https://registry.npmjs.org/bigi/-/bigi-1.4.2.tgz", "integrity": "sha1-nGZalfiLiwj8Bc/XMfVhhZ1yWCU=" }, + "bignumber.js": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", + "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==" + }, "binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", @@ -1056,11 +1211,15 @@ "safe-buffer": "^5.1.2" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, "buffer-xor": { "version": "1.0.3", @@ -1339,11 +1498,42 @@ "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", "dev": true }, + "compressible": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.16.tgz", + "integrity": "sha512-JQfEOdnI7dASwCuSPWIeVYwc/zMsu/+tRhoUvEfXz2gxOA2DNjmG5vhtFdBlhWPPGo+RdT9S3tgc/uH5qgDiiA==", + "requires": { + "mime-db": ">= 1.38.0 < 2" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz", + "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "concurrently": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-4.1.0.tgz", @@ -1610,6 +1800,15 @@ "sha.js": "^2.4.8" } }, + "cron-parser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-2.11.0.tgz", + "integrity": "sha512-L5LAGlvq2xmCLErhjQRX8IL5v72y8jhGOaxrarYOhse0kJjJGb/vY/0sV/c7F/SylJGkUIY2iZPPJXZD3glZqA==", + "requires": { + "is-nan": "^1.2.1", + "moment-timezone": "^0.5.23" + } + }, "cross-os": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/cross-os/-/cross-os-1.3.0.tgz", @@ -1630,8 +1829,7 @@ "crypto-random-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", - "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", - "dev": true + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" }, "dashdash": { "version": "1.14.1", @@ -1641,6 +1839,11 @@ "assert-plus": "^1.0.0" } }, + "date-and-time": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.6.3.tgz", + "integrity": "sha512-lcWy3AXDRJOD7MplwZMmNSRM//kZtJaLz4n6D1P5z9wEmZGBKhJRBIr1Xs9KNQJmdXPblvgffynYji4iylUTcA==" + }, "date-fns": { "version": "1.30.1", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", @@ -1727,7 +1930,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -1874,7 +2076,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", - "dev": true, "requires": { "is-obj": "^1.0.0" } @@ -1900,6 +2101,17 @@ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -1909,6 +2121,14 @@ "safer-buffer": "^2.1.0" } }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ecurve": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/ecurve/-/ecurve-1.0.6.tgz", @@ -1966,8 +2186,7 @@ "ent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "dev": true + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=" }, "env-variable": { "version": "0.0.5", @@ -2022,6 +2241,19 @@ "is-symbol": "^1.0.2" } }, + "es6-promise": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.6.tgz", + "integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "requires": { + "es6-promise": "^4.0.3" + } + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -2050,6 +2282,11 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, "evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", @@ -2233,6 +2470,11 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz", "integrity": "sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg==" }, + "fast-text-encoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz", + "integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ==" + }, "fecha": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", @@ -2974,6 +3216,62 @@ "wide-align": "^1.1.0" } }, + "gaxios": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-1.8.3.tgz", + "integrity": "sha512-6Lc1P0NjbPNQ2FGgTRurz32P6FktNJbwLqXvrUNhfwzKb9iizcWuAJiHoSG2W186K9ZL0X6ST5xD9gJWhHI1sg==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^2.2.1", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-1.0.0.tgz", + "integrity": "sha512-Q6HrgfrCQeEircnNP3rCcEgiDv7eF9+1B+1MMgpE190+/+0mjQR8PxeOaRgxZWmdDAF9EIryHB9g1moPiw1SbQ==", + "requires": { + "gaxios": "^1.0.2", + "json-bigint": "^0.3.0" + } + }, + "gcs-resumable-upload": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-1.1.0.tgz", + "integrity": "sha512-uBz7uHqp44xjSDzG3kLbOYZDjxxR/UAGbB47A0cC907W6yd2LkcyFDTHg+bjivkHMwiJlKv4guVWcjPCk2zScg==", + "requires": { + "abort-controller": "^2.0.2", + "configstore": "^4.0.0", + "gaxios": "^1.5.0", + "google-auth-library": "^3.0.0", + "pumpify": "^1.5.1", + "stream-events": "^1.0.4" + }, + "dependencies": { + "abort-controller": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-2.0.3.tgz", + "integrity": "sha512-EPSq5wr2aFyAZ1PejJB32IX9Qd4Nwus+adnp7STYFM5/23nLPBazqZ1oor6ZqbH+4otaaGXTlC8RN5hq3C8w9Q==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "configstore": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-4.0.0.tgz", + "integrity": "sha512-CmquAXFBocrzaSM8mtGPMM/HiWmyIpr4CcJl/rgY2uCObZ/S7cKU0silxslqJejl+t/T9HS8E0PUNQD81JGUEQ==", + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + } + } + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -3086,6 +3384,48 @@ "ini": "^1.3.4" } }, + "google-auth-library": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-3.1.2.tgz", + "integrity": "sha512-cDQMzTotwyWMrg5jRO7q0A4TL/3GWBgO7I7q5xGKNiiFf9SmGY/OJ1YsLMgI2MVHHsEGyrqYnbnmV1AE+Z6DnQ==", + "requires": { + "base64-js": "^1.3.0", + "fast-text-encoding": "^1.0.0", + "gaxios": "^1.2.1", + "gcp-metadata": "^1.0.0", + "gtoken": "^2.3.2", + "https-proxy-agent": "^2.2.1", + "jws": "^3.1.5", + "lru-cache": "^5.0.0", + "semver": "^5.5.0" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + } + } + }, + "google-p12-pem": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-1.0.4.tgz", + "integrity": "sha512-SwLAUJqUfTB2iS+wFfSS/G9p7bt4eWcc2LyfvmUXe7cWp6p3mpxDo6LLI29MXdU6wvPcQ/up298X7GMC5ylAlA==", + "requires": { + "node-forge": "^0.8.0", + "pify": "^4.0.0" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + } + } + }, "google-protobuf": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.6.1.tgz", @@ -3122,8 +3462,7 @@ "graceful-fs": { "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" }, "growl": { "version": "1.10.5", @@ -4040,6 +4379,30 @@ "handlebars-helpers": "^0.10.0" } }, + "gtoken": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-2.3.3.tgz", + "integrity": "sha512-EaB49bu/TCoNeQjhCYKI/CurooBKkGxIqFHsWABW0b25fobBYVTMe84A8EBVVZhl8emiUdNypil9huMOTmyAnw==", + "requires": { + "gaxios": "^1.0.4", + "google-p12-pem": "^1.0.0", + "jws": "^3.1.5", + "mime": "^2.2.0", + "pify": "^4.0.0" + }, + "dependencies": { + "mime": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.2.tgz", + "integrity": "sha512-zJBfZDkwRu+j3Pdd2aHsR5GfH2jIWhmL1ZzBoc+X+3JEti2hbArWcyJ+1laC1D2/U/W1a/+Cegj0/OnEU2ybjg==" + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + } + } + }, "handlebars": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", @@ -4209,6 +4572,25 @@ "safe-buffer": "^5.0.1" } }, + "hash-stream-validation": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.1.tgz", + "integrity": "sha1-7Mm5l7IYvluzEphii7gHhptz3NE=", + "requires": { + "through2": "^2.0.0" + }, + "dependencies": { + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, "hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", @@ -4311,6 +4693,15 @@ "sshpk": "^1.7.0" } }, + "https-proxy-agent": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + } + }, "iconv-lite": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", @@ -4342,8 +4733,7 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "inflection": { "version": "1.12.0", @@ -4543,6 +4933,14 @@ "is-path-inside": "^1.0.0" } }, + "is-nan": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.2.1.tgz", + "integrity": "sha1-n69ltvttskt/XAYoR16nH5iEAeI=", + "requires": { + "define-properties": "^1.1.1" + } + }, "is-npm": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", @@ -4558,8 +4956,7 @@ "is-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" }, "is-odd": { "version": "0.1.2", @@ -4643,6 +5040,11 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, + "is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" + }, "is-symbol": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", @@ -4716,6 +5118,14 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, + "json-bigint": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz", + "integrity": "sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4=", + "requires": { + "bignumber.js": "^7.0.0" + } + }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -4748,6 +5158,25 @@ "verror": "1.10.0" } }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -4873,6 +5302,11 @@ "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" }, + "long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", + "integrity": "sha1-lyHXiLR+C8taJMLivuGg2lXatRQ=" + }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -4901,7 +5335,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, "requires": { "pify": "^3.0.0" } @@ -5351,6 +5784,16 @@ } } }, + "node-fetch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", + "integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==" + }, + "node-forge": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.2.tgz", + "integrity": "sha512-mXQ9GBq1N3uDCyV1pdSzgIguwgtVpM7f5/5J4ipz12PKWElmPpVWLDuWl8iXmhysr21+WmX/OJ5UKx82wjomgg==" + }, "node-pre-gyp": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", @@ -5368,6 +5811,16 @@ "tar": "^4" } }, + "node-schedule": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-1.3.2.tgz", + "integrity": "sha512-GIND2pHMHiReSZSvS6dpZcDH7pGPGFfWBIEud6S00Q8zEIzAs9ommdyRK1ZbQt8y1LyZsJYZgPnyi7gpU2lcdw==", + "requires": { + "cron-parser": "^2.7.3", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.0.0" + } + }, "nodemon": { "version": "1.18.11", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.11.tgz", @@ -5495,8 +5948,7 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object-visit": { "version": "1.0.1", @@ -5559,6 +6011,14 @@ "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, "optimist": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", @@ -5722,8 +6182,7 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" }, "posix-character-classes": { "version": "0.1.1", @@ -5809,6 +6268,27 @@ "once": "^1.3.1" } }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -6075,6 +6555,25 @@ "any-promise": "^1.3.0" } }, + "retry-request": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.0.0.tgz", + "integrity": "sha512-S4HNLaWcMP6r8E4TMH52Y7/pM8uNayOcTDDQNBwsCccL1uI+Ol2TljxRDPzaNfbhOB30+XWP5NnZkB3LiJxi1w==", + "requires": { + "through2": "^2.0.0" + }, + "dependencies": { + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, "rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", @@ -6317,6 +6816,11 @@ "is-arrayish": "^0.3.1" } }, + "snakeize": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", + "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=" + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -6401,6 +6905,11 @@ "resolved": "https://registry.npmjs.org/snekfetch/-/snekfetch-3.6.4.tgz", "integrity": "sha512-NjxjITIj04Ffqid5lqr7XdgwM7X61c/Dns073Ly170bPQHLm6jkmelye/eglS++1nfTWktpP6Y2bFXjdPlQqdw==" }, + "sorted-array-functions": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.2.0.tgz", + "integrity": "sha512-sWpjPhIZJtqO77GN+LD8dDsDKcWZ9GCOJNqKzi1tvtjGIzwfoyuRH8S0psunmc6Z5P+qfDqztSbwYR5X/e1UTg==" + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -6474,6 +6983,14 @@ "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==", "dev": true }, + "split-array-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/split-array-stream/-/split-array-stream-2.0.0.tgz", + "integrity": "sha512-hmMswlVY91WvGMxs0k8MRgq8zb2mSen4FmDNc5AFiTWtrBpdZN6nwD6kROVe4vNL+ywrvbCKsWVCnEd4riELIg==", + "requires": { + "is-stream-ended": "^0.1.4" + } + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -6574,6 +7091,19 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" }, + "stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "requires": { + "stubs": "^3.0.0" + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" + }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -6616,6 +7146,11 @@ "integrity": "sha1-yMPn/db7S7OjKjt1LltePjgJPr0=", "dev": true }, + "stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=" + }, "success-symbol": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/success-symbol/-/success-symbol-0.1.0.tgz", @@ -6645,6 +7180,16 @@ "yallist": "^3.0.2" } }, + "teeny-request": { + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-3.11.3.tgz", + "integrity": "sha512-CKncqSF7sH6p4rzCgkb/z/Pcos5efl0DmolzvlqRQUNcpRIruOhY9+T1FsIlyEbfWd7MsFpodROOwHYh2BaXzw==", + "requires": { + "https-proxy-agent": "^2.2.1", + "node-fetch": "^2.2.0", + "uuid": "^3.3.2" + } + }, "term-size": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", @@ -6693,6 +7238,14 @@ "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" }, + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "requires": { + "readable-stream": "2 || 3" + } + }, "time-stamp": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", @@ -7064,6 +7617,11 @@ "mime-types": "~2.1.18" } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, "typeforce": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", @@ -7090,9 +7648,9 @@ } }, "typescript": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.3.tgz", - "integrity": "sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.4.tgz", + "integrity": "sha512-xt5RsIRCEaf6+j9AyOBgvVuAec0i92rgCaS3S+UVf5Z/vF2Hvtsw08wtUTJqp4djwznoAgjSxeCcU4r+CcDBJA==", "dev": true }, "uglify-js": { @@ -7174,7 +7732,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", - "dev": true, "requires": { "crypto-random-string": "^1.0.0" } @@ -7487,7 +8044,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.2.tgz", "integrity": "sha512-s0b6vB3xIVRLWywa6X9TOMA7k9zio0TMOsl9ZnDkliA/cfJlpHXAscj0gbHVJiTdIuAYpIyqS5GW91fqm6gG5g==", - "dev": true, "requires": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", @@ -7497,8 +8053,12 @@ "xdg-basedir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", - "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", - "dev": true + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=" + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" }, "y18n": { "version": "3.2.1", diff --git a/package.json b/package.json index a7ee2c6..c5d639a 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "lint": "tslint --project tsconfig.json && tslint --config tslint-alt.json 'bin/*' 'test/**/*.ts'", "lint:fix": "tslint --fix --project tsconfig.json && tslint --config tslint-alt.json 'bin/*' 'test/**/*.ts'", "test": "npm run test:unit", - "test:unit": "mocha test/unit/*.spec.ts test/unit/rates/*.spec.ts" + "test:unit": "mocha test/unit/*.spec.ts test/unit/rates/*.spec.ts test/unit/backup/*.spec.ts" }, "cross-os": { "postcompile": { @@ -36,6 +36,7 @@ }, "dependencies": { "@boltz/bolt11": "^1.2.5", + "@google-cloud/storage": "^2.5.0", "axios": "^0.18.0", "bluebird": "^3.5.4", "cors": "^2.8.5", @@ -43,6 +44,7 @@ "discord.js": "^11.4.2", "express": "^4.16.4", "grpc": "^1.20.0", + "node-schedule": "^1.3.2", "sequelize": "^5.7.0", "sqlite3": "^4.0.6", "toml": "^3.0.0", @@ -55,7 +57,8 @@ "@types/cors": "^2.8.4", "@types/express": "^4.16.1", "@types/mocha": "^5.2.6", - "@types/node": "^11.13.5", + "@types/node": "^11.13.6", + "@types/node-schedule": "^1.2.3", "@types/yargs": "^13.0.0", "chai": "^4.2.0", "concurrently": "^4.1.0", @@ -68,6 +71,6 @@ "tslint": "^5.16.0", "tslint-config-airbnb": "^5.11.1", "tslint-no-circular-imports": "^0.6.2", - "typescript": "^3.4.3" + "typescript": "^3.4.4" } } diff --git a/test/unit/backup/BackupScheduler.spec.ts b/test/unit/backup/BackupScheduler.spec.ts new file mode 100644 index 0000000..278ba8b --- /dev/null +++ b/test/unit/backup/BackupScheduler.spec.ts @@ -0,0 +1,88 @@ +import { Bucket, File } from '@google-cloud/storage'; +import { mock, instance, verify, deepEqual, when, anything, anyString } from 'ts-mockito'; +import Logger from '../../../lib/Logger'; +import Swap from '../../../lib/db/models/Swap'; +import { generateReport } from '../../../lib/report/Report'; +import SwapRepository from '../../../lib/service/SwapRepository'; +import ReverseSwapRepository from '../../../lib/service/ReverseSwapRepository'; +import BackupScheduler, { BackupConfig } from '../../../lib/backup/BackupScheduler'; + +describe('BackupScheduler', () => { + const dbPath = 'middleware.db'; + const backendDbPath = 'backend.db'; + + const swapMock = mock(Swap); + when(swapMock.fee).thenReturn(780); + when(swapMock.orderSide).thenReturn(0); + when(swapMock.pair).thenReturn('BTC/BTC'); + when(swapMock.createdAt).thenReturn('2019-04-19 09:21:01.156 +00:00'); + + const swapRepositoryMock = mock(SwapRepository); + when(swapRepositoryMock.getSwaps(anything())).thenResolve([instance(swapMock)]); + const swapRepository = instance(swapRepositoryMock); + + const reverseSwapRepositoryMock = mock(ReverseSwapRepository); + when(reverseSwapRepositoryMock.getReverseSwaps(anything())).thenResolve([]); + const reverseSwapRepository = instance(reverseSwapRepositoryMock); + + const fileMock = mock(File); + + const bucketMock = mock(Bucket); + when(bucketMock.file('report.csv')).thenReturn(instance(fileMock)); + const bucket = instance(bucketMock); + + const backupConfig: BackupConfig = { + email: '', + privatekeypath: '', + + bucketname: '', + + interval: '', + + backenddbpath: backendDbPath, + }; + + const backupScheduler = new BackupScheduler( + Logger.disabledLogger, + dbPath, + backupConfig, + swapRepository, + reverseSwapRepository, + ); + + backupScheduler['bucket'] = bucket; + + it('should upload the databases', async () => { + const date = new Date(); + const dateString = backupScheduler['getDate'](date); + + await backupScheduler.uploadDatabases(date); + + verify(bucketMock.upload(anyString(), anything())).twice(); + + verify(bucketMock.upload(dbPath, deepEqual({ + destination: `middleware/database-${dateString}.db`, + }))).once(); + + verify(bucketMock.upload(backendDbPath, deepEqual({ + destination: `backend/database-${dateString}.db`, + }))).once(); + }); + + it('should not upload backend database if path is not specified', async () => { + backupConfig.backenddbpath = ''; + + await backupScheduler.uploadDatabases(new Date()); + + verify(bucketMock.upload(anyString(), anything())).thrice(); + verify(bucketMock.upload(dbPath, anything())).twice(); + }); + + it('should upload the report', async () => { + await backupScheduler.uploadReport(); + + const data = await generateReport(swapRepository, reverseSwapRepository); + + verify(fileMock.save(data)).once(); + }); +}); diff --git a/tslint.json b/tslint.json index bed7806..16fa0b5 100644 --- a/tslint.json +++ b/tslint.json @@ -55,6 +55,17 @@ "jsdoc-format": true, "no-unnecessary-type-assertion": true, "await-promise": [true, "Bluebird"], - "no-inferrable-types": true + "no-inferrable-types": true, + "space-before-function-paren": [true, + { + "anonymous": "always", + "named": "never", + "constructor": "never", + "asyncArrow": "always", + "method": "never" + } + ], + "one-line": [true, "check-catch", "check-finally", "check-open-brace", "check-whitespace"], + "no-switch-case-fall-through": true } }