Skip to content

Commit 1571dcc

Browse files
committed
Update coinrank error handling & fallback
- It was possible to return USD results for non-USD requests when data was expired or if there was an error in the fetched data. - Ensure we always return accurate non-USD prices if possible, falling back to the cached data if necessary.
1 parent a1f7fd7 commit 1571dcc

File tree

1 file changed

+100
-51
lines changed

1 file changed

+100
-51
lines changed

src/exchangeRateRouter.ts

Lines changed: 100 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
addIso,
2929
fromCode,
3030
isIsoCode,
31+
logger,
3132
normalizeDate,
3233
subIso,
3334
toCode,
@@ -329,62 +330,110 @@ const getRedisMarkets = async (
329330
const now = new Date()
330331
const nowTimestamp = now.getTime()
331332

332-
const jsonString = await getAsync(`${REDIS_COINRANK_KEY_PREFIX}_${fiatCode}`)
333-
let redisResult: CoinrankRedis = JSON.parse(jsonString)
334-
335-
if (fiatCode !== defaultFiatCode) {
336-
const lastUpdated =
337-
redisResult != null ? new Date(redisResult.lastUpdate).getTime() : 0
338-
339-
// If no result in redis or it's out of date
340-
if (redisResult == null || nowTimestamp - lastUpdated > EXPIRE_TIME) {
341-
// Attempt to scale prices by foreign exchange rate
333+
try {
334+
// First try to get data for the requested fiat code
335+
const jsonString = await getAsync(
336+
`${REDIS_COINRANK_KEY_PREFIX}_${fiatCode}`
337+
)
342338

343-
// Get exchange rate
344-
const result = await fetch(
345-
`${ratesServerAddress}/v2/exchangeRate?currency_pair=${defaultFiatCode}_${fiatCode}`
346-
)
347-
const resultJson = await result.json()
348-
const { exchangeRate } = asExchangeRateResponse(resultJson)
349-
const rate = Number(exchangeRate)
339+
// Only parse if jsonString is not null/undefined
340+
let redisResult: CoinrankRedis | undefined
341+
if (jsonString !== null && jsonString !== undefined) {
342+
try {
343+
redisResult = JSON.parse(jsonString)
344+
345+
// If we have valid data that hasn't expired, return it
346+
const lastUpdated =
347+
redisResult != null ? new Date(redisResult.lastUpdate).getTime() : 0
348+
if (nowTimestamp - lastUpdated <= EXPIRE_TIME) {
349+
return redisResult
350+
}
351+
// We have cached data but it's expired - we'll try to refresh it below
352+
// but will fall back to this expired data if refresh fails
353+
} catch (e) {
354+
// JSON parsing error, redisResult remains undefined
355+
logger(`Error parsing Redis data for ${fiatCode}: ${e}`)
356+
// Continue to try to get fresh data
357+
}
358+
}
350359

351-
// Get USD rankings
352-
const jsonString = await getAsync(
353-
`${REDIS_COINRANK_KEY_PREFIX}_${defaultFiatCode}`
354-
)
355-
redisResult = JSON.parse(jsonString)
356-
let { markets } = redisResult
357-
358-
// Modify fiat-related fields with the forex rate
359-
markets = markets.map(m => ({
360-
...m,
361-
marketCap: m.marketCap * rate,
362-
price: m.price * rate,
363-
volume24h: m.volume24h * rate,
364-
high24h: m.high24h * rate,
365-
low24h: m.low24h * rate,
366-
priceChange24h: m.priceChange24h * rate,
367-
marketCapChange24h: m.marketCapChange24h * rate,
368-
circulatingSupply: m.circulatingSupply * rate,
369-
totalSupply: m.totalSupply * rate,
370-
maxSupply: m.maxSupply * rate,
371-
allTimeHigh: m.allTimeHigh * rate,
372-
allTimeLow: m.allTimeLow * rate
373-
}))
374-
375-
// Update redis cache
376-
const redisData: CoinrankRedis = {
377-
markets,
378-
lastUpdate: now.toISOString()
360+
// If we need to convert from USD (either no data or expired data)
361+
if (fiatCode !== defaultFiatCode) {
362+
try {
363+
// Get exchange rate
364+
const result = await fetch(
365+
`${ratesServerAddress}/v2/exchangeRate?currency_pair=${defaultFiatCode}_${fiatCode}`
366+
)
367+
if (!result.ok) {
368+
throw new Error(`Exchange rate API returned status ${result.status}`)
369+
}
370+
371+
const resultJson = await result.json()
372+
const { exchangeRate } = asExchangeRateResponse(resultJson)
373+
const rate = Number(exchangeRate)
374+
375+
// Validate the rate
376+
if (rate == null || isNaN(rate) || rate <= 0) {
377+
throw new Error(`Invalid exchange rate: ${exchangeRate}`)
378+
}
379+
380+
// Get USD rankings
381+
const usdJsonString = await getAsync(
382+
`${REDIS_COINRANK_KEY_PREFIX}_${defaultFiatCode}`
383+
)
384+
if (usdJsonString == null) {
385+
throw new Error(`No USD data available in Redis`)
386+
}
387+
388+
const usdRedisResult = JSON.parse(usdJsonString)
389+
let { markets } = usdRedisResult
390+
391+
// Modify fiat-related fields with the forex rate
392+
markets = markets.map(m => ({
393+
...m,
394+
marketCap: m.marketCap * rate,
395+
price: m.price * rate,
396+
volume24h: m.volume24h * rate,
397+
high24h: m.high24h * rate,
398+
low24h: m.low24h * rate,
399+
priceChange24h: m.priceChange24h * rate,
400+
marketCapChange24h: m.marketCapChange24h * rate,
401+
circulatingSupply: m.circulatingSupply * rate,
402+
totalSupply: m.totalSupply * rate,
403+
maxSupply: m.maxSupply * rate,
404+
allTimeHigh: m.allTimeHigh * rate,
405+
allTimeLow: m.allTimeLow * rate
406+
}))
407+
408+
// Update redis cache
409+
const redisData: CoinrankRedis = {
410+
markets,
411+
lastUpdate: now.toISOString()
412+
}
413+
await setAsync(
414+
`${REDIS_COINRANK_KEY_PREFIX}_${fiatCode}`,
415+
JSON.stringify(redisData)
416+
)
417+
418+
return redisData
419+
} catch (e) {
420+
logger(`Error converting USD data to ${fiatCode}: ${e}`)
421+
// If conversion fails but we have cached data (even if expired), return that
422+
if (redisResult != null) {
423+
logger(`Falling back to cached data for ${fiatCode}`)
424+
return redisResult
425+
}
426+
// Only return undefined if we have no cached data at all
427+
return undefined
379428
}
380-
await setAsync(
381-
`${REDIS_COINRANK_KEY_PREFIX}_${fiatCode}`,
382-
JSON.stringify(redisData)
383-
)
429+
} else {
430+
// For USD requests when data is missing or expired
431+
return redisResult
384432
}
433+
} catch (e) {
434+
logger(`Error in getRedisMarkets for ${fiatCode}: ${e}`)
435+
return undefined
385436
}
386-
387-
return redisResult
388437
}
389438

390439
const sendCoinrankList: express.RequestHandler = async (

0 commit comments

Comments
 (0)