Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 7 additions & 1 deletion components/Claim/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,17 @@ const Claim = () => {
fees: migrationParams.fees.toString(),
}),
});

if (!res.ok) {
const error = await res.json().catch(() => null);
throw new Error(error?.error || "Failed to generate proof");
}

const proof = await res.json();

setProof(proof);
} catch (e) {
console.log(e);
console.error(e);
throw new Error((e as Error)?.message);
}
}}
Expand Down
64 changes: 64 additions & 0 deletions lib/api/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { NextApiResponse } from "next";

import { ApiError, ErrorCode } from "./types/api-error";

export const apiError = (
res: NextApiResponse,
status: number,
code: ErrorCode,
error: string,
details?: string
) => {
console.error(`[API Error] ${code}: ${error}`, details ?? "");
const response = { error, code, details } as ApiError;
res.status(status).json(response);
return response;
};

export const badRequest = (
res: NextApiResponse,
message: string,
details?: string
) => apiError(res, 400, "VALIDATION_ERROR", message, details);

export const notFound = (
res: NextApiResponse,
message: string,
details?: string
) => apiError(res, 404, "NOT_FOUND", message, details);

export const internalError = (res: NextApiResponse, err?: unknown) =>
apiError(
res,
500,
"INTERNAL_ERROR",
"Internal server error",
err instanceof Error ? err.message : undefined
);

export const externalApiError = (
res: NextApiResponse,
service: string,
details?: string
) =>
apiError(
res,
502,
"EXTERNAL_API_ERROR",
`Failed to fetch from ${service}`,
details
);

export const methodNotAllowed = (
res: NextApiResponse,
method: string,
allowed: string[]
) => {
res.setHeader("Allow", allowed);
return apiError(
res,
405,
"METHOD_NOT_ALLOWED",
`Method ${method} Not Allowed`
);
};
12 changes: 12 additions & 0 deletions lib/api/types/api-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export interface ApiError {
error: string;
code: ErrorCode;
details?: string;
}

export type ErrorCode =
| "INTERNAL_ERROR"
| "VALIDATION_ERROR"
| "NOT_FOUND"
| "EXTERNAL_API_ERROR"
| "METHOD_NOT_ALLOWED";
12 changes: 11 additions & 1 deletion lib/axios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,14 @@ export const axiosClient = defaultAxios.create({
});

export const fetcher = <T>(url: string) =>
axiosClient.get<T>(url).then((res) => res.data);
axiosClient
.get<T>(url)
.then((res) => res.data)
.catch((err) => {
const apiError = err.response?.data;
if (apiError?.code) {
const errorMessage = apiError.error ?? "An unknown error occurred";
throw new Error(`${apiError.code}: ${errorMessage}`);
}
throw err;
});
9 changes: 4 additions & 5 deletions pages/api/account-balance/[address].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
getBondingManagerAddress,
getLivepeerTokenAddress,
} from "@lib/api/contracts";
import { badRequest, internalError, methodNotAllowed } from "@lib/api/errors";
import { AccountBalance } from "@lib/api/types/get-account-balance";
import { l2PublicClient } from "@lib/chains";
import { NextApiRequest, NextApiResponse } from "next";
Expand Down Expand Up @@ -47,15 +48,13 @@ const handler = async (

return res.status(200).json(accountBalance);
} else {
return res.status(500).end("Invalid ID");
return badRequest(res, "Invalid address format");
}
}

res.setHeader("Allow", ["GET"]);
return res.status(405).end(`Method ${method} Not Allowed`);
return methodNotAllowed(res, method ?? "unknown", ["GET"]);
} catch (err) {
console.error(err);
return res.status(500).json(null);
return internalError(res, err);
}
};

Expand Down
57 changes: 38 additions & 19 deletions pages/api/changefeed.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { getCacheControlHeader } from "@lib/api";
import {
externalApiError,
internalError,
methodNotAllowed,
} from "@lib/api/errors";
import { fetchWithRetry } from "@lib/fetchWithRetry";
import type { NextApiRequest, NextApiResponse } from "next";

Expand All @@ -25,27 +30,41 @@ const query = `
`;

const changefeed = async (_req: NextApiRequest, res: NextApiResponse) => {
const response = await fetchWithRetry(
"https://changefeed.app/graphql",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.CHANGEFEED_ACCESS_TOKEN!}`,
},
body: JSON.stringify({ query }),
},
{
retryOnMethods: ["POST"],
}
);
try {
const method = _req.method;

if (method === "GET") {
const response = await fetchWithRetry(
"https://changefeed.app/graphql",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.CHANGEFEED_ACCESS_TOKEN!}`,
},
body: JSON.stringify({ query }),
},
{
retryOnMethods: ["POST"],
}
);

if (!response.ok) {
return externalApiError(res, "changefeed.app");
}

res.setHeader("Cache-Control", getCacheControlHeader("hour"));

res.setHeader("Cache-Control", getCacheControlHeader("hour"));
const {
data: { projectBySlugs },
} = await response.json();
return res.status(200).json(projectBySlugs);
}

const {
data: { projectBySlugs },
} = await response.json();
res.json(projectBySlugs);
return methodNotAllowed(res, method ?? "unknown", ["GET"]);
} catch (err) {
return internalError(res, err);
}
};

export default changefeed;
7 changes: 3 additions & 4 deletions pages/api/contracts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
getLivepeerGovernorAddress,
getTreasuryAddress,
} from "@lib/api/contracts";
import { internalError, methodNotAllowed } from "@lib/api/errors";
import { ContractInfo } from "@lib/api/types/get-contract-info";
import { CHAIN_INFO, DEFAULT_CHAIN_ID } from "@lib/chains";
import { NextApiRequest, NextApiResponse } from "next";
Expand Down Expand Up @@ -126,11 +127,9 @@ const handler = async (
return res.status(200).json(contractsInfo);
}

res.setHeader("Allow", ["GET"]);
return res.status(405).end(`Method ${method} Not Allowed`);
return methodNotAllowed(res, method ?? "unknown", ["GET"]);
} catch (err) {
console.error(err);
return res.status(500).json(null);
return internalError(res, err);
}
};

Expand Down
11 changes: 5 additions & 6 deletions pages/api/current-round.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getCacheControlHeader, getCurrentRound } from "@lib/api";
import { internalError, methodNotAllowed } from "@lib/api/errors";
import { CurrentRoundInfo } from "@lib/api/types/get-current-round";
import { l1PublicClient } from "@lib/chains";
import { NextApiRequest, NextApiResponse } from "next";
Expand All @@ -19,11 +20,11 @@ const handler = async (
const currentRound = protocol?.currentRound;

if (!currentRound) {
return res.status(500).end("No current round found");
throw new Error("No current round found");
}

if (!_meta?.block) {
return res.status(500).end("No block number found");
throw new Error("No block number found");
}

const { id, startBlock, initialized } = currentRound;
Expand All @@ -42,11 +43,9 @@ const handler = async (
return res.status(200).json(roundInfo);
}

res.setHeader("Allow", ["GET"]);
return res.status(405).end(`Method ${method} Not Allowed`);
return methodNotAllowed(res, method ?? "unknown", ["GET"]);
} catch (err) {
console.error(err);
return res.status(500).json(null);
return internalError(res, err);
}
};

Expand Down
9 changes: 4 additions & 5 deletions pages/api/ens-data/[address].tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getCacheControlHeader } from "@lib/api";
import { getEnsForAddress } from "@lib/api/ens";
import { badRequest, internalError, methodNotAllowed } from "@lib/api/errors";
import { EnsIdentity } from "@lib/api/types/get-ens";
import { NextApiRequest, NextApiResponse } from "next";
import { Address, isAddress } from "viem";
Expand Down Expand Up @@ -30,15 +31,13 @@ const handler = async (

return res.status(200).json(ens);
} else {
return res.status(500).end("Invalid ID");
return badRequest(res, "Invalid address format");
}
}

res.setHeader("Allow", ["GET"]);
return res.status(405).end(`Method ${method} Not Allowed`);
return methodNotAllowed(res, method ?? "unknown", ["GET"]);
} catch (err) {
console.error(err);
return res.status(500).json(null);
return internalError(res, err);
}
};

Expand Down
16 changes: 10 additions & 6 deletions pages/api/ens-data/image/[name].tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { getCacheControlHeader } from "@lib/api";
import {
badRequest,
internalError,
methodNotAllowed,
notFound,
} from "@lib/api/errors";
import { l1PublicClient } from "@lib/chains";
import { parseArweaveTxId, parseCid } from "livepeer/utils";
import { NextApiRequest, NextApiResponse } from "next";
Expand Down Expand Up @@ -47,18 +53,16 @@ const handler = async (
return res.end(Buffer.from(arrayBuffer));
} catch (e) {
console.error(e);
return res.status(404).end("Invalid name");
return notFound(res, "ENS avatar not found");
}
} else {
return res.status(500).end("Invalid name");
return badRequest(res, "Invalid ENS name");
}
}

res.setHeader("Allow", ["GET"]);
return res.status(405).end(`Method ${method} Not Allowed`);
return methodNotAllowed(res, method ?? "unknown", ["GET"]);
} catch (err) {
console.error(err);
return res.status(500).json(null);
return internalError(res, err);
}
};

Expand Down
7 changes: 3 additions & 4 deletions pages/api/ens-data/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getCacheControlHeader } from "@lib/api";
import { getEnsForAddress } from "@lib/api/ens";
import { internalError, methodNotAllowed } from "@lib/api/errors";
import { EnsIdentity } from "@lib/api/types/get-ens";
import { CHAIN_INFO, DEFAULT_CHAIN_ID } from "@lib/chains";
import { fetchWithRetry } from "@lib/fetchWithRetry";
Expand Down Expand Up @@ -67,11 +68,9 @@ const handler = async (
return res.status(200).json(ensAddresses);
}

res.setHeader("Allow", ["GET"]);
return res.status(405).end(`Method ${method} Not Allowed`);
return methodNotAllowed(res, method ?? "unknown", ["GET"]);
} catch (err) {
console.error(err);
return res.status(500).json(null);
return internalError(res, err);
}
};

Expand Down
Loading