From ed2923ab1bc101de4aa94e2e56c5abb72eb7d7c2 Mon Sep 17 00:00:00 2001 From: jmzwar Date: Thu, 27 Jun 2024 09:10:43 +0700 Subject: [PATCH] return distinct collateral types with no limit update v3 route schema and table names add routes for multi-collateral feat: added votes parsing ref: using alpine . Delete redis.env done add mainnet typo feat: added votes parsing Delete api.env Delete redis.env ref: updated git ignore add mainnet typo ref: reset docker dev file feat: added example files ref: renaming router return distinct collateral types with no limit update v3 route schema and table names update v3 route schema and table names add routes for multi-collateral feat: added votes parsing ref: using alpine . Delete redis.env done add mainnet typo feat: added votes parsing Delete api.env Delete redis.env ref: updated git ignore add mainnet typo ref: reset docker dev file feat: added example files ref: renaming router --- api.env => api.envexample | 0 redis.env | 1 - redis.envexample | 1 + src/routes/v3/mainnet/sc-pool-apy-all.js | 62 ++++---- src/routes/v3/snax-testnet/votes.js | 175 +++++++++++++++++++++++ src/server.js | 3 + 6 files changed, 210 insertions(+), 32 deletions(-) rename api.env => api.envexample (100%) delete mode 100644 redis.env create mode 100644 redis.envexample create mode 100644 src/routes/v3/snax-testnet/votes.js diff --git a/api.env b/api.envexample similarity index 100% rename from api.env rename to api.envexample diff --git a/redis.env b/redis.env deleted file mode 100644 index b3fba51..0000000 --- a/redis.env +++ /dev/null @@ -1 +0,0 @@ -REDIS_HOST_PASSWORD=password1 diff --git a/redis.envexample b/redis.envexample new file mode 100644 index 0000000..952b0c5 --- /dev/null +++ b/redis.envexample @@ -0,0 +1 @@ +REDIS_HOST_PASSWORD= diff --git a/src/routes/v3/mainnet/sc-pool-apy-all.js b/src/routes/v3/mainnet/sc-pool-apy-all.js index eaeee17..645b6d6 100644 --- a/src/routes/v3/mainnet/sc-pool-apy-all.js +++ b/src/routes/v3/mainnet/sc-pool-apy-all.js @@ -167,39 +167,39 @@ async function fetchDataFromPostgres() { log.debug('[v3EthSCPoolAPYAll] Fetching data from postgres..'); const queryResult = await postgresClient.query( `WITH latest_records AS ( - SELECT DISTINCT ON (collateral_type) - ts, - pool_id, - collateral_type, - collateral_value, - debt, - hourly_issuance, - hourly_pnl, - cumulative_pnl, - cumulative_issuance, - rewards_usd, - hourly_pnl_pct, - hourly_rewards_pct, - apr_24h, - apy_24h, - apr_7d, - apy_7d, - apr_28d, - apy_28d, - apr_24h_pnl, - apy_24h_pnl, - apr_7d_pnl, - apy_7d_pnl, - apr_28d_pnl, - apy_28d_pnl, - apr_24h_rewards, - apy_24h_rewards, - apr_7d_rewards, - apy_7d_rewards, - apr_28d_rewards, + SELECT DISTINCT ON (collateral_type) + ts, + pool_id, + collateral_type, + collateral_value, + debt, + hourly_issuance, + hourly_pnl, + cumulative_pnl, + cumulative_issuance, + rewards_usd, + hourly_pnl_pct, + hourly_rewards_pct, + apr_24h, + apy_24h, + apr_7d, + apy_7d, + apr_28d, + apy_28d, + apr_24h_pnl, + apy_24h_pnl, + apr_7d_pnl, + apy_7d_pnl, + apr_28d_pnl, + apy_28d_pnl, + apr_24h_rewards, + apy_24h_rewards, + apr_7d_rewards, + apy_7d_rewards, + apr_28d_rewards, apy_28d_rewards FROM prod_eth_mainnet.fct_core_apr_eth_mainnet - WHERE pool_id = 1 + WHERE pool_id = 1 ORDER BY collateral_type, ts DESC ) SELECT * FROM latest_records diff --git a/src/routes/v3/snax-testnet/votes.js b/src/routes/v3/snax-testnet/votes.js new file mode 100644 index 0000000..398287d --- /dev/null +++ b/src/routes/v3/snax-testnet/votes.js @@ -0,0 +1,175 @@ +const express = require('express'); +const router = express.Router(); +const { log, postgresClient, getCache, setCache } = require('../../../utils'); + +const cacheKey = 'snax-votes'; + +fetchDataFromPostgres(); +const cacheTime = + ((process.env.CACHE_TIME = + typeof process.env.CACHE_TIME === 'string' + ? parseInt(process.env.CACHE_TIME) + : process.env.CACHE_TIME) - + 30) * + 1000; +setInterval(fetchDataFromPostgres, cacheTime < 30000 ? 30000 : cacheTime); + +/** + * @openapi + * /v3/snax-testnet/votes: + * get: + * tags: + * - v3 + * description: Returns SNX buyback details on Base. + * responses: + * 200: + * description: Successful response. + * content: + * application/json: + * schema: + * type: array + * items: + * type: object + * properties: + * eventName: + * type: string + * example: "VoteRecorded" + * chainId: + * type: number + * example: "13001" + * epochId: + * type: number + * example: "1" + * voter: + * type: string + * example: "0xc3Cf311e04c1f8C74eCF6a795Ae760dc6312F345" + * blockTimestamp: + * type: number + * example: "1231231231" + * id: + * type: string + * example: "093890428092342034-dsfb3-000000" + * transactionHash: + * type: string + * example: "0xbb9982fbae46a551f5e503a059251b51fdb2bc5b06c13ca15db8a61785b8d095" + * votingPower: + * type: string + * example: "10" + * blockNumber: + * type: number + * example: "580095" + * candidates: + * type: array + * items: + * type: string + * example: ["0x98591879709e2e3106941C818CBD94eD29475d1f"] + * contract: + * type: string + * example: "0xbc85f11300a8ef619592fd678418ec4ef26fbdfd" + * 401: + * description: Unauthorized. + * 403: + * description: You have been banned by WAF. + * 429: + * description: Too many requests, you're being rate-limited. + * 5XX: + * description: Service unavailable. + * default: + * description: Unexpected error. + */ + +router.get('/', async (req, res, next) => { + try { + log.debug('Checking cache..'); + const cachedResponse = await getCache(cacheKey); + if (cachedResponse) { + log.debug('Cache found'); + res.json(cachedResponse); + } else { + log.debug('Cache not found, executing..'); + const responseData = await fetchDataFromPostgres(); + res.json(responseData); + } + } catch (error) { + log.error(`[v3SnaxVote] Error: ${error.message}`); + next(error); + } +}); + +module.exports = router; + +async function fetchDataFromPostgres() { + log.debug('[v3SnaxVote] Fetching data from postgres..'); + const queryResultVotesCasted = await postgresClient.query( + `select * + from prod_raw_snax_testnet.gov_vote_recorded_snax_testnet;`, + ); + + const queryResultVotesWithdrawn = await postgresClient.query( + `select * + from prod_raw_snax_testnet.gov_vote_withdrawn_snax_testnet;`, + ); + + const votesCasted = queryResultVotesCasted.rows.map(parseAndRenameKeys); + const votesWithdrawn = queryResultVotesWithdrawn.rows.map(parseAndRenameKeys); + + const allVotes = votesCasted + .concat(votesWithdrawn) + .sort((a, b) => a.blockNumber - b.blockNumber) + .reduce((cur, next) => { + if (cur[next.epochId]) { + if (next.eventName === 'VoteWithdrawn') { + const previousVoteIndex = cur[next.epochId].findIndex((vote) => { + return vote.contract === next.contract && vote.voter === next.voter; + }); + if (previousVoteIndex !== -1) { + cur[next.epochId].splice(previousVoteIndex, 1); + } else { + console.error( + 'Could not find previous vote', + cur[next.epochId], + next, + ); + } + } else { + const previousVoteIndex = cur[next.epochId].findIndex( + (vote) => + vote.contract === next.contract && vote.voter === next.voter, + ); + if (previousVoteIndex !== -1) { + cur[next.epochId].splice(previousVoteIndex, 1); + } + cur[next.epochId].push(next); + } + } else { + cur[next.epochId] = [next]; + } + return cur; + }, {}); + + const responseData = allVotes; + log.debug('[v3SnaxVote] Setting cache..'); + await setCache(cacheKey, responseData, 60); + return responseData; +} + +function toCamelCase(str) { + return str.replace(/([-_][a-z])/gi, (group) => + group.toUpperCase().replace('-', '').replace('_', ''), + ); +} + +function parseAndRenameKeys(obj) { + const newObj = {}; + for (const key in obj) { + const camelCaseKey = toCamelCase(key); + if (key === 'block_timestamp') { + newObj[camelCaseKey] = Date.parse(obj[key]); + } else if (key === 'epoch_id' || key === 'chain_id') { + newObj[camelCaseKey] = Number(obj[key]); + } else { + newObj[camelCaseKey] = obj[key]; + } + } + return newObj; +} diff --git a/src/server.js b/src/server.js index 32f531d..c9d3010 100644 --- a/src/server.js +++ b/src/server.js @@ -126,6 +126,9 @@ redisClient.on('ready', () => { const v3MainnetScPoolAPYAllRouter = require('./routes/v3/mainnet/sc-pool-apy-all.js'); app.use('/v3/mainnet/sc-pool-apy-all', v3MainnetScPoolAPYAllRouter); + const v3SnaxTestnetVotesRouter = require('./routes/v3/snax-testnet/votes.js'); + app.use('/v3/snax-testnet/votes', v3SnaxTestnetVotesRouter); + log.debug('[Express] Starting server..'); const port = typeof process.env.API_PORT === 'string'