Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring and Improving Price related API Route #290

Merged
merged 16 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 28 additions & 14 deletions src/components/client/StatsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,27 @@ 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>
)
const ShowData = ({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rename this to

const NumericDisplay = ({
  value,
  prefix,
  isFetched,
  isFailedToFetchData
}: NumericDisplayProps) => {
  // ... same implementation
}

Also, we could handle the isFailedToFetchData inside it not outside, i.e

interface NumericDisplayProps {
  value: number | undefined
  prefix?: string
  isFetched?: boolean
  isFailedToFetchData?: boolean
}

const NumericDisplay = ({
  value,
  prefix,
  isFetched,
  isFailedToFetchData
}: ShowDataProps) => {
  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) : '-'
}

value,
prefix,
isFetched,
}: {
value: number | undefined
prefix?: string
isFetched?: boolean
}) => {
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 generateArray = (prop: string) => [
{
Expand Down Expand Up @@ -162,14 +170,20 @@ const StatsPage = () => {
fontSize={{ base: 'sm', md: 'md' }}
fontWeight="semibold"
>
{isFailedToFetchData
? '-'
: showData(item.value, valuePrefix)}
{isFailedToFetchData ? (
'-'
) : (
<ShowData
value={item.value || 0}
prefix={valuePrefix}
isFetched={isFetched}
/>
)}
</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()}>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed this, why radio props?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getCheckboxProps is deprecated

<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
Loading