Skip to content

Commit

Permalink
Merge pull request #290 from EveripediaNetwork/implement-api-caching
Browse files Browse the repository at this point in the history
Refactoring and Improving Price related API Route
  • Loading branch information
Aliiiu authored Jan 14, 2025
2 parents 4407b0b + 6f7383d commit 8ab9781
Show file tree
Hide file tree
Showing 17 changed files with 275 additions and 106 deletions.
48 changes: 34 additions & 14 deletions src/components/client/StatsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,36 @@ type Stat = {
icon?: (props: IconProps) => JSX.Element
}

const showData = (value: Stat['value'], prefix?: string) => {
return value !== undefined ? (
// eslint-disable-next-line radix
(prefix || '') + Humanize.formatNumber(value)
) : (
<Spinner variant="primary" role="status" size="sm">
<span>Loading...</span>
</Spinner>
)
interface NumericDisplayProps {
value: number | undefined
prefix?: string
isFetched?: boolean
isFailedToFetchData?: boolean
}

const NumericDisplay = ({
value,
prefix,
isFetched,
isFailedToFetchData,
}: NumericDisplayProps) => {
if (isFailedToFetchData) {
return '-'
}

if (value === undefined && !isFetched) {
return (
<Spinner variant="primary" role="status" size="sm">
<span>Loading...</span>
</Spinner>
)
}

return value ? (prefix || '') + Humanize.formatNumber(value) : '-'
}

const StatsPage = () => {
const { data } = useStatsData()
const { data, isFetched } = useStatsData()

const t = useTranslations('stats')

Expand Down Expand Up @@ -166,14 +183,17 @@ const StatsPage = () => {
fontSize={{ base: 'sm', md: 'md' }}
fontWeight="semibold"
>
{isFailedToFetchData
? '-'
: showData(item.value, valuePrefix)}
<NumericDisplay
value={item.value || 0}
prefix={valuePrefix}
isFetched={isFetched}
isFailedToFetchData={isFailedToFetchData}
/>
</Text>
</Flex>
)
})}
</Stack>
</Stack>{' '}
</Flex>
)
})}
Expand Down
5 changes: 2 additions & 3 deletions src/components/dashboard/GraphPeriodButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import React from 'react'

const GraphPeriodButton = (props: { label: string } & UseRadioProps) => {
const { label, ...radioProps } = props
const { state, getInputProps, getCheckboxProps, htmlProps, getLabelProps } =
const { state, getInputProps, getRadioProps, htmlProps, getLabelProps } =
useRadio(radioProps)
return (
<chakra.label
{...htmlProps}
cursor={state.isDisabled ? 'not-allowed' : 'pointer'}
>
<input {...getInputProps({})} hidden />
<Box {...getCheckboxProps()}>
<Box {...getRadioProps()}>
<Box
bg={state.isChecked ? 'brandText' : 'transparent'}
border="solid 1px"
Expand All @@ -38,5 +38,4 @@ const GraphPeriodButton = (props: { label: string } & UseRadioProps) => {
</chakra.label>
)
}

export default GraphPeriodButton
4 changes: 3 additions & 1 deletion src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ const config = {
},
posthogApikey: process.env.NEXT_PUBLIC_POSTHOG_KEY || '',
merlinApiKey: process.env.MERLIN_API_KEY || '',
CACHE_DURATION_SECONDS: 43200,
CACHE_DURATION_SECONDS: 24 * 60 * 60,
CACHE_STALE_WHILE_REVALIDATE_SECONDS: 2 * 24 * 60 * 60,
twitterRapidApiKey: process.env.TWITTER_RAPID_API_KEY || '',
}

export default config
10 changes: 9 additions & 1 deletion src/pages/api/cmc-token-details.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import config from '@/config'
import type { NextApiRequest, NextApiResponse } from 'next'
import axios from 'axios'
import { setCacheHeaders } from '@/utils/cache'

const CACHE_DURATION_SECONDS = 300

export type ResponseData = {
status: boolean
Expand All @@ -22,7 +25,12 @@ export default async function handler(
},
},
)
res.setHeader('Cache-Control', 's-maxage=120')
setCacheHeaders(
res,
`public, s-maxage=${CACHE_DURATION_SECONDS}, stale-while-revalidate=${
2 * CACHE_DURATION_SECONDS
}`,
)
return res.status(200).json({
response: response.data,
status: true,
Expand Down
54 changes: 36 additions & 18 deletions src/pages/api/fetch-tokens.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,47 @@
import config from '@/config'
import type { NextApiRequest, NextApiResponse } from 'next'
import { ResponseData } from '@/types/TreasuryTokenType'
import { setCacheHeaders } from '@/utils/cache'

export default async function handler(
req: NextApiRequest,
res: NextApiResponse<ResponseData>,
) {
const { walletAddress } = req.query
if (!walletAddress) {
res.setHeader('Cache-Control', `s-maxage=${config.CACHE_DURATION_SECONDS}`)
if (req.method !== 'GET') {
return res
.status(400)
.json({ status: false, message: 'Wallet address are needed' })
.status(405)
.json({ status: false, message: 'Method not allowed' })
}
try {
const { walletAddress } = req.query
if (!walletAddress) {
return res
.status(400)
.json({ status: false, message: 'Wallet address are needed' })
}
const url = `https://pro-openapi.debank.com/v1/user/all_token_list?id=${walletAddress}&is_all=true`
const result = await fetch(url, {
headers: {
Accept: 'application/json',
Accesskey: `${config.debankApiKey}`,
},
})

setCacheHeaders(res)
return res.status(200).json({
response: await result.json(),
status: true,
message: 'token details successfully fetched',
})
} catch (error) {
console.error('Fetch token error:', error)

return res.status(500).json({
status: false,
message:
error instanceof Error
? error.message
: 'Failed to fetch token details',
})
}
const url = `https://pro-openapi.debank.com/v1/user/all_token_list?id=${walletAddress}&is_all=true`
const result = await fetch(url, {
headers: {
Accept: 'application/json',
Accesskey: `${config.debankApiKey}`,
},
})
res.setHeader('Cache-Control', `s-maxage=${config.CACHE_DURATION_SECONDS}`)
return res.status(200).json({
response: await result.json(),
status: true,
message: 'token details successfully fetched',
})
}
49 changes: 33 additions & 16 deletions src/pages/api/gas-price.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import config from '@/config'
import { setCacheHeaders } from '@/utils/cache'
import type { NextApiRequest, NextApiResponse } from 'next'

const NORMALIZE_VALUE = 10e8
Expand All @@ -9,22 +10,38 @@ type ResponseData = {
}

export default async function handler(
_req: NextApiRequest,
req: NextApiRequest,
res: NextApiResponse<ResponseData>,
) {
const url = 'https://pro-openapi.debank.com/v1/wallet/gas_market?chain_id=eth'
const result = await fetch(url, {
headers: {
Accept: 'application/json',
Accesskey: `${config.debankApiKey}`,
},
})
const response = await result.json()
const gasPrice = response[1]?.price / NORMALIZE_VALUE
res.setHeader('Cache-Control', `s-maxage=${config.CACHE_DURATION_SECONDS}`)
return res.status(200).json({
response: gasPrice,
status: true,
message: 'gas price successfully fetched',
})
if (req.method !== 'GET') {
return res
.status(405)
.json({ status: false, message: 'Method not allowed' })
}
try {
const url =
'https://pro-openapi.debank.com/v1/wallet/gas_market?chain_id=eth'
const result = await fetch(url, {
headers: {
Accept: 'application/json',
Accesskey: `${config.debankApiKey}`,
},
})
const response = await result.json()
const gasPrice = response[1]?.price / NORMALIZE_VALUE
setCacheHeaders(res)
return res.status(200).json({
response: gasPrice,
status: true,
message: 'gas price successfully fetched',
})
} catch (error) {
console.error('gas price fetch error:', error)

return res.status(500).json({
status: false,
message:
error instanceof Error ? error.message : 'Failed to fetch gas price',
})
}
}
10 changes: 10 additions & 0 deletions src/pages/api/iq-historical-data.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { setCacheHeaders } from '@/utils/cache'
import { NextApiRequest, NextApiResponse } from 'next'

const CACHE_DURATION_SECONDS = 300

export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
Expand All @@ -23,6 +26,13 @@ export default async function handler(
}

const data = await response.json()

setCacheHeaders(
res,
`public, s-maxage=${CACHE_DURATION_SECONDS}, stale-while-revalidate=${
2 * CACHE_DURATION_SECONDS
}`,
)
res.status(200).json(data)
} catch (error) {
console.error('Error fetching data from CoinGecko:', error)
Expand Down
10 changes: 9 additions & 1 deletion src/pages/api/iq-price.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { setCacheHeaders } from '@/utils/cache'
import type { NextApiRequest, NextApiResponse } from 'next'

const CACHE_DURATION_SECONDS = 300

type ResponseData = {
response?: number
status: boolean
Expand All @@ -16,7 +19,12 @@ export default async function handler(
const result = await fetch(url)
const response = await result.json()
const price = response?.everipedia?.usd
res.setHeader('Cache-Control', 's-maxage=60')
setCacheHeaders(
res,
`public, s-maxage=${CACHE_DURATION_SECONDS}, stale-while-revalidate=${
2 * CACHE_DURATION_SECONDS
}`,
)
return res.status(200).json({
response: price,
status: true,
Expand Down
53 changes: 35 additions & 18 deletions src/pages/api/protocols.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,47 @@
import config from '@/config'
import { ResponseData } from '@/types/TreasuryTokenType'
import { setCacheHeaders } from '@/utils/cache'
import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
req: NextApiRequest,
res: NextApiResponse<ResponseData>,
) {
const { protocolId, id } = req.query
if (!protocolId || !id) {
res.setHeader('Cache-Control', `s-maxage=${config.CACHE_DURATION_SECONDS}`)
return res.status(400).json({
if (req.method !== 'GET') {
return res
.status(405)
.json({ status: false, message: 'Method not allowed' })
}
try {
const { protocolId, id } = req.query
if (!protocolId || !id) {
return res.status(400).json({
status: false,
message: 'protocol id and wallet address are needed',
})
}
const url = `https://pro-openapi.debank.com/v1/user/protocol?protocol_id=${protocolId}&id=${id}&is_all=true`
const result = await fetch(url, {
headers: {
Accept: 'application/json',
Accesskey: `${config.debankApiKey}`,
},
})
setCacheHeaders(res)
return res.status(200).json({
response: await result.json(),
status: true,
message: 'protocol details successfully fetched',
})
} catch (error) {
console.error('Fetch protocol details error:', error)

return res.status(500).json({
status: false,
message: 'protocol id and wallet address are needed',
message:
error instanceof Error
? error.message
: 'Failed to fetch protocol details',
})
}
const url = `https://pro-openapi.debank.com/v1/user/protocol?protocol_id=${protocolId}&id=${id}&is_all=true`
const result = await fetch(url, {
headers: {
Accept: 'application/json',
Accesskey: `${config.debankApiKey}`,
},
})
res.setHeader('Cache-Control', `s-maxage=${config.CACHE_DURATION_SECONDS}`)
return res.status(200).json({
response: await result.json(),
status: true,
message: 'protocol details successfully fetched',
})
}
11 changes: 2 additions & 9 deletions src/pages/api/quickswap-details.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import config from '@/config'
import { setCacheHeaders } from '@/utils/cache'
import type { NextApiRequest, NextApiResponse } from 'next'

const ONE_DAY_IN_SECONDS = 24 * 60 * 60
const TWO_DAYS_IN_SECONDS = 2 * ONE_DAY_IN_SECONDS

type ResponseData = {
status: boolean
message?: string
Expand Down Expand Up @@ -47,12 +45,7 @@ export default async function handler(
throw new Error('Invalid response format: missing valueUsd')
}

const cacheHeader = `public, s-maxage=${ONE_DAY_IN_SECONDS}, stale-while-revalidate=${TWO_DAYS_IN_SECONDS}`

res.setHeader('Cache-Control', cacheHeader)
res.setHeader('CDN-Cache-Control', cacheHeader)
res.setHeader('Vercel-CDN-Cache-Control', cacheHeader)

setCacheHeaders(res)
return res.status(200).json({
status: true,
data: data.valueUsd,
Expand Down
Loading

0 comments on commit 8ab9781

Please sign in to comment.