From 8dfc23e16d246c2f32ccac2a5e67f7eb482d7ba3 Mon Sep 17 00:00:00 2001 From: Maxime Cyr <2829180+Redm4x@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:59:15 -0400 Subject: [PATCH] Improve error handling in api (#383) --- apps/api/src/caching/helpers.ts | 19 +++++++++++++------ .../src/routes/v1/addresses/deployments.ts | 16 +++++++++++++++- .../src/services/external/githubService.ts | 19 +++++++++++++------ 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/apps/api/src/caching/helpers.ts b/apps/api/src/caching/helpers.ts index 621f997ae..b6e371982 100644 --- a/apps/api/src/caching/helpers.ts +++ b/apps/api/src/caching/helpers.ts @@ -1,8 +1,11 @@ import * as Sentry from "@sentry/node"; import { differenceInSeconds } from "date-fns"; +import { LoggerService } from "@src/core"; import MemoryCacheEngine from "./memoryCacheEngine"; +const logger = new LoggerService({ context: "Caching" }); + export const cacheEngine = new MemoryCacheEngine(); const pendingRequests: { [key: string]: Promise } = {}; @@ -30,20 +33,24 @@ export const Memoize = (options?: MemoizeOptions) => (target: object, propertyNa export async function cacheResponse(seconds: number, key: string, refreshRequest: () => Promise, keepData?: boolean): Promise { const duration = seconds * 1000; const cachedObject = cacheEngine.getFromCache(key) as CachedObject | undefined; - // console.log(`Cache key: ${key}`); + logger.debug(`Request for key: ${key}`); // If first time or expired, must refresh data if not already refreshing const cacheExpired = Math.abs(differenceInSeconds(cachedObject?.date, new Date())) > seconds; if ((!cachedObject || cacheExpired) && !(key in pendingRequests)) { - // console.log(`Making request: ${key}`); + logger.debug(`Object was not in cache or is expired, making new request for key: ${key}`); pendingRequests[key] = refreshRequest() .then(data => { cacheEngine.storeInCache(key, { date: new Date(), data: data }, keepData ? undefined : duration); return data; }) .catch(err => { - console.error(`Error making cache request ${err}`); - Sentry.captureException(err); + if (cachedObject) { + logger.error(`Error making cache request ${err}`); + Sentry.captureException(err); + } else { + throw err; + } }) .finally(() => { delete pendingRequests[key]; @@ -52,10 +59,10 @@ export async function cacheResponse(seconds: number, key: string, refreshRequ // If there is data in cache, return it even if it is expired. Otherwise, wait for the refresh request to finish if (cachedObject) { - // console.log(`Cache hit: ${key}`); + logger.debug(`Returning cached object for key: ${key}`); return cachedObject.data; } else { - // console.log(`Waiting for pending request: ${key}`); + logger.debug(`Waiting for pending request for key: ${key}`); return (await pendingRequests[key]) as T; } } diff --git a/apps/api/src/routes/v1/addresses/deployments.ts b/apps/api/src/routes/v1/addresses/deployments.ts index 559a53c80..628581af2 100644 --- a/apps/api/src/routes/v1/addresses/deployments.ts +++ b/apps/api/src/routes/v1/addresses/deployments.ts @@ -1,6 +1,7 @@ import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; import { getAddressDeployments } from "@src/services/external/apiNodeService"; +import { isValidBech32Address } from "@src/utils/addresses"; import { openApiExampleAddress } from "@src/utils/constants"; const maxLimit = 100; @@ -59,15 +60,28 @@ const route = createRoute({ }) } } + }, + 400: { + description: "Invalid address" } } }); export default new OpenAPIHono().openapi(route, async c => { + if (!isValidBech32Address(c.req.valid("param").address, "akash")) { + return c.text("Invalid address", 400); + } + const skip = parseInt(c.req.valid("param").skip); const limit = Math.min(maxLimit, parseInt(c.req.valid("param").limit)); - // TODO Add param validation + if (isNaN(skip)) { + return c.text("Invalid skip.", 400); + } + + if (isNaN(limit)) { + return c.text("Invalid limit.", 400); + } const deployments = await getAddressDeployments(c.req.valid("param").address, skip, limit, c.req.valid("query").reverseSorting === "true", { status: c.req.valid("query").status diff --git a/apps/api/src/services/external/githubService.ts b/apps/api/src/services/external/githubService.ts index 82f94e557..19728232e 100644 --- a/apps/api/src/services/external/githubService.ts +++ b/apps/api/src/services/external/githubService.ts @@ -1,5 +1,6 @@ import { Octokit } from "@octokit/rest"; import axios from "axios"; +import minutesToSeconds from "date-fns/minutesToSeconds"; import { cacheKeys, cacheResponse } from "@src/caching/helpers"; import { Auditor, ProviderAttributesSchema } from "@src/types/provider"; @@ -22,19 +23,25 @@ export function getOctokit() { export const getProviderAttributesSchema = async (): Promise => { // Fetching provider attributes schema const response = await cacheResponse( - 30, + minutesToSeconds(5), cacheKeys.getProviderAttributesSchema, - async () => await axios.get("https://raw.githubusercontent.com/akash-network/console/main/config/provider-attributes.json") + async () => await axios.get("https://raw.githubusercontent.com/akash-network/console/main/config/provider-attributes.json"), + true ); return response.data; }; export async function getAuditors() { - const response = await cacheResponse(60 * 5, cacheKeys.getAuditors, async () => { - const res = await axios.get("https://raw.githubusercontent.com/akash-network/console/main/config/auditors.json"); - return res.data; - }); + const response = await cacheResponse( + minutesToSeconds(5), + cacheKeys.getAuditors, + async () => { + const res = await axios.get("https://raw.githubusercontent.com/akash-network/console/main/config/auditors.json"); + return res.data; + }, + true + ); return response; }