diff --git a/apps/backend/README.md b/apps/backend/README.md index e4c2f4f6..e9d4f737 100644 --- a/apps/backend/README.md +++ b/apps/backend/README.md @@ -2,10 +2,27 @@ Backend contains database migrations 📦 and cron jobs 🕒 to fetch and analyze statistics 📊 +### Config + +``` +DATABASE_URL= +RPC_URL= +NETWORK= +COINGECKO_API_KEY= +COINMARKETCAP_API_KEY= +LIVECOINWATCH_API_KEY= + +# Optional +DATABASE_CA= +DATABASE_CERT= +DATABASE_KEY= +SENTRY_DSN= +``` + ### Migrations Migrations 📦 can be applied by accessing the Docker container 🐳 and executing the following command ``` -cd apps/backend & yarn migrate +cd apps/backend && yarn migrate ``` diff --git a/apps/backend/knexfile.js b/apps/backend/knexfile.js index 3010472b..415693ea 100644 --- a/apps/backend/knexfile.js +++ b/apps/backend/knexfile.js @@ -1,7 +1,20 @@ +const ssl = { + rejectUnauthorized: true, +}; + +if (process.env.DATABASE_CA) { + ssl.ca = Buffer.from(process.env.DATABASE_CA, 'base64').toString('utf-8'); + ssl.cert = Buffer.from(process.env.DATABASE_CERT, 'base64').toString('utf-8'); + ssl.key = Buffer.from(process.env.DATABASE_KEY, 'base64').toString('utf-8'); +} + export default { production: { client: 'postgresql', - connection: process.env.DATABASE_URL, + connection: { + connectionString: process.env.DATABASE_URL, + ssl: ssl?.ca ? ssl : false, + }, migrations: { directory: './migrations', tableName: 'knex_migrations', diff --git a/apps/backend/package.json b/apps/backend/package.json index 4fbe21fd..3b6a94e7 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -20,28 +20,26 @@ "seed": "NODE_ENV=production knex seed:run" }, "dependencies": { - "@sentry/node": "7.10.0", - "@sentry/tracing": "7.10.0", - "axios": "1.6.0", - "big.js": "6.2.1", + "@sentry/node": "7.74.1", + "axios": "1.6.2", "bree": "9.1.2", - "dayjs": "1.11.4", - "knex": "2.1.0", + "envalid": "8.0.0", "lodash-es": "4.17.21", - "p-map": "5.5.0", - "pg": "8.7.3", - "pino": "8.1.0" + "p-map": "5.5.0" }, "devDependencies": { - "@types/big.js": "~6.1", "@types/lodash-es": "~4.17", - "@types/node": "~16", + "@types/node": "~20.8", "eslint-config-custom-node": "*", "knex-migrate-sql-file": "~2.0", + "nb-knex": "*", + "nb-logger": "*", + "nb-near": "*", "nb-tsconfig": "*", + "nb-types": "*", + "nb-utils": "*", "nodemon": "~2.0", - "pino-pretty": "~8.1", - "rimraf": "~3.0", + "rimraf": "~5.0", "typescript": "~5.2" } } diff --git a/apps/backend/src/config.ts b/apps/backend/src/config.ts index c0de45ca..bfad6e43 100644 --- a/apps/backend/src/config.ts +++ b/apps/backend/src/config.ts @@ -1,26 +1,20 @@ -import log from '#libs/log'; +import { cleanEnv, str } from 'envalid'; + import { Network } from '#types/enums'; import { Config } from '#types/types'; -const dbUrl = process.env.DATABASE_URL; -const rpcUrl = process.env.RPC_URL; -const cmcApiKey = process.env.COINMARKETCAP_API_KEY; -const lcwApiKey = process.env.LIVECOINWATCH_API_KEY; -const coingeckoApiKey = process.env.COINGECKO_API_KEY; - -if (!dbUrl || !rpcUrl || !cmcApiKey || !lcwApiKey || !coingeckoApiKey) { - log.error( - { - COINGECKO_API_KEY: coingeckoApiKey || null, - COINMARKETCAP_API_KEY: cmcApiKey || null, - DATABASE_URL: dbUrl || null, - LIVECOINWATCH_API_KEY: lcwApiKey || null, - RPC_NODE_URL: rpcUrl || null, - }, - 'missing config...', - ); - process.exit(); -} +const env = cleanEnv(process.env, { + COINGECKO_API_KEY: str(), + COINMARKETCAP_API_KEY: str(), + DATABASE_CA: str({ default: '' }), + DATABASE_CERT: str({ default: '' }), + DATABASE_KEY: str({ default: '' }), + DATABASE_URL: str(), + LIVECOINWATCH_API_KEY: str(), + NETWORK: str(), + RPC_URL: str(), + SENTRY_DSN: str({ default: '' }), +}); const network: Network = process.env.NETWORK === Network.TESTNET ? Network.TESTNET : Network.MAINNET; @@ -29,14 +23,17 @@ const genesisDate = network === Network.MAINNET ? '2020-07-21' : '2021-04-01'; const sentryDsn = process.env.SENTRY_DSN; const config: Config = { - cmcApiKey, - coingeckoApiKey, - dbUrl, + cmcApiKey: env.COINMARKETCAP_API_KEY, + coingeckoApiKey: env.COINGECKO_API_KEY, + dbCa: env.DATABASE_CA, + dbCert: env.DATABASE_CERT, + dbKey: env.DATABASE_KEY, + dbUrl: env.DATABASE_URL, genesisDate, genesisHeight, - lcwApiKey, + lcwApiKey: env.LIVECOINWATCH_API_KEY, network, - rpcUrl, + rpcUrl: env.RPC_URL, sentryDsn, }; diff --git a/apps/backend/src/index.ts b/apps/backend/src/index.ts index 84abffc1..f5dc6137 100644 --- a/apps/backend/src/index.ts +++ b/apps/backend/src/index.ts @@ -3,11 +3,13 @@ import { fileURLToPath } from 'url'; import Bree from 'bree'; +import { logger as log } from 'nb-logger'; + import knex from '#libs/knex'; -import log from '#libs/log'; import sentry from '#libs/sentry'; const root = path.join(path.dirname(fileURLToPath(import.meta.url)), 'jobs'); + const logger: Bree.BreeLogger = { error: () => { // @@ -19,16 +21,9 @@ const logger: Bree.BreeLogger = { // }, }; + const jobs: Bree.JobOptions[] = [ - // { name: 'stats', cron: '*/15 * * * * *', hasSeconds: true }, // every 15 seconds - // { name: 'dailyStats', cron: '1 1 * * *' }, // every day at 01:01 - // { name: 'ftMeta', cron: '* * * * *' }, // every minute - // { name: 'nftMeta', cron: '*/20 * * * * *', hasSeconds: true }, // every 20 seconds - // { name: 'marketData', cron: '* * * * *' }, // every minute - // { name: 'marketSearch', cron: '* * * * *' }, // every minute - // { name: 'ftTotalSupply', cron: '*/5 * * * * *', hasSeconds: true }, // every 5 seconds - // { name: 'refreshViews', cron: '*/15 * * * *' }, // every 15 minutes - { cron: '0 0 1 1 *', name: 'stats' }, + { cron: '0 0 1 1 * *', hasSeconds: true, name: 'stats' }, ]; const bree = new Bree({ jobs, logger, root }); diff --git a/apps/backend/src/jobs/dailyStats.ts b/apps/backend/src/jobs/dailyStats.ts deleted file mode 100644 index 184c1644..00000000 --- a/apps/backend/src/jobs/dailyStats.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { parentPort } from 'worker_threads'; - -import log from '#libs/log'; -import sentry from '#libs/sentry'; -import { sleep } from '#libs/utils'; -import { syncStats } from '#services/dailyStats'; - -(async () => { - try { - await syncStats(); - } catch (error) { - log.error(error); - sentry.captureException(error); - await sleep(1000); - } - - if (parentPort) { - return parentPort.postMessage('done'); - } - - process.exit(0); -})(); diff --git a/apps/backend/src/jobs/ftMeta.ts b/apps/backend/src/jobs/ftMeta.ts deleted file mode 100644 index e3943534..00000000 --- a/apps/backend/src/jobs/ftMeta.ts +++ /dev/null @@ -1,33 +0,0 @@ -import process from 'process'; -import { parentPort } from 'worker_threads'; - -import log from '#libs/log'; -import sentry from '#libs/sentry'; -import { sleep } from '#libs/utils'; -import { syncMeta, unSyncedTokens } from '#services/ftMeta'; - -(async () => { - try { - const contracts = await unSyncedTokens(); - - await Promise.all( - contracts.map(async (contract) => { - try { - await syncMeta(contract.emitted_by_contract_account_id); - } catch (error) { - log.error(error); - } - }), - ); - } catch (error) { - log.error(error); - sentry.captureException(error); - await sleep(1000); - } - - if (parentPort) { - return parentPort.postMessage('done'); - } - - return process.exit(0); -})(); diff --git a/apps/backend/src/jobs/ftTotalSupply.ts b/apps/backend/src/jobs/ftTotalSupply.ts deleted file mode 100644 index 20932f3e..00000000 --- a/apps/backend/src/jobs/ftTotalSupply.ts +++ /dev/null @@ -1,32 +0,0 @@ -import process from 'process'; -import { parentPort } from 'worker_threads'; - -import log from '#libs/log'; -import sentry from '#libs/sentry'; -import { sleep } from '#libs/utils'; -import { getTokens, updateTotalSupply } from '#services/ftTotalSupply'; - -(async () => { - try { - const tokens = await getTokens(); - - for await (const token of tokens) { - try { - await updateTotalSupply(token); - await sleep(1000); - } catch (error) { - log.error(error); - } - } - } catch (error) { - log.error(error); - sentry.captureException(error); - await sleep(1000); - } - - if (parentPort) { - return parentPort.postMessage('done'); - } - - return process.exit(0); -})(); diff --git a/apps/backend/src/jobs/marketData.ts b/apps/backend/src/jobs/marketData.ts deleted file mode 100644 index c5dfa41b..00000000 --- a/apps/backend/src/jobs/marketData.ts +++ /dev/null @@ -1,36 +0,0 @@ -import process from 'process'; -import { parentPort } from 'worker_threads'; - -import config from '#config'; -import log from '#libs/log'; -import sentry from '#libs/sentry'; -import { sleep } from '#libs/utils'; -import { getTokens, syncMarketData } from '#services/marketData'; -import { Network } from '#types/enums'; - -(async () => { - try { - if (config.network === Network.TESTNET) return; - - const tokens = await getTokens(); - - for await (const token of tokens) { - try { - await syncMarketData(token); - await sleep(1000); - } catch (error) { - log.error(error); - } - } - } catch (error) { - log.error(error); - sentry.captureException(error); - await sleep(1000); - } - - if (parentPort) { - return parentPort.postMessage('done'); - } - - return process.exit(0); -})(); diff --git a/apps/backend/src/jobs/marketSearch.ts b/apps/backend/src/jobs/marketSearch.ts deleted file mode 100644 index 525182ff..00000000 --- a/apps/backend/src/jobs/marketSearch.ts +++ /dev/null @@ -1,50 +0,0 @@ -import process from 'process'; -import { parentPort } from 'worker_threads'; - -import config from '#config'; -import log from '#libs/log'; -import sentry from '#libs/sentry'; -import { sleep } from '#libs/utils'; -import { - getSearchedTokens, - getTokens, - searchContract, -} from '#services/marketSearch'; -import { Network } from '#types/enums'; - -(async () => { - try { - if (config.network === Network.TESTNET) return; - - const tokens = await getTokens(); - const searchedTokens = await getSearchedTokens(); - - for await (const token of tokens) { - try { - await searchContract(token); - await sleep(1000); - } catch (error) { - log.error(error); - } - } - - for await (const searchedToken of searchedTokens) { - try { - await searchContract(searchedToken); - await sleep(1000); - } catch (error) { - log.error(error); - } - } - } catch (error) { - log.error(error); - sentry.captureException(error); - await sleep(1000); - } - - if (parentPort) { - return parentPort.postMessage('done'); - } - - return process.exit(0); -})(); diff --git a/apps/backend/src/jobs/nftMeta.ts b/apps/backend/src/jobs/nftMeta.ts deleted file mode 100644 index 8ed7e594..00000000 --- a/apps/backend/src/jobs/nftMeta.ts +++ /dev/null @@ -1,53 +0,0 @@ -import process from 'process'; -import { parentPort } from 'worker_threads'; - -import log from '#libs/log'; -import sentry from '#libs/sentry'; -import { sleep } from '#libs/utils'; -import { - syncMeta, - syncTokenMeta, - unSyncedContracts, - unSyncedTokens, -} from '#services/nftMeta'; - -(async () => { - try { - const contracts = await unSyncedContracts(); - - await Promise.all( - contracts.map(async (contract) => { - try { - await syncMeta(contract.emitted_by_contract_account_id); - } catch (error) { - log.error(error); - } - }), - ); - - const tokens = await unSyncedTokens(); - - await Promise.all( - tokens.map(async (token) => { - try { - await syncTokenMeta( - token.emitted_by_contract_account_id, - token.token_id, - ); - } catch (error) { - log.error(error); - } - }), - ); - } catch (error) { - log.error(error); - sentry.captureException(error); - await sleep(1000); - } - - if (parentPort) { - return parentPort.postMessage('done'); - } - - return process.exit(0); -})(); diff --git a/apps/backend/src/jobs/refreshViews.ts b/apps/backend/src/jobs/refreshViews.ts deleted file mode 100644 index 0852094d..00000000 --- a/apps/backend/src/jobs/refreshViews.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { parentPort } from 'worker_threads'; - -import knex from '#libs/knex'; -import log from '#libs/log'; -import sentry from '#libs/sentry'; -import { sleep } from '#libs/utils'; - -const catchError = async (error: unknown) => { - log.error(error); - sentry.captureException(error); - await sleep(1000); -}; - -(async () => { - try { - await knex.raw('REFRESH MATERIALIZED VIEW CONCURRENTLY ft_holders'); - } catch (error) { - await catchError(error); - } - try { - await knex.raw('REFRESH MATERIALIZED VIEW CONCURRENTLY nft_holders'); - } catch (error) { - await catchError(error); - } - try { - await knex.raw('REFRESH MATERIALIZED VIEW CONCURRENTLY ft_list'); - } catch (error) { - await catchError(error); - } - try { - await knex.raw('REFRESH MATERIALIZED VIEW CONCURRENTLY nft_list'); - } catch (error) { - await catchError(error); - } - - if (parentPort) { - return parentPort.postMessage('done'); - } - - process.exit(0); -})(); diff --git a/apps/backend/src/jobs/stats.ts b/apps/backend/src/jobs/stats.ts index 2a87661d..c321d448 100644 --- a/apps/backend/src/jobs/stats.ts +++ b/apps/backend/src/jobs/stats.ts @@ -1,15 +1,15 @@ import { parentPort } from 'worker_threads'; -import log from '#libs/log'; +import { logger } from 'nb-logger'; +import { sleep } from 'nb-utils'; + import sentry from '#libs/sentry'; -import { sleep } from '#libs/utils'; -import { syncStats } from '#services/stats'; (async () => { try { - await syncStats(); + // } catch (error) { - log.error(error); + logger.error(error); sentry.captureException(error); await sleep(1000); } diff --git a/apps/backend/src/libs/coinGecko.ts b/apps/backend/src/libs/coinGecko.ts deleted file mode 100644 index d50bd287..00000000 --- a/apps/backend/src/libs/coinGecko.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import axios from 'axios'; - -import config from '#config'; -import log from '#libs/log'; -import sentry from '#libs/sentry'; -import { FtMarketData } from '#types/types'; - -interface Coin { - contract: string; - id: string; -} - -const marketData = async (id: string): Promise => { - try { - const price = await axios.get( - `https://pro-api.coingecko.com/api/v3/coins/${id}?localization=false&tickers=false&community_data=false&developer_data=false&sparkline=false&x_cg_pro_api_key=${config.coingeckoApiKey}`, - { timeout: 10000 }, - ); - - return { - change_24: price?.data?.market_data?.price_change_percentage_24h ?? null, - circulating_supply: price?.data?.market_data?.circulating_supply ?? null, - description: price?.data?.description?.en ?? null, - facebook: price?.data?.links?.facebook_username ?? null, - fully_diluted_market_cap: - price?.data?.market_data?.fully_diluted_valuation?.usd ?? null, - market_cap: price?.data?.market_data?.market_cap?.usd ?? null, - price: price?.data?.market_data?.current_price?.usd ?? null, - price_btc: price?.data?.market_data?.current_price?.btc ?? null, - price_eth: price?.data?.market_data?.current_price?.eth ?? null, - reddit: price?.data?.links?.subreddit_url ?? null, - telegram: price?.data?.links?.telegram_channel_identifier ?? null, - twitter: price?.data?.links?.twitter_screen_name ?? null, - volume_24h: price?.data?.market_data?.total_volume?.usd ?? null, - website: price?.data?.links?.homepage[0] ?? null, - }; - } catch (error) { - log.error(error); - sentry.captureException(error); - return null; - } -}; - -const marketSearch = async (address: string): Promise => { - try { - let contract = address; - let platform = 'near-protocol'; - - if (address.endsWith('.factory.bridge.near')) { - platform = 'ethereum'; - contract = `0x${address.split('.')[0]}`; - } - - const coin = await axios.get( - `https://pro-api.coingecko.com/api/v3/coins/${platform}/contract/${contract}?x_cg_pro_api_key=${config.coingeckoApiKey}`, - { timeout: 10000 }, - ); - - return coin?.data?.id ?? null; - } catch (error) { - return null; - } -}; - -const coinList = async (): Promise => { - try { - const coin = await axios.get( - `https://pro-api.coingecko.com/api/v3/coins/list?include_platform=true&x_cg_pro_api_key=${config.coingeckoApiKey}`, - { timeout: 60000 }, - ); - - const contracts = coin?.data - ?.filter((coin: any) => coin?.platforms?.['near-protocol']) - .map((coin: any) => ({ - contract: coin.platforms['near-protocol'], - id: coin.id, - })); - - return Array.isArray(contracts) ? contracts : []; - } catch (error) { - return []; - } -}; - -export default { coinList, marketData, marketSearch }; diff --git a/apps/backend/src/libs/coinMarketCap.ts b/apps/backend/src/libs/coinMarketCap.ts deleted file mode 100644 index 897b061f..00000000 --- a/apps/backend/src/libs/coinMarketCap.ts +++ /dev/null @@ -1,75 +0,0 @@ -import axios from 'axios'; -import get from 'lodash/get.js'; - -import config from '#config'; -import log from '#libs/log'; -import sentry from '#libs/sentry'; -import { FtMarketData } from '#types/types'; - -const marketData = async (id: string): Promise => { - try { - const price = await axios.get( - `https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?id=${id}&convert=usd`, - { - headers: { - 'X-CMC_PRO_API_KEY': config.cmcApiKey, - }, - timeout: 10000, - }, - ); - const meta = await axios.get( - `https://pro-api.coinmarketcap.com/v1/cryptocurrency/info?id=${id}`, - { - headers: { - 'X-CMC_PRO_API_KEY': config.cmcApiKey, - }, - timeout: 10000, - }, - ); - - return { - change_24: - price?.data?.data?.[id]?.quote?.USD?.percent_change_24h ?? null, - circulating_supply: price?.data?.data?.[id]?.circulating_supply ?? null, - description: meta?.data?.data?.[id]?.description ?? null, - facebook: null, - fully_diluted_market_cap: - price?.data?.data?.[id]?.quote?.USD?.fully_diluted_market_cap ?? null, - market_cap: price?.data?.data?.[id]?.quote?.USD?.market_cap ?? null, - price: price?.data?.data?.[id]?.quote?.USD?.price ?? null, - price_btc: null, - price_eth: null, - reddit: meta?.data?.data?.[id]?.urls?.reddit?.[0] ?? null, - telegram: null, - twitter: meta?.data?.data?.[id]?.urls?.twitter?.[0] ?? null, - volume_24h: price?.data?.data?.[id]?.quote?.USD?.volume_24h ?? null, - website: meta?.data?.data?.[id]?.urls?.website?.[0] ?? null, - }; - } catch (error) { - log.error(error); - sentry.captureException(error); - return null; - } -}; - -const marketSearch = async (address: string): Promise => { - try { - const coin = await axios.get( - `https://pro-api.coinmarketcap.com/v1/cryptocurrency/info?address=${address}`, - { - headers: { - 'X-CMC_PRO_API_KEY': config.cmcApiKey, - }, - timeout: 10000, - }, - ); - const resp: unknown = Object.values(coin?.data?.data) ?? []; - const id = get(resp, '0.id'); - - return id ?? null; - } catch (error) { - return null; - } -}; - -export default { marketData, marketSearch }; diff --git a/apps/backend/src/libs/contract.ts b/apps/backend/src/libs/contract.ts deleted file mode 100644 index 25365a40..00000000 --- a/apps/backend/src/libs/contract.ts +++ /dev/null @@ -1,26 +0,0 @@ -import knex from './knex.js'; - -export const upsertErrorEntry = async ( - contract: string, - type: string, - token: null | string, -) => { - const entry = await knex('error_contracts') - .where('contract', contract) - .where('type', type) - .where('token', token) - .first(); - - if (entry) { - return await knex('error_contracts') - .where('id', entry.id) - .update({ attempts: +entry.attempts + 1 }); - } - - return await knex('error_contracts').insert({ - attempts: 1, - contract, - token, - type, - }); -}; diff --git a/apps/backend/src/libs/dayjs.ts b/apps/backend/src/libs/dayjs.ts deleted file mode 100644 index c827ed3e..00000000 --- a/apps/backend/src/libs/dayjs.ts +++ /dev/null @@ -1,8 +0,0 @@ -import dayjs from 'dayjs'; -import isSameOrBefore from 'dayjs/plugin/isSameOrBefore.js'; -import utc from 'dayjs/plugin/utc.js'; - -dayjs.extend(utc); -dayjs.extend(isSameOrBefore); - -export default dayjs; diff --git a/apps/backend/src/libs/knex.ts b/apps/backend/src/libs/knex.ts index 38621a83..77141102 100644 --- a/apps/backend/src/libs/knex.ts +++ b/apps/backend/src/libs/knex.ts @@ -1,14 +1,26 @@ -import knexPkg, { Knex } from 'knex'; +import { ConnectionOptions } from 'tls'; + +import { createKnex, Knex } from 'nb-knex'; import config from '#config'; -const { knex: createKnex } = knexPkg; -const knexConfig: Knex.Config = { - client: 'pg', - connection: config.dbUrl, - pool: { max: 1, min: 0 }, +const ssl: ConnectionOptions = { + rejectUnauthorized: true, }; -const knex = createKnex(knexConfig); +if (config.dbCa) { + ssl.ca = Buffer.from(config.dbCa, 'base64').toString('utf-8'); + ssl.cert = Buffer.from(config.dbCert, 'base64').toString('utf-8'); + ssl.key = Buffer.from(config.dbKey, 'base64').toString('utf-8'); +} + +const knex: Knex = createKnex({ + client: 'pg', + connection: { + connectionString: config.dbUrl, + ssl: ssl?.ca ? ssl : false, + }, + pool: { max: 10, min: 1 }, +}); export default knex; diff --git a/apps/backend/src/libs/liveCoinWatch.ts b/apps/backend/src/libs/liveCoinWatch.ts deleted file mode 100644 index 53e62c69..00000000 --- a/apps/backend/src/libs/liveCoinWatch.ts +++ /dev/null @@ -1,80 +0,0 @@ -import axios from 'axios'; - -import config from '#config'; -import log from '#libs/log'; -import sentry from '#libs/sentry'; -import { FtMarketData } from '#types/types'; - -const marketData = async (id: string): Promise => { - try { - const price = await axios.post( - 'https://api.livecoinwatch.com/coins/single', - { - code: id, - currency: 'USD', - meta: true, - }, - { - headers: { - 'x-api-key': config.lcwApiKey, - }, - timeout: 10000, - }, - ); - - return { - change_24: price?.data?.delta?.day ?? null, - circulating_supply: price?.data?.circulatingSupply ?? null, - description: null, - facebook: null, - fully_diluted_market_cap: null, - market_cap: price?.data?.cap ?? null, - price: price?.data?.rate ?? null, - price_btc: null, - price_eth: null, - reddit: price?.data?.links?.reddit ?? null, - telegram: price?.data?.links?.telegram ?? null, - twitter: price?.data?.links?.twitter ?? null, - volume_24h: price?.data?.volume ?? null, - website: price?.data?.links?.website ?? null, - }; - } catch (error) { - log.error(error); - sentry.captureException(error); - return null; - } -}; - -const marketSearch = async (contract: string): Promise => { - try { - let platform = 'NEAR'; - let address = contract; - - if (contract.endsWith('.factory.bridge.near')) { - platform = 'ETH'; - address = `0x${contract.split('.')[0]}`; - } - - const coin = await axios.post( - 'https://api.livecoinwatch.com/coins/contract', - { - address, - currency: 'USD', - meta: false, - platform, - }, - { - headers: { - 'x-api-key': config.lcwApiKey, - }, - timeout: 10000, - }, - ); - - return coin?.data?.code ?? null; - } catch (error) { - return null; - } -}; - -export default { marketData, marketSearch }; diff --git a/apps/backend/src/libs/log.ts b/apps/backend/src/libs/log.ts deleted file mode 100644 index dbee9531..00000000 --- a/apps/backend/src/libs/log.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { pino } from 'pino'; - -const options = - process.env.NODE_ENV === 'production' - ? {} - : { - transport: { - options: { - colorize: true, - }, - target: 'pino-pretty', - }, - }; -const log = pino(options); - -export default log; diff --git a/apps/backend/src/libs/near.ts b/apps/backend/src/libs/near.ts deleted file mode 100644 index 359c1023..00000000 --- a/apps/backend/src/libs/near.ts +++ /dev/null @@ -1,77 +0,0 @@ -import axios from 'axios'; - -import config from '#config'; -import { decodeResults } from '#libs/utils'; -import { FtMetadata, NftMetadata, NftTokenInfo } from '#types/types'; - -const rpcQuery = async (method: string, params: unknown) => { - return axios.post(config.rpcUrl, { - id: 'dontcare', - jsonrpc: '2.0', - method, - params, - }); -}; - -export const validators = async () => { - const response = await rpcQuery('validators', [null]); - - return Number(response?.data?.result?.current_validators?.length || 0); -}; - -export const ftMeta = async (contract: string): Promise => { - const response = await rpcQuery('query', { - account_id: contract, - args_base64: '', - finality: 'final', - method_name: 'ft_metadata', - request_type: 'call_function', - }); - - return decodeResults(response.data?.result?.result); -}; - -export const ftTotalSupply = async (contract: string): Promise => { - const response = await rpcQuery('query', { - account_id: contract, - args_base64: '', - finality: 'final', - method_name: 'ft_total_supply', - request_type: 'call_function', - }); - - return decodeResults(response.data?.result?.result); -}; - -export const nftMeta = async (contract: string): Promise => { - const response = await rpcQuery('query', { - account_id: contract, - args_base64: '', - finality: 'final', - method_name: 'nft_metadata', - request_type: 'call_function', - }); - - return decodeResults(response.data?.result?.result); -}; - -export const nftTokenMeta = async ( - contract: string, - token: string, -): Promise => { - const args = Buffer.from( - JSON.stringify({ - token_id: token, - }), - ).toString('base64'); - - const response = await rpcQuery('query', { - account_id: contract, - args_base64: args, - finality: 'final', - method_name: 'nft_token', - request_type: 'call_function', - }); - - return decodeResults(response.data?.result?.result); -}; diff --git a/apps/backend/src/libs/sentry.ts b/apps/backend/src/libs/sentry.ts index fc97685a..2ce80a51 100644 --- a/apps/backend/src/libs/sentry.ts +++ b/apps/backend/src/libs/sentry.ts @@ -1,10 +1,10 @@ import * as Sentry from '@sentry/node'; -import '@sentry/tracing'; import config from '#config'; Sentry.init({ dsn: config.sentryDsn, + integrations: [...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations()], tracesSampleRate: 1.0, }); diff --git a/apps/backend/src/libs/utils.ts b/apps/backend/src/libs/utils.ts deleted file mode 100644 index 460b82ef..00000000 --- a/apps/backend/src/libs/utils.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Big } from 'big.js'; - -const MS_PER_NS = Big(10).pow(6); -const YOCTO_PER_NEAR = Big(10).pow(24); - -export const sleep = (ms: number) => { - return new Promise((resolve) => setTimeout(resolve, ms)); -}; - -export const msToNsTime = (ms: number | string) => { - return Big(ms).mul(MS_PER_NS).toFixed(); -}; - -export const nsToMsTime = (ns: number | string) => { - return Big(ns).div(MS_PER_NS).toNumber(); -}; - -export const yoctoToNear = (yocto: number | string) => { - return Big(yocto).div(YOCTO_PER_NEAR).toFixed(); -}; - -export const fiatValue = (amount: number | string, price: number | string) => { - return Big(amount).mul(Big(price)).toFixed(); -}; - -export const txnFees = ( - tokensBurntByTxn: number | string, - tokensBurntByReceipts: number | string, -) => { - return Big(tokensBurntByTxn).add(Big(tokensBurntByReceipts)).toFixed(); -}; - -export const decodeResults = (buffer: ArrayBuffer): T => - JSON.parse(Buffer.from(buffer).toString()); - -export const tokenAmount = (amount: string, decimal = 18) => { - if (amount === undefined || amount === null) return ''; - - return Big(amount).div(Big(10).pow(+decimal)).toFixed(); -}; diff --git a/apps/backend/src/services/dailyStats.ts b/apps/backend/src/services/dailyStats.ts deleted file mode 100644 index 56b1470b..00000000 --- a/apps/backend/src/services/dailyStats.ts +++ /dev/null @@ -1,184 +0,0 @@ -import axios from 'axios'; -import { Big } from 'big.js'; -import { Dayjs } from 'dayjs'; -import PMap from 'p-map'; - -import config from '#config'; -import dayjs from '#libs/dayjs'; -import knex from '#libs/knex'; -import { - fiatValue, - msToNsTime, - nsToMsTime, - sleep, - txnFees, - yoctoToNear, -} from '#libs/utils'; -import { Network } from '#types/enums'; - -const marketData = async (date: Dayjs) => { - if (config.network === Network.TESTNET) { - return { - market_cap: 0, - near_price: 0, - }; - } - - const start = date.format('DD-MM-YYYY'); - const price = await axios.get( - `https://api.coingecko.com/api/v3/coins/near/history?date=${start}&localization=false`, - { timeout: 10000 }, - ); - - return { - market_cap: Number(price?.data?.market_data?.market_cap?.usd || 0), - near_price: Number(price?.data?.market_data?.current_price?.usd || 0), - }; -}; - -const blockData = async (start: string, end: string) => { - const blocks = await knex('blocks') - .whereBetween('block_timestamp', [start, end]) - .count() - .first(); - - const supply = await knex('blocks') - .whereBetween('block_timestamp', [start, end]) - .orderBy('block_timestamp', 'desc') - .limit(1) - .first('total_supply'); - - return { - avg_gas_limit: 0, - avg_gas_price: '0', - blocks: Number(blocks?.count || 0), - gas_fee: '0', - gas_used: '0', - total_supply: supply?.total_supply || '0', - }; -}; - -const txnData = async (start: string, end: string, price: number) => { - const txns = await knex('transactions') - .whereBetween('block_timestamp', [start, end]) - .count() - .first(); - - const volume = await knex('action_receipt_actions') - .whereBetween('receipt_included_in_block_timestamp', [start, end]) - .where('action_kind', 'TRANSFER') - .sum({ sum: knex.raw("CAST(args->>'deposit' as NUMERIC)") }) - .first(); - - const volumeInNear = yoctoToNear(volume?.sum || 0); - const volumeUSD = fiatValue(volumeInNear, price); - - const tokensBurntByTxn = await knex('transactions') - .whereBetween('block_timestamp', [start, end]) - .sum('receipt_conversion_tokens_burnt') - .first(); - - const tokensBurntByReceipts = await knex('execution_outcomes') - .whereBetween('executed_in_block_timestamp', [start, end]) - .sum('tokens_burnt') - .first(); - - const txnFee = txnFees( - tokensBurntByTxn?.sum || 0, - tokensBurntByReceipts?.sum || 0, - ); - - const txnFeeInNear = yoctoToNear(txnFee); - const txnFeeUsd = fiatValue(txnFeeInNear, price); - - return { - txn_fee: txnFee, - txn_fee_usd: Number(txnFeeUsd), - txn_volume: String(volume?.sum || 0), - txn_volume_usd: Number(volumeUSD), - txns: Number(txns?.count || 0), - }; -}; - -const addressData = async (start: string, end: string) => { - const latest = await knex('daily_stats').orderBy('date', 'desc').first(); - - const accounts = await knex('accounts') - .join('blocks', 'accounts.last_update_block_height', 'blocks.block_height') - .whereBetween('blocks.block_timestamp', [start, end]) - .count() - .first(); - - const count = Number(accounts?.count || 0); - const lastTotal = latest?.total_addresses ?? 0; - const total = Big(lastTotal).add(Big(count)).toFixed(); - - return { - addresses: count, - total_addresses: Number(total), - }; -}; - -const dayStats = async (date: string) => { - const day = dayjs.utc(date); - - if (dayjs.utc().isSameOrBefore(day)) return; - - const start = msToNsTime(day.startOf('day').valueOf()); - const end = msToNsTime(day.endOf('day').valueOf()); - - const price = await marketData(day); - const block = await blockData(start, end); - const txn = await txnData(start, end, price.near_price); - const address = await addressData(start, end); - - const data = { - date: day.format('YYYY-MM-DD'), - ...price, - ...block, - ...txn, - ...address, - }; - - await knex('daily_stats').insert(data); -}; - -export const syncStats = async () => { - let start = dayjs.utc(config.genesisDate); - const latestBlock = await knex - .table('blocks') - .orderBy('block_timestamp', 'desc') - .first(); - - if (!latestBlock || dayjs.utc().isSameOrBefore(start, 'day')) return; - - const end = dayjs - .utc(nsToMsTime(latestBlock.block_timestamp)) - .startOf('day') - .subtract(1, 'day'); - - const latestStat = await knex - .table('daily_stats') - .orderBy('date', 'desc') - .first(); - - if (latestStat) { - start = dayjs.utc(latestStat.date).add(1, 'day'); - } - - let diff = end.diff(start, 'day'); - diff = diff < 20 ? diff + 1 : 20; - - if (diff < 1) return; - - await PMap( - [...Array(diff)], - async (_, i) => { - const date = start.clone().add(i, 'day'); - - await dayStats(date.format('YYYY-MM-DD')); - await sleep(1000); - }, - { concurrency: 1 }, - ); -}; diff --git a/apps/backend/src/services/ftMeta.ts b/apps/backend/src/services/ftMeta.ts deleted file mode 100644 index 9676ffe5..00000000 --- a/apps/backend/src/services/ftMeta.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { upsertErrorEntry } from '#libs/contract'; -import dayjs from '#libs/dayjs'; -import knex from '#libs/knex'; -import { ftMeta } from '#libs/near'; - -export const unSyncedTokens = async () => { - return knex - .select('emitted_by_contract_account_id') - .from( - knex - .select('emitted_by_contract_account_id') - .fromRaw('assets__fungible_token_events a') - .whereNotExists( - knex - .select(1) - .fromRaw('error_contracts e') - .whereRaw('a.emitted_by_contract_account_id = e.contract') - .where('type', 'ft') - .where('attempts', '>=', 5), - ) - .groupBy('emitted_by_contract_account_id') - .as('a'), - ) - .whereNotExists( - knex - .select('*') - .from('ft_meta') - .whereRaw('a.emitted_by_contract_account_id = ft_meta.contract'), - ) - .limit(10); -}; - -export const syncMeta = async (contract: string) => { - try { - const meta = await ftMeta(contract); - - if (meta?.name && meta?.symbol) { - return knex('ft_meta') - .insert({ - contract, - decimals: meta.decimals, - icon: meta.icon || null, - name: meta.name, - reference: meta.reference || null, - reference_hash: meta.reference_hash || null, - spec: meta.spec, - symbol: meta.symbol, - updated_at: dayjs.utc().toISOString(), - }) - .onConflict('contract') - .ignore(); - } - - return upsertErrorEntry(contract, 'ft', null); - } catch (error) { - return upsertErrorEntry(contract, 'ft', null); - } -}; diff --git a/apps/backend/src/services/ftTotalSupply.ts b/apps/backend/src/services/ftTotalSupply.ts deleted file mode 100644 index bdbd66da..00000000 --- a/apps/backend/src/services/ftTotalSupply.ts +++ /dev/null @@ -1,30 +0,0 @@ -import dayjs from '#libs/dayjs'; -import knex from '#libs/knex'; -import { ftTotalSupply } from '#libs/near'; -import { tokenAmount } from '#libs/utils'; -import { FtMeta } from '#types/types'; - -export const getTokens = async () => { - return knex('ft_meta').orderByRaw('rpccall_at ASC NULLS FIRST').limit(5); -}; - -export const updateTotalSupply = async (ftMeta: FtMeta) => { - try { - const data = await ftTotalSupply(ftMeta.contract); - - if (data) { - const totalSupply = tokenAmount(data, ftMeta.decimals); - - return knex('ft_meta').where('contract', ftMeta.contract).update({ - rpccall_at: dayjs.utc().toISOString(), - total_supply: +totalSupply, - }); - } - } catch (error) { - // - } - - return knex('ft_meta').where('contract', ftMeta.contract).update({ - rpccall_at: dayjs.utc().toISOString(), - }); -}; diff --git a/apps/backend/src/services/marketData.ts b/apps/backend/src/services/marketData.ts deleted file mode 100644 index dfc62d8d..00000000 --- a/apps/backend/src/services/marketData.ts +++ /dev/null @@ -1,55 +0,0 @@ -import isNull from 'lodash/isNull.js'; -import omitBy from 'lodash/omitBy.js'; - -import coinGecko from '#libs/coinGecko'; -import coinMarketCap from '#libs/coinMarketCap'; -import dayjs from '#libs/dayjs'; -import knex from '#libs/knex'; -import liveCoinWatch from '#libs/liveCoinWatch'; -import { FtMarketData, FtMeta } from '#types/types'; - -export const getTokens = async () => { - return knex('ft_meta') - .where(function () { - this.whereNotNull('coingecko_id') - .orWhereNotNull('coinmarketcap_id') - .orWhereNotNull('livecoinwatch_id'); - }) - .where(function () { - this.whereNull('updated_at').orWhere( - 'updated_at', - '<', - dayjs.utc().subtract(5, 'minutes').toISOString(), - ); - }) - .orderBy('updated_at', 'asc') - .limit(5); -}; - -export const syncMarketData = async (ftMeta: FtMeta) => { - let coinMarketData: FtMarketData | null = null; - let coinGeckoData: FtMarketData | null = null; - let liveCoinWatchData: FtMarketData | null = null; - - if (ftMeta.coinmarketcap_id) { - coinMarketData = await coinMarketCap.marketData(ftMeta.coinmarketcap_id); - } - - if (ftMeta.coingecko_id) { - coinGeckoData = await coinGecko.marketData(ftMeta.coingecko_id); - } - - if (ftMeta.livecoinwatch_id) { - liveCoinWatchData = await liveCoinWatch.marketData(ftMeta.livecoinwatch_id); - } - - const marketData = { - ...omitBy(coinMarketData, isNull), - ...omitBy(coinGeckoData, isNull), - ...omitBy(liveCoinWatchData, isNull), - }; - - await knex('ft_meta') - .where('contract', ftMeta.contract) - .update({ ...(marketData || {}), updated_at: dayjs.utc().toISOString() }); -}; diff --git a/apps/backend/src/services/marketSearch.ts b/apps/backend/src/services/marketSearch.ts deleted file mode 100644 index be187252..00000000 --- a/apps/backend/src/services/marketSearch.ts +++ /dev/null @@ -1,89 +0,0 @@ -import coinGecko from '#libs/coinGecko'; -import coinMarketCap from '#libs/coinMarketCap'; -import dayjs from '#libs/dayjs'; -import knex from '#libs/knex'; -import liveCoinWatch from '#libs/liveCoinWatch'; -import { FtMeta } from '#types/types'; - -export const getTokens = async () => { - return knex('ft_meta') - .whereNull('coingecko_id') - .whereNull('coinmarketcap_id') - .whereNull('livecoinwatch_id') - .whereNull('searched_at') - .orderBy('updated_at', 'asc') - .limit(2); -}; - -export const getSearchedTokens = async () => { - return knex('ft_meta') - .where(function () { - this.whereNull('searched_at').orWhere( - 'searched_at', - '<', - dayjs.utc().subtract(7, 'days').toISOString(), - ); - }) - .where(function () { - this.whereNull('coingecko_id') - .orWhereNull('coinmarketcap_id') - .orWhereNull('livecoinwatch_id'); - }) - .orderBy('searched_at', 'asc') - .limit(1); -}; - -export const searchContract = async (ftMeta: FtMeta) => { - if (ftMeta.contract === 'aurora') { - return await knex('ft_meta').where('contract', ftMeta.contract).update({ - coingecko_id: 'ethereum', - }); - } - - const [cmcId, coinGeckoId, liveCoinWatchId] = await Promise.all([ - coinMarketCap.marketSearch(ftMeta.contract), - coinGecko.marketSearch(ftMeta.contract), - liveCoinWatch.marketSearch(ftMeta.contract), - ]); - - if (cmcId || coinGeckoId || liveCoinWatchId) { - const marketId = { - ...(cmcId && { coinmarketcap_id: cmcId }), - ...(coinGeckoId && { coingecko_id: coinGeckoId }), - ...(liveCoinWatchId && { livecoinwatch_id: liveCoinWatchId }), - searched_at: dayjs.utc().toISOString(), - updated_at: dayjs.utc().toISOString(), - }; - - return await knex('ft_meta') - .where('contract', ftMeta.contract) - .update(marketId); - } - - return await knex('ft_meta').where('contract', ftMeta.contract).update({ - searched_at: dayjs.utc().toISOString(), - updated_at: dayjs.utc().toISOString(), - }); -}; - -export const searchContractList = async () => { - const coinList = await coinGecko.coinList(); - - if (!coinList.length) return; - - const contracts = coinList.map((coin) => coin.contract); - - const tokens = await knex('ft_meta') - .whereIn('contract', contracts) - .whereNull('coingecko_id'); - - for await (const token of tokens) { - const coin = coinList.find((coin) => coin.contract === token.contract); - - if (coin) { - await knex('ft_meta') - .where('contract', coin.contract) - .update({ coingecko_id: coin.id }); - } - } -}; diff --git a/apps/backend/src/services/nftMeta.ts b/apps/backend/src/services/nftMeta.ts deleted file mode 100644 index 8c52f60d..00000000 --- a/apps/backend/src/services/nftMeta.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { upsertErrorEntry } from '#libs/contract'; -import knex from '#libs/knex'; -import { nftMeta, nftTokenMeta } from '#libs/near'; - -export const unSyncedContracts = async () => { - return knex - .select('emitted_by_contract_account_id') - .from( - knex - .select('emitted_by_contract_account_id') - .fromRaw('assets__non_fungible_token_events a') - .whereNotExists( - knex - .select(1) - .fromRaw('error_contracts e') - .whereRaw('a.emitted_by_contract_account_id = e.contract') - .where('type', 'nft') - .whereNull('token') - .where('attempts', '>=', 5), - ) - .groupBy('emitted_by_contract_account_id') - .as('a'), - ) - .whereNotExists( - knex - .select('*') - .fromRaw('nft_meta n') - .whereRaw('a.emitted_by_contract_account_id = n.contract'), - ) - .limit(10); -}; - -export const unSyncedTokens = async () => { - return knex - .select('emitted_by_contract_account_id', 'token_id') - .from( - knex - .select('emitted_by_contract_account_id', 'token_id') - .fromRaw('assets__non_fungible_token_events a') - .whereNotExists( - knex - .select(1) - .fromRaw('error_contracts e') - .whereRaw('a.emitted_by_contract_account_id = e.contract') - .whereRaw('a.token_id = e.token') - .where('type', 'nft') - .where('attempts', '>=', 5), - ) - .groupBy('emitted_by_contract_account_id', 'token_id') - .as('a'), - ) - .whereNotExists( - knex - .select(1) - .fromRaw('nft_token_meta n') - .whereRaw('a.emitted_by_contract_account_id = n.contract') - .whereRaw('a.token_id = n.token'), - ) - .limit(25); -}; - -export const syncMeta = async (contract: string) => { - try { - const meta = await nftMeta(contract); - - if (meta?.name && meta?.symbol) { - return knex('nft_meta') - .insert({ - base_uri: meta.base_uri || null, - contract, - icon: meta.icon || null, - name: meta.name, - reference: meta.reference || null, - reference_hash: meta.reference_hash || null, - spec: meta.spec || null, - symbol: meta.symbol, - }) - .onConflict('contract') - .ignore(); - } - - return upsertErrorEntry(contract, 'nft', null); - } catch (error) { - return upsertErrorEntry(contract, 'nft', null); - } -}; - -export const syncTokenMeta = async (contract: string, token: string) => { - try { - const meta = await nftTokenMeta(contract, token); - - if (meta?.metadata) { - return knex('nft_token_meta') - .insert({ - contract, - copies: meta.metadata.copies || null, - description: meta.metadata.description || null, - extra: meta.metadata.extra || null, - media: meta.metadata.media || null, - media_hash: meta.metadata.media_hash || null, - reference: meta.metadata.reference || null, - reference_hash: meta.metadata.reference_hash || null, - title: meta.metadata.title || null, - token, - }) - .onConflict(['contract', 'token']) - .ignore(); - } - - return upsertErrorEntry(contract, 'nft', token); - } catch (error) { - return upsertErrorEntry(contract, 'nft', token); - } -}; diff --git a/apps/backend/src/services/stats.ts b/apps/backend/src/services/stats.ts deleted file mode 100644 index cde02032..00000000 --- a/apps/backend/src/services/stats.ts +++ /dev/null @@ -1,94 +0,0 @@ -import axios from 'axios'; -import { Big } from 'big.js'; - -import knex from '#libs/knex'; -import { validators } from '#libs/near'; -import { msToNsTime } from '#libs/utils'; - -const blockTime = async (timestamp: string) => { - const start = Big(timestamp) - .minus(Big(msToNsTime(60000))) - .toFixed(); - - const blocks = await knex('blocks') - .where('block_timestamp', '>', start) - .count() - .first(); - - if (!blocks?.count) return 0; - - return Number((60 / +blocks.count).toFixed(4)); -}; - -const blockData = async () => { - const latestBlock = await knex('blocks') - .orderBy('block_height', 'desc') - .first('block_height', 'total_supply', 'gas_price', 'block_timestamp'); - - if (!latestBlock) { - throw Error('No blocks'); - } - - const avgBlockTime = await blockTime(latestBlock.block_timestamp); - - return { - avg_block_time: avgBlockTime, - block: latestBlock.block_height, - gas_price: latestBlock.gas_price, - total_supply: latestBlock.total_supply, - }; -}; - -const marketData = async () => { - const price = await axios.get( - 'https://api.coingecko.com/api/v3/coins/near?localization=false&tickers=false&community_data=false&developer_data=false&sparkline=false', - { timeout: 10000 }, - ); - - return { - change_24: Number(price.data.market_data.price_change_percentage_24h || 0), - high_24h: Number(price.data.market_data.high_24h.usd || 0), - high_all: Number(price.data.market_data.ath.usd || 0), - low_24h: Number(price.data.market_data.low_24h.usd || 0), - low_all: Number(price.data.market_data.atl.usd || 0), - market_cap: Number(price.data.market_data.market_cap.usd || 0), - near_btc_price: Number(price.data.market_data.current_price.btc || 0), - near_price: Number(price.data.market_data.current_price.usd || 0), - volume: Number(price.data.market_data.total_volume.usd || 0), - }; -}; - -const networkData = async () => { - const network = await validators(); - - return { - nodes_online: network, - }; -}; - -const transactionData = async () => { - const txns = await knex('transactions') - .count('block_timestamp') - .where('block_timestamp', '>', 0) - .first(); - - return { - total_txns: Number(txns?.count || 0), - }; -}; - -export const syncStats = async () => { - const block = await blockData(); - const price = await marketData(); - const network = await networkData(); - const txn = await transactionData(); - const data = { ...block, ...price, ...network, ...txn }; - - const stats = await knex('stats').first(); - - if (stats) { - return await knex('stats').update(data); - } - - return await knex('stats').insert(data); -}; diff --git a/apps/backend/src/types/knex.d.ts b/apps/backend/src/types/knex.d.ts new file mode 100644 index 00000000..dbf6f80a --- /dev/null +++ b/apps/backend/src/types/knex.d.ts @@ -0,0 +1,7 @@ +import 'knex/types/tables'; + +import { TTables } from 'nb-types'; + +declare module 'knex/types/tables.js' { + interface Tables extends TTables {} +} diff --git a/apps/backend/src/types/knex.ts b/apps/backend/src/types/knex.ts deleted file mode 100644 index ad03b8cc..00000000 --- a/apps/backend/src/types/knex.ts +++ /dev/null @@ -1,47 +0,0 @@ -import 'knex/types/tables'; - -import { - AccessKey, - Account, - ActionReceiptAction, - ActionReceiptOutputData, - AssetsFungibleTokenEvent, - AssetsNonFungibleTokenEvent, - Block, - Chunk, - DailyStats, - ErrorContract, - ExecutionOutcome, - ExecutionOutcomeReceipt, - FtMeta, - NftMeta, - NftTokenMeta, - Receipt, - Setting, - Stats, - Transaction, -} from '#types/types'; - -declare module 'knex/types/tables.js' { - interface Tables { - access_keys: AccessKey; - accounts: Account; - action_receipt_actions: ActionReceiptAction; - action_receipt_output_data: ActionReceiptOutputData; - assets__fungible_token_events: AssetsFungibleTokenEvent; - assets__non_fungible_token_events: AssetsNonFungibleTokenEvent; - blocks: Block; - chunks: Chunk; - daily_stats: DailyStats; - error_contracts: ErrorContract; - execution_outcome_receipts: ExecutionOutcomeReceipt; - execution_outcomes: ExecutionOutcome; - ft_meta: FtMeta; - nft_meta: NftMeta; - nft_token_meta: NftTokenMeta; - receipts: Receipt; - settings: Setting; - stats: Stats; - transactions: Transaction; - } -} diff --git a/apps/backend/src/types/types.ts b/apps/backend/src/types/types.ts index a2b58581..fd80583e 100644 --- a/apps/backend/src/types/types.ts +++ b/apps/backend/src/types/types.ts @@ -267,6 +267,9 @@ export type JsonValue = export interface Config { cmcApiKey: string; coingeckoApiKey?: string; + dbCa: string; + dbCert: string; + dbKey: string; dbUrl: string; genesisDate: string; genesisHeight: number; diff --git a/apps/indexer-balance/README.md b/apps/indexer-balance/README.md index 33ed98ba..208fa917 100644 --- a/apps/indexer-balance/README.md +++ b/apps/indexer-balance/README.md @@ -2,7 +2,7 @@ Balance indexer collects the info about native NEAR token balance changes 📊 (all the changes are validated ✔️) -## Config +### Config ``` DATABASE_URL= @@ -11,4 +11,10 @@ RPC_URL= NETWORK=mainnet AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= + +# Optional +DATABASE_CA= +DATABASE_CERT= +DATABASE_KEY= +SENTRY_DSN= ``` diff --git a/apps/indexer-balance/src/config.ts b/apps/indexer-balance/src/config.ts index ecd5ddc5..8708408f 100644 --- a/apps/indexer-balance/src/config.ts +++ b/apps/indexer-balance/src/config.ts @@ -5,6 +5,9 @@ import { Network } from 'nb-types'; import { Config } from '#types/types'; const env = cleanEnv(process.env, { + DATABASE_CA: str({ default: '' }), + DATABASE_CERT: str({ default: '' }), + DATABASE_KEY: str({ default: '' }), DATABASE_URL: str(), NETWORK: str(), REDIS_URL: str(), @@ -20,7 +23,10 @@ const s3BucketName = : 'near-lake-data-testnet'; const config: Config = { - cacheExpiry: 60 * 60 * 24, // cache expiry time in seconds (1 day) + cacheExpiry: 60 * 60, // cache expiry time in seconds (1 hour) + dbCa: env.DATABASE_CA, + dbCert: env.DATABASE_CERT, + dbKey: env.DATABASE_KEY, dbUrl: env.DATABASE_URL, delta: 100, // start from blocks earlier on sync interuption insertLimit: 1_000, // records to insert into the db at a time diff --git a/apps/indexer-balance/src/libs/knex.ts b/apps/indexer-balance/src/libs/knex.ts index 0ba1894a..77141102 100644 --- a/apps/indexer-balance/src/libs/knex.ts +++ b/apps/indexer-balance/src/libs/knex.ts @@ -1,7 +1,26 @@ +import { ConnectionOptions } from 'tls'; + import { createKnex, Knex } from 'nb-knex'; import config from '#config'; -const knex: Knex = createKnex(config.dbUrl); +const ssl: ConnectionOptions = { + rejectUnauthorized: true, +}; + +if (config.dbCa) { + ssl.ca = Buffer.from(config.dbCa, 'base64').toString('utf-8'); + ssl.cert = Buffer.from(config.dbCert, 'base64').toString('utf-8'); + ssl.key = Buffer.from(config.dbKey, 'base64').toString('utf-8'); +} + +const knex: Knex = createKnex({ + client: 'pg', + connection: { + connectionString: config.dbUrl, + ssl: ssl?.ca ? ssl : false, + }, + pool: { max: 10, min: 1 }, +}); export default knex; diff --git a/apps/indexer-balance/src/types/types.ts b/apps/indexer-balance/src/types/types.ts index f8ccace3..bc48c6e2 100644 --- a/apps/indexer-balance/src/types/types.ts +++ b/apps/indexer-balance/src/types/types.ts @@ -2,6 +2,9 @@ import { Network, StateChangeCauseView, StateChangeValueView } from 'nb-types'; export type Config = { cacheExpiry: number; + dbCa: string; + dbCert: string; + dbKey: string; dbUrl: string; delta: number; insertLimit: number; diff --git a/apps/indexer-base/README.md b/apps/indexer-base/README.md index d74da147..0d7f7b9c 100644 --- a/apps/indexer-base/README.md +++ b/apps/indexer-base/README.md @@ -10,4 +10,10 @@ REDIS_URL= NETWORK=mainnet AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= + +# Optional +DATABASE_CA= +DATABASE_CERT= +DATABASE_KEY= +SENTRY_DSN= ``` diff --git a/apps/indexer-base/src/config.ts b/apps/indexer-base/src/config.ts index 1e299e32..a4c59bae 100644 --- a/apps/indexer-base/src/config.ts +++ b/apps/indexer-base/src/config.ts @@ -5,6 +5,9 @@ import { Network } from 'nb-types'; import { Config } from '#types/types'; const env = cleanEnv(process.env, { + DATABASE_CA: str({ default: '' }), + DATABASE_CERT: str({ default: '' }), + DATABASE_KEY: str({ default: '' }), DATABASE_URL: str(), NETWORK: str(), REDIS_URL: str(), @@ -24,6 +27,9 @@ const s3BucketName = const config: Config = { cacheExpiry: 5 * 60, // cache expiry time in seconds (5 min) + dbCa: env.DATABASE_CA, + dbCert: env.DATABASE_CERT, + dbKey: env.DATABASE_KEY, dbUrl: env.DATABASE_URL, delta: 1_000, // start from blocks earlier on sync interuption genesisFile: genesisFile, // url to download genesis data diff --git a/apps/indexer-base/src/libs/knex.ts b/apps/indexer-base/src/libs/knex.ts index 0ba1894a..77141102 100644 --- a/apps/indexer-base/src/libs/knex.ts +++ b/apps/indexer-base/src/libs/knex.ts @@ -1,7 +1,26 @@ +import { ConnectionOptions } from 'tls'; + import { createKnex, Knex } from 'nb-knex'; import config from '#config'; -const knex: Knex = createKnex(config.dbUrl); +const ssl: ConnectionOptions = { + rejectUnauthorized: true, +}; + +if (config.dbCa) { + ssl.ca = Buffer.from(config.dbCa, 'base64').toString('utf-8'); + ssl.cert = Buffer.from(config.dbCert, 'base64').toString('utf-8'); + ssl.key = Buffer.from(config.dbKey, 'base64').toString('utf-8'); +} + +const knex: Knex = createKnex({ + client: 'pg', + connection: { + connectionString: config.dbUrl, + ssl: ssl?.ca ? ssl : false, + }, + pool: { max: 10, min: 1 }, +}); export default knex; diff --git a/apps/indexer-base/src/types/types.ts b/apps/indexer-base/src/types/types.ts index 5623e003..a3416e3b 100644 --- a/apps/indexer-base/src/types/types.ts +++ b/apps/indexer-base/src/types/types.ts @@ -4,6 +4,9 @@ import { AccessKeyPermissionKind, ActionKind, Network } from 'nb-types'; export interface Config { cacheExpiry: number; + dbCa: string; + dbCert: string; + dbKey: string; dbUrl: string; delta: number; genesisFile: string; diff --git a/apps/indexer-events/README.md b/apps/indexer-events/README.md index f3274470..f34ccede 100644 --- a/apps/indexer-events/README.md +++ b/apps/indexer-events/README.md @@ -13,4 +13,10 @@ RPC_URL= NETWORK=mainnet AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= + +# Optional +DATABASE_CA= +DATABASE_CERT= +DATABASE_KEY= +SENTRY_DSN= ``` diff --git a/apps/indexer-events/package.json b/apps/indexer-events/package.json index aa76b4d4..3083fab0 100644 --- a/apps/indexer-events/package.json +++ b/apps/indexer-events/package.json @@ -17,7 +17,7 @@ }, "dependencies": { "@sentry/node": "7.74.1", - "axios": "1.5.1", + "axios": "1.6.2", "borsh": "1.0.0", "envalid": "8.0.0", "globby": "13.2.2", diff --git a/apps/indexer-events/src/config.ts b/apps/indexer-events/src/config.ts index f2f2782a..53e9e102 100644 --- a/apps/indexer-events/src/config.ts +++ b/apps/indexer-events/src/config.ts @@ -5,6 +5,9 @@ import { Network } from 'nb-types'; import { Config } from '#types/types'; const env = cleanEnv(process.env, { + DATABASE_CA: str({ default: '' }), + DATABASE_CERT: str({ default: '' }), + DATABASE_KEY: str({ default: '' }), DATABASE_URL: str(), NETWORK: str(), REDIS_URL: str(), @@ -20,7 +23,10 @@ const s3BucketName = : 'near-lake-data-testnet'; const config: Config = { - cacheExpiry: 60 * 60 * 24, // cache expiry time in seconds (1 day) + cacheExpiry: 60 * 60, // cache expiry time in seconds (1 hour) + dbCa: env.DATABASE_CA, + dbCert: env.DATABASE_CERT, + dbKey: env.DATABASE_KEY, dbUrl: env.DATABASE_URL, delta: 100, // start from blocks earlier on sync interuption insertLimit: 1_000, // records to insert into the db at a time diff --git a/apps/indexer-events/src/libs/knex.ts b/apps/indexer-events/src/libs/knex.ts index 0ba1894a..77141102 100644 --- a/apps/indexer-events/src/libs/knex.ts +++ b/apps/indexer-events/src/libs/knex.ts @@ -1,7 +1,26 @@ +import { ConnectionOptions } from 'tls'; + import { createKnex, Knex } from 'nb-knex'; import config from '#config'; -const knex: Knex = createKnex(config.dbUrl); +const ssl: ConnectionOptions = { + rejectUnauthorized: true, +}; + +if (config.dbCa) { + ssl.ca = Buffer.from(config.dbCa, 'base64').toString('utf-8'); + ssl.cert = Buffer.from(config.dbCert, 'base64').toString('utf-8'); + ssl.key = Buffer.from(config.dbKey, 'base64').toString('utf-8'); +} + +const knex: Knex = createKnex({ + client: 'pg', + connection: { + connectionString: config.dbUrl, + ssl: ssl?.ca ? ssl : false, + }, + pool: { max: 10, min: 1 }, +}); export default knex; diff --git a/apps/indexer-events/src/types/types.ts b/apps/indexer-events/src/types/types.ts index 96dc66e9..40a031a5 100644 --- a/apps/indexer-events/src/types/types.ts +++ b/apps/indexer-events/src/types/types.ts @@ -5,6 +5,9 @@ import { EventCause, Network } from 'nb-types'; export type Config = { cacheExpiry: number; + dbCa: string; + dbCert: string; + dbKey: string; dbUrl: string; delta: number; insertLimit: number; diff --git a/mainnet.env.example b/mainnet.env.example index 23d40bc4..ac1c0642 100644 --- a/mainnet.env.example +++ b/mainnet.env.example @@ -1,9 +1,13 @@ DATABASE_URL= REDIS_URL= -RPC_NODE_URL= +RPC_URL= NETWORK=mainnet SENTRY_DSN= +DATABASE_CA= +DATABASE_CERT= +DATABASE_KEY= + COINMARKETCAP_API_KEY= LIVECOINWATCH_API_KEY= COINGECKO_API_KEY= diff --git a/packages/nb-near/package.json b/packages/nb-near/package.json index 73b60f0c..3d175164 100644 --- a/packages/nb-near/package.json +++ b/packages/nb-near/package.json @@ -11,7 +11,7 @@ "lint": "tsc --noEmit && eslint ./ --fix" }, "dependencies": { - "axios": "1.5.1", + "axios": "1.6.2", "near-api-js": "2.1.4" }, "devDependencies": { diff --git a/testnet.env.example b/testnet.env.example index 23d40bc4..ac1c0642 100644 --- a/testnet.env.example +++ b/testnet.env.example @@ -1,9 +1,13 @@ DATABASE_URL= REDIS_URL= -RPC_NODE_URL= +RPC_URL= NETWORK=mainnet SENTRY_DSN= +DATABASE_CA= +DATABASE_CERT= +DATABASE_KEY= + COINMARKETCAP_API_KEY= LIVECOINWATCH_API_KEY= COINGECKO_API_KEY= diff --git a/yarn.lock b/yarn.lock index c5e50d2e..3dfe0c73 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3738,16 +3738,6 @@ "@sentry/utils" "7.74.1" tslib "^2.4.1 || ^1.9.3" -"@sentry/core@7.10.0": - version "7.10.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.10.0.tgz#ebe1f7158954d3d29ba319bdfe745a06d3a43d08" - integrity sha512-uq6oUXPH+6cjsEL5/j/xSW91mVrJo7knTqax7E5MDiA5j98BPK4budGiBiPO7GEB856QhA7N+pOO0lccii5QYQ== - dependencies: - "@sentry/hub" "7.10.0" - "@sentry/types" "7.10.0" - "@sentry/utils" "7.10.0" - tslib "^1.9.3" - "@sentry/core@7.18.0": version "7.18.0" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.18.0.tgz#9fe4c6f45086c20ad45a6357a33381a500a8b704" @@ -3766,29 +3756,6 @@ "@sentry/utils" "7.74.1" tslib "^2.4.1 || ^1.9.3" -"@sentry/hub@7.10.0": - version "7.10.0" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-7.10.0.tgz#41a32c1e68efaedaebc1dca10b8e22d12937715f" - integrity sha512-9Appy7J87EU7Xu2BDY1cLK79nsuE72geeYmG71lgdttTD3XOMcQBOxET4/2sAI+d/ansurXnURx+DAQ9FOKT+w== - dependencies: - "@sentry/types" "7.10.0" - "@sentry/utils" "7.10.0" - tslib "^1.9.3" - -"@sentry/node@7.10.0": - version "7.10.0" - resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.10.0.tgz#977141ff554c26a43739575d1fee9d3819fba998" - integrity sha512-L/DSEJ7Biy8ovvlCyfu5MpCYG108FIGVbJ1h0NBGr5+uLxTNg2WJWojJoiQNiRcWl4s0dcIXrRdi0HR2Sx+DUw== - dependencies: - "@sentry/core" "7.10.0" - "@sentry/hub" "7.10.0" - "@sentry/types" "7.10.0" - "@sentry/utils" "7.10.0" - cookie "^0.4.1" - https-proxy-agent "^5.0.0" - lru_map "^0.3.3" - tslib "^1.9.3" - "@sentry/node@7.18.0": version "7.18.0" resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.18.0.tgz#0af74872b6cbb54e40d1dd024aa396be8bf95ac4" @@ -3816,16 +3783,6 @@ lru_map "^0.3.3" tslib "^2.4.1 || ^1.9.3" -"@sentry/tracing@7.10.0": - version "7.10.0" - resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.10.0.tgz#084019c6ab841dff1722ed5eb397ee80fb36af51" - integrity sha512-ojuBYS1bL/IGWKt/ItY4HmC8NElJrYtTUvm73VbhylhIO4zcn5ICHmgMFj1lqL9gQ1nCnAlifKiWIjL9qUatTA== - dependencies: - "@sentry/hub" "7.10.0" - "@sentry/types" "7.10.0" - "@sentry/utils" "7.10.0" - tslib "^1.9.3" - "@sentry/tracing@7.18.0": version "7.18.0" resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.18.0.tgz#2a3cbe770cb3483a2611dfadc5a669cbbe9e4c64" @@ -3836,11 +3793,6 @@ "@sentry/utils" "7.18.0" tslib "^1.9.3" -"@sentry/types@7.10.0": - version "7.10.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.10.0.tgz#c91d634768336238ac30ed750fa918326c384cbb" - integrity sha512-1UBwdbS0xXzANzp63g4eNQly/qKIXp0swP5OTKWoADvKBtL4anroLUA/l8ADMtuwFZYtVANc8WRGxM2+YmaXtg== - "@sentry/types@7.18.0": version "7.18.0" resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.18.0.tgz#8b0eacd19cc9bcd1f14ca5dcaf7065ebd3186632" @@ -3851,14 +3803,6 @@ resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.74.1.tgz#b6f9b1bd266254f1f8b55fbcc92fa649ba2100ed" integrity sha512-2jIuPc+YKvXqZETwr2E8VYnsH1zsSUR/wkIvg1uTVeVNyoowJv+YsOtCdeGyL2AwiotUBSPKu7O1Lz0kq5rMOQ== -"@sentry/utils@7.10.0": - version "7.10.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.10.0.tgz#237cfcfa300f65fad45ec703d4acb4f02f22093b" - integrity sha512-/aD2DnfyOhV0Wdbb6VF78vu4fQIZJyuReDpBI7MV/EqcEB6FxUKq2YjinfKZF/exHEPig6Ag/Yt+CRFgvtVFuw== - dependencies: - "@sentry/types" "7.10.0" - tslib "^1.9.3" - "@sentry/utils@7.18.0": version "7.18.0" resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.18.0.tgz#ede662acf8ac9af9a973d58e277d7afa77721b6c" @@ -4510,7 +4454,7 @@ dependencies: "@types/node" "*" -"@types/big.js@~6.1", "@types/big.js@~6.1.6": +"@types/big.js@~6.1.6": version "6.1.6" resolved "https://registry.yarnpkg.com/@types/big.js/-/big.js-6.1.6.tgz#3d417e758483d55345a03a087f7e0c87137ca444" integrity sha512-0r9J+Zz9rYm2hOTwiMAVkm3XFQ4u5uTK37xrQMhc9bysn/sf/okzovWMYYIBMFTn/yrEZ11pusgLEaoarTlQbA== @@ -5801,10 +5745,10 @@ axios@1.5.1: form-data "^4.0.0" proxy-from-env "^1.1.0" -axios@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.0.tgz#f1e5292f26b2fd5c2e66876adc5b06cdbd7d2102" - integrity sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg== +axios@1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2" + integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A== dependencies: follow-redirects "^1.15.0" form-data "^4.0.0" @@ -6323,11 +6267,6 @@ colord@^2.9.1: resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== -colorette@2.0.16: - version "2.0.16" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" - integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== - colorette@2.0.19: version "2.0.19" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" @@ -6378,7 +6317,7 @@ commander@^7.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== -commander@^9.0.0, commander@^9.1.0: +commander@^9.0.0: version "9.5.0" resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== @@ -6611,11 +6550,6 @@ dateformat@^4.6.3: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5" integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== -dayjs@1.11.4: - version "1.11.4" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.4.tgz#3b3c10ca378140d8917e06ebc13a4922af4f433e" - integrity sha512-Zj/lPM5hOvQ1Bf7uAvewDaUcsJoI6JmNqmHhHl3nyumwe0XHwt8sWdOVAPACJzCebL8gQCi+K49w7iKWnGwX9g== - dayjs@1.11.6: version "1.11.6" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.6.tgz#2e79a226314ec3ec904e3ee1dd5a4f5e5b1c7afb" @@ -8746,26 +8680,6 @@ knex-migrate-sql-file@~2.0: caller-path "^4.0.0" execa "^7.1.1" -knex@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/knex/-/knex-2.1.0.tgz#9348aace3a08ff5be26eb1c8e838416ddf1aa216" - integrity sha512-vVsnD6UJdSJy55TvCXfFF9syfwyXNxfE9mvr2hJL/4Obciy2EPGoqjDpgRSlMruHuPWDOeYAG25nyrGvU+jJog== - dependencies: - colorette "2.0.16" - commander "^9.1.0" - debug "4.3.4" - escalade "^3.1.1" - esm "^3.2.25" - get-package-type "^0.1.0" - getopts "2.3.0" - interpret "^2.2.0" - lodash "^4.17.21" - pg-connection-string "2.5.0" - rechoir "^0.8.0" - resolve-from "^5.0.0" - tarn "^3.0.2" - tildify "2.0.0" - knex@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/knex/-/knex-3.0.1.tgz#b12f3173c30d8c7b6d69dc257cc9c84db00ad60e" @@ -10204,11 +10118,6 @@ pg-cloudflare@^1.1.1: resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== -pg-connection-string@2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" - integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== - pg-connection-string@2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.1.tgz#78c23c21a35dd116f48e12e23c0965e8d9e2cbfb" @@ -10229,7 +10138,7 @@ pg-int8@1.0.1: resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== -pg-pool@^3.5.1, pg-pool@^3.5.2, pg-pool@^3.6.1: +pg-pool@^3.5.2, pg-pool@^3.6.1: version "3.6.1" resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.1.tgz#5a902eda79a8d7e3c928b77abf776b3cb7d351f7" integrity sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og== @@ -10272,19 +10181,6 @@ pg@8.11.3: optionalDependencies: pg-cloudflare "^1.1.1" -pg@8.7.3: - version "8.7.3" - resolved "https://registry.yarnpkg.com/pg/-/pg-8.7.3.tgz#8a5bdd664ca4fda4db7997ec634c6e5455b27c44" - integrity sha512-HPmH4GH4H3AOprDJOazoIcpI49XFsHCe8xlrjHkWiapdbHK+HLtbm/GQzXYAZwmPju/kzKhjaSfMACG+8cgJcw== - dependencies: - buffer-writer "2.0.0" - packet-reader "1.0.0" - pg-connection-string "^2.5.0" - pg-pool "^3.5.1" - pg-protocol "^1.5.0" - pg-types "^2.1.0" - pgpass "1.x" - pg@8.8.0: version "8.8.0" resolved "https://registry.yarnpkg.com/pg/-/pg-8.8.0.tgz#a77f41f9d9ede7009abfca54667c775a240da686"