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
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { DeveloperToolsResponse } from "@/lib/types"

import { fetchRetry } from "@/data-layer/fetchers/fetchRetry"

export async function fetchBuidlGuidl(): Promise<DeveloperToolsResponse[]> {
const url =
"https://raw.githubusercontent.com/BuidlGuidl/Developer-Tooling/refs/heads/main/output/results.json"

console.log("Starting BuidlGuidl developer tooling data fetch")

const response = await fetch(url)
const response = await fetchRetry(url)

if (!response.ok) {
const status = response.status
Expand Down
12 changes: 4 additions & 8 deletions src/data-layer/fetchers/developer-tools/fetchGitHub.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { DeveloperToolsResponse } from "@/lib/types"

import { retry, sleep } from "@/lib/utils/fetch"
import { fetchRetry, sleep } from "@/data-layer/fetchers/fetchRetry"

import type { DeveloperTool } from "./utils"

Expand Down Expand Up @@ -66,7 +66,7 @@ async function fetchReposBatch(

const query = buildGraphQLQuery(repos)

const response = await fetch("https://api.github.com/graphql", {
const response = await fetchRetry("https://api.github.com/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.GITHUB_TOKEN_READ_ONLY}`,
Expand Down Expand Up @@ -129,17 +129,13 @@ export async function fetchGitHub(
const batch = allRepos.slice(i, i + BATCH_SIZE)

try {
// Retry with exponential backoff (3 attempts: 0ms, 1s, 2s)
const batchResults = await retry(() => fetchReposBatch(batch))
const batchResults = await fetchReposBatch(batch)

for (const [href, data] of batchResults) {
repoDataMap.set(href, data)
}
} catch (error) {
console.error(
`Failed to fetch batch ${i / BATCH_SIZE + 1} after retries:`,
error
)
console.error(`Failed to fetch batch ${i / BATCH_SIZE + 1}:`, error)
// Continue with next batch instead of failing entirely
}

Expand Down
45 changes: 21 additions & 24 deletions src/data-layer/fetchers/developer-tools/fetchNpmJs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { retry, sleep } from "@/lib/utils/fetch"
import { fetchRetry, sleep } from "@/data-layer/fetchers/fetchRetry"

import type { DeveloperTool } from "./utils"

Expand Down Expand Up @@ -37,7 +37,7 @@ async function fetchSinglePackageDownloads(
packageName: string
): Promise<number | null> {
try {
const response = await fetch(
const response = await fetchRetry(
`https://api.npmjs.org/downloads/point/last-week/${encodeURIComponent(packageName)}`
)

Expand Down Expand Up @@ -94,35 +94,32 @@ async function fetchBulkDownloads(
const packageList = batch.join(",")

try {
// Retry with exponential backoff (3 attempts: 0ms, 1s, 2s)
await retry(async () => {
const response = await fetch(
`https://api.npmjs.org/downloads/point/last-week/${packageList}`
const response = await fetchRetry(
`https://api.npmjs.org/downloads/point/last-week/${packageList}`
)

if (!response.ok) {
throw new Error(
`npm downloads API returned ${response.status} for unscoped batch`
)
}

if (!response.ok) {
throw new Error(
`npm downloads API returned ${response.status} for unscoped batch`
)
}
const data = await response.json()

const data = await response.json()

// Handle single-package response: { downloads: number, package: string }
// vs multi-package response: { "pkg1": { downloads: n }, "pkg2": { downloads: m } }
if ("downloads" in data && "package" in data) {
results.set(data.package, data.downloads)
} else {
for (const [pkg, info] of Object.entries(data)) {
if (info && typeof info === "object" && "downloads" in info) {
results.set(pkg, (info as { downloads: number }).downloads)
}
// Handle single-package response: { downloads: number, package: string }
// vs multi-package response: { "pkg1": { downloads: n }, "pkg2": { downloads: m } }
if ("downloads" in data && "package" in data) {
results.set(data.package, data.downloads)
} else {
for (const [pkg, info] of Object.entries(data)) {
if (info && typeof info === "object" && "downloads" in info) {
results.set(pkg, (info as { downloads: number }).downloads)
}
}
})
}
} catch (err) {
console.error(
`Failed to fetch bulk npm downloads for batch ${i / BATCH_SIZE + 1} after retries:`,
`Failed to fetch bulk npm downloads for batch ${i / BATCH_SIZE + 1}:`,
err
)
// Continue with next batch instead of failing entirely
Expand Down
4 changes: 3 additions & 1 deletion src/data-layer/fetchers/fetchAccountHolders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { MetricReturnData } from "@/lib/types"

import { DUNE_API_URL } from "@/lib/constants"

import { fetchRetry } from "./fetchRetry"

export const FETCH_ACCOUNT_HOLDERS_TASK_ID = "fetch-account-holders"

// Dune query: https://dune.com/queries/6676254
Expand Down Expand Up @@ -30,7 +32,7 @@ export async function fetchAccountHolders(): Promise<MetricReturnData> {

console.log("Starting account holders data fetch from Dune Analytics")

const response = await fetch(url, {
const response = await fetchRetry(url, {
headers: { "X-Dune-API-Key": duneApiKey },
})

Expand Down
8 changes: 5 additions & 3 deletions src/data-layer/fetchers/fetchApps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { AppCategoryEnum, AppData } from "@/lib/types"

import { uploadToS3 } from "@/data-layer/s3"

import { fetchRetry } from "./fetchRetry"

export const FETCH_APPS_TASK_ID = "fetch-apps"

/**
Expand All @@ -25,7 +27,7 @@ export async function fetchApps(): Promise<Record<string, AppData[]>> {
// First, get the spreadsheet metadata to see what sheets exist
const metadataUrl = `https://sheets.googleapis.com/v4/spreadsheets/${sheetId}?key=${googleApiKey}`

const metadataResponse = await fetch(metadataUrl)
const metadataResponse = await fetchRetry(metadataUrl)

if (!metadataResponse.ok) {
const errorText = await metadataResponse.text()
Expand All @@ -52,7 +54,7 @@ export async function fetchApps(): Promise<Record<string, AppData[]>> {

console.log(`Found ${appCategorySheetNames.length} app category sheets`)

const appsOfTheWeek = await fetch(
const appsOfTheWeek = await fetchRetry(
`https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/App%20of%20the%20day!A2:C?majorDimension=ROWS&key=${googleApiKey}`
)

Expand All @@ -69,7 +71,7 @@ export async function fetchApps(): Promise<Record<string, AppData[]>> {
// Fetch and process data from each sheet
for (const sheetName of appCategorySheetNames) {
const dataUrl = `https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/${sheetName}!A:Z?majorDimension=ROWS&key=${googleApiKey}`
const dataResponse = await fetch(dataUrl)
const dataResponse = await fetchRetry(dataUrl)

if (!dataResponse.ok) {
console.warn(
Expand Down
4 changes: 3 additions & 1 deletion src/data-layer/fetchers/fetchBlobscanStats.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { fetchRetry } from "./fetchRetry"

export const FETCH_BLOBSCAN_STATS_TASK_ID = "fetch-blobscan-stats"

export type BlobscanStats = {
Expand Down Expand Up @@ -26,7 +28,7 @@ export async function fetchBlobscanStats(): Promise<BlobscanStats> {

console.log("Starting blobscan stats data fetch")

const response = await fetch(url)
const response = await fetchRetry(url)

if (!response.ok) {
const status = response.status
Expand Down
6 changes: 4 additions & 2 deletions src/data-layer/fetchers/fetchCalendarEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type {
ReqCommunityEvent,
} from "@/lib/interfaces"

import { fetchRetry } from "./fetchRetry"

export const FETCH_CALENDAR_EVENTS_TASK_ID = "fetch-calendar-events"

/**
Expand All @@ -13,13 +15,13 @@ export async function fetchCalendarEvents(): Promise<CommunityEventsReturnType>
const apiKey = process.env.GOOGLE_API_KEY
const calendarId = process.env.GOOGLE_CALENDAR_ID

const futureEventsReq = await fetch(
const futureEventsReq = await fetchRetry(
`https://content.googleapis.com/calendar/v3/calendars/${calendarId}/events?key=${apiKey}&timeMin=${new Date().toISOString()}&maxResults=3&singleEvents=true&orderby=starttime`
)
const futureEvents = await futureEventsReq.json()
const futureEventsReqData: ReqCommunityEvent[] = futureEvents.items

const pastEventsReq = await fetch(
const pastEventsReq = await fetchRetry(
`https://content.googleapis.com/calendar/v3/calendars/${calendarId}/events?key=${apiKey}&timeMax=${new Date().toISOString()}&orderby=starttime`
)
const pastEvents = await pastEventsReq.json()
Expand Down
4 changes: 3 additions & 1 deletion src/data-layer/fetchers/fetchCommunityPicks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { CommunityPick } from "@/lib/types"

import { fetchRetry } from "./fetchRetry"

export const FETCH_COMMUNITY_PICKS_TASK_ID = "fetch-community-picks"

/**
Expand All @@ -20,7 +22,7 @@ export async function fetchCommunityPicks(): Promise<CommunityPick[]> {

console.log("Starting community picks data fetch from Google Sheets")

const response = await fetch(
const response = await fetchRetry(
`https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/community_picks!A:Z?majorDimension=ROWS&key=${googleApiKey}`
)

Expand Down
4 changes: 3 additions & 1 deletion src/data-layer/fetchers/fetchEthPrice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { MetricReturnData } from "@/lib/types"

import { fetchRetry } from "./fetchRetry"

export const FETCH_ETH_PRICE_TASK_ID = "fetch-eth-price"

/**
Expand All @@ -12,7 +14,7 @@ export async function fetchEthPrice(): Promise<MetricReturnData> {

console.log("Starting Ethereum price data fetch")

const response = await fetch(url)
const response = await fetchRetry(url)

if (!response.ok) {
const status = response.status
Expand Down
4 changes: 3 additions & 1 deletion src/data-layer/fetchers/fetchEthereumMarketcap.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { MetricReturnData } from "@/lib/types"

import { fetchRetry } from "./fetchRetry"

export const FETCH_ETHEREUM_MARKETCAP_TASK_ID = "fetch-ethereum-marketcap"

/**
Expand All @@ -12,7 +14,7 @@ export async function fetchEthereumMarketcap(): Promise<MetricReturnData> {

console.log("Starting Ethereum market cap data fetch")

const response = await fetch(url)
const response = await fetchRetry(url)

if (!response.ok) {
const status = response.status
Expand Down
4 changes: 3 additions & 1 deletion src/data-layer/fetchers/fetchEthereumStablecoinsMcap.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { MetricReturnData } from "@/lib/types"

import { fetchRetry } from "./fetchRetry"

export const FETCH_ETHEREUM_STABLECOINS_MCAP_TASK_ID =
"fetch-ethereum-stablecoins-mcap"

Expand All @@ -19,7 +21,7 @@ export async function fetchEthereumStablecoinsMcap(): Promise<MetricReturnData>

console.log("Starting Ethereum stablecoins market cap data fetch")

const response = await fetch(url)
const response = await fetchRetry(url)

if (!response.ok) {
const status = response.status
Expand Down
4 changes: 3 additions & 1 deletion src/data-layer/fetchers/fetchEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { slugify } from "@/lib/utils/url"

import { uploadToS3 } from "../s3"

import { fetchRetry } from "./fetchRetry"

export const FETCH_EVENTS_TASK_ID = "fetch-events"

// Priority order for eventTypes
Expand Down Expand Up @@ -66,7 +68,7 @@ export async function fetchEvents(): Promise<EventItem[]> {
console.log("Starting events data fetch from Geode Labs API")

try {
const response = await fetch(`${url}?select=*`, {
const response = await fetchRetry(`${url}?select=*`, {
headers: {
apikey: key,
Authorization: `Bearer ${key}`,
Expand Down
4 changes: 3 additions & 1 deletion src/data-layer/fetchers/fetchGFIs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { GHIssue } from "@/lib/types"

import { fetchRetry } from "./fetchRetry"

export const FETCH_GFIS_TASK_ID = "fetch-gfis"

const owner = "ethereum"
Expand Down Expand Up @@ -27,7 +29,7 @@ export async function fetchGFIs(): Promise<GHIssue[]> {

console.log("Starting GitHub good first issues data fetch")

const response = await fetch(url, {
const response = await fetchRetry(url, {
headers: {
Authorization: `token ${githubToken}`,
Accept: "application/vnd.github.v3+json",
Expand Down
4 changes: 3 additions & 1 deletion src/data-layer/fetchers/fetchGasPrice.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { fetchRetry } from "./fetchRetry"

export interface GasPriceData {
gasPrice: number
timestamp: number
Expand All @@ -8,7 +10,7 @@ export async function fetchGasPrice(): Promise<GasPriceData> {

console.log("Starting gas price data fetch")

const response = await fetch(
const response = await fetchRetry(
`https://api.etherscan.io/v2/api?chainid=1&module=gastracker&action=gasoracle&apikey=${etherscanApiKey}`
)

Expand Down
4 changes: 3 additions & 1 deletion src/data-layer/fetchers/fetchGitHistory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { Commit } from "@/lib/types"

import { fetchRetry } from "./fetchRetry"

export const FETCH_GIT_HISTORY_TASK_ID = "fetch-git-history"

const owner = "ethereum"
Expand All @@ -22,7 +24,7 @@ export async function fetchGitHistory(): Promise<Commit[]> {

console.log("Starting GitHub commit history data fetch")

const response = await fetch(url.href, {
const response = await fetchRetry(url.href, {
headers: {
Authorization: `token ${githubToken}`,
Accept: "application/vnd.github.v3+json",
Expand Down
10 changes: 6 additions & 4 deletions src/data-layer/fetchers/fetchGitHubContributors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { FileContributor, GitHubContributorsData } from "@/lib/types"

import { CONTENT_DIR, OLD_CONTENT_DIR } from "@/lib/constants"

import { fetchRetry } from "./fetchRetry"

const GITHUB_API_BASE =
"https://api.github.com/repos/ethereum/ethereum-org-website"

Expand All @@ -26,7 +28,7 @@ type NameLookup = Map<string, AllContributorsEntry>
async function fetchNameLookup(): Promise<NameLookup> {
const url =
"https://raw.githubusercontent.com/ethereum/ethereum-org-website/master/.all-contributorsrc"
const response = await fetch(url)
const response = await fetchRetry(url)

if (!response.ok) {
console.warn("Failed to fetch .all-contributorsrc:", response.status)
Expand Down Expand Up @@ -250,14 +252,14 @@ async function fetchCommitsForPath(
url.searchParams.set("path", filepath)
url.searchParams.set("sha", "master")

const response = await fetch(url.href, {
const response = await fetchRetry(url.href, {
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/vnd.github.v3+json",
},
})

// Handle rate limiting
// Handle GitHub-specific rate limiting (403, not 429)
if (
response.status === 403 &&
response.headers.get("X-RateLimit-Remaining") === "0"
Expand Down Expand Up @@ -368,7 +370,7 @@ async function discoverPathsFromTree(token: string): Promise<{
}> {
const url = `${GITHUB_API_BASE}/git/trees/master?recursive=1`

const response = await fetch(url, {
const response = await fetchRetry(url, {
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/vnd.github.v3+json",
Expand Down
Loading
Loading