-
Notifications
You must be signed in to change notification settings - Fork 5.4k
[feat] data layer #16801
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
Open
corwintines
wants to merge
9
commits into
dev
Choose a base branch
from
datalayer
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+54,390
−406
Open
[feat] data layer #16801
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
3d917b8
setup netlify blobs data layer
corwintines ae3c059
Enhance data layer by adding new fetch functions for community picks,…
corwintines 255911f
More apis
corwintines 5a5e859
more api
corwintines b72be11
mocks and docs
corwintines 668f798
Merge branch 'dev' into datalayer
corwintines b971851
architecture change requests
corwintines c3920bd
change requests and fix errors
corwintines 3c13281
Merge branch 'dev' into datalayer
corwintines File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,16 +23,19 @@ | |
| "crowdin-needs-review": "ts-node -O '{ \"module\": \"commonjs\" }' src/scripts/crowdin/reports/generateReviewReport.ts", | ||
| "update-tutorials": "ts-node -O '{ \"module\": \"commonjs\" }' src/scripts/update-tutorials-list.ts", | ||
| "prepare": "husky", | ||
| "test:e2e": "playwright test", | ||
| "test:e2e:ui": "playwright test --ui", | ||
| "test:e2e:debug": "playwright test --debug", | ||
| "test:e2e:report": "playwright show-report tests/e2e/__report__" | ||
| "test:e2e": "playwright test --project=e2e", | ||
| "test:e2e:ui": "playwright test --project=e2e --ui", | ||
| "test:e2e:debug": "playwright test --project=e2e --debug", | ||
| "test:e2e:report": "playwright show-report tests/__report__", | ||
| "test:unit": "USE_MOCK_DATA=true playwright test --project=unit", | ||
| "trigger:dev": "dotenv -e .env -- npx trigger.dev@latest dev" | ||
| }, | ||
| "dependencies": { | ||
| "@aws-sdk/client-ses": "^3.859.0", | ||
| "@crowdin/crowdin-api-client": "^1.25.0", | ||
| "@docsearch/react": "^3.5.2", | ||
| "@hookform/resolvers": "^3.8.0", | ||
| "@netlify/blobs": "^10.4.1", | ||
| "@next/bundle-analyzer": "^14.2.5", | ||
| "@radix-ui/react-accordion": "^1.2.0", | ||
| "@radix-ui/react-avatar": "^1.1.2", | ||
|
|
@@ -60,6 +63,7 @@ | |
| "@tanstack/react-query": "^5.66.7", | ||
| "@tanstack/react-table": "^8.19.3", | ||
| "@tanstack/react-virtual": "^3.13.12", | ||
| "@trigger.dev/sdk": "4.2.0", | ||
| "@types/canvas-confetti": "^1.9.0", | ||
| "@types/three": "^0.177.0", | ||
| "@wagmi/core": "^2.17.3", | ||
|
|
@@ -136,6 +140,7 @@ | |
| "chromatic": "12.0.0", | ||
| "decompress": "^4.2.1", | ||
| "dotenv": "^16.5.0", | ||
| "dotenv-cli": "^11.0.0", | ||
| "eslint": "^8.57.1", | ||
| "eslint-config-next": "^14.2.2", | ||
| "eslint-config-prettier": "^9", | ||
|
|
@@ -166,4 +171,4 @@ | |
| "xml2js": "^0.6.2" | ||
| }, | ||
| "packageManager": "[email protected]" | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,192 @@ | ||
| import { AppCategoryEnum, AppData } from "@/lib/types" | ||
|
|
||
| export const FETCH_APPS_TASK_ID = "fetch-apps" | ||
|
|
||
| /** | ||
| * Fetch apps data from Google Sheets. | ||
| * Returns the fetched apps data organized by category. | ||
| */ | ||
| export async function fetchApps(): Promise<Record<string, AppData[]>> { | ||
| const googleApiKey = process.env.GOOGLE_API_KEY | ||
| const sheetId = process.env.GOOGLE_SHEET_ID_DAPPS | ||
|
|
||
| if (!sheetId) { | ||
| throw new Error("Google Sheets ID not set") | ||
| } | ||
|
|
||
| if (!googleApiKey) { | ||
| throw new Error("Google API key not set") | ||
| } | ||
|
|
||
| console.log("Starting apps data fetch from Google Sheets") | ||
|
|
||
| // 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) | ||
|
|
||
| if (!metadataResponse.ok) { | ||
| const errorText = await metadataResponse.text() | ||
| console.error("Metadata fetch error", { | ||
| status: metadataResponse.status, | ||
| statusText: metadataResponse.statusText, | ||
| error: errorText, | ||
| }) | ||
| throw new Error( | ||
| `Metadata fetch failed: ${metadataResponse.status} ${metadataResponse.statusText}` | ||
| ) | ||
| } | ||
|
|
||
| const metadata = await metadataResponse.json() | ||
| const sheetNames = | ||
| metadata.sheets?.map( | ||
| (sheet: { properties: { title: string } }) => sheet.properties.title | ||
| ) || [] | ||
|
|
||
| // Filter out sheets that are not valid AppCategoryEnum values | ||
| const appCategorySheetNames = sheetNames.filter((name: string) => | ||
| Object.values(AppCategoryEnum).includes(name as AppCategoryEnum) | ||
| ) | ||
|
|
||
| console.log(`Found ${appCategorySheetNames.length} app category sheets`) | ||
|
|
||
| const appsOfTheWeek = await fetch( | ||
| `https://sheets.googleapis.com/v4/spreadsheets/${sheetId}/values/App%20of%20the%20day!A2:C?majorDimension=ROWS&key=${googleApiKey}` | ||
| ) | ||
|
|
||
| if (!appsOfTheWeek.ok) { | ||
| console.warn( | ||
| `Failed to fetch from sheet Apps of the day: ${appsOfTheWeek.status} ${appsOfTheWeek.statusText}` | ||
| ) | ||
| } | ||
|
|
||
| const appsOfTheWeekData = await appsOfTheWeek.json() | ||
|
|
||
| const result: 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) | ||
|
|
||
| if (!dataResponse.ok) { | ||
| console.warn( | ||
| `Failed to fetch from sheet ${sheetName}: ${dataResponse.status} ${dataResponse.statusText}` | ||
| ) | ||
| result[sheetName] = [] | ||
| continue | ||
| } | ||
|
|
||
| const data = await dataResponse.json() | ||
| const rows = data.values || [] | ||
|
|
||
| if (rows.length === 0) { | ||
| result[sheetName] = [] | ||
| continue | ||
| } | ||
|
|
||
| // Process data rows (skip header) | ||
| const dataRows = rows.slice(1).filter((row: string[]) => { | ||
| // Filter out completely empty rows or rows without a name | ||
| return row.length > 0 && row[0]?.trim() !== "" | ||
| }) | ||
|
|
||
| const apps: AppData[] = dataRows | ||
| .map((row: string[]) => { | ||
| // Map row data to app object | ||
| const appData = { | ||
| name: row[0] || "", | ||
| url: row[1] || "", | ||
| description: row[2] || "", | ||
| image: row[3] || "", // Use the SVG data directly from the Logo Image column | ||
| category: getCategoryFromSheetName(sheetName), | ||
| subCategory: parseCommaSeparated(row[5] || ""), | ||
| networks: parseCommaSeparated(row[6] || ""), | ||
| screenshots: parseCommaSeparated(row[7] || ""), | ||
| bannerImage: row[8] || "", | ||
| platforms: parseCommaSeparated(row[9] || ""), | ||
| twitter: row[10] || "", | ||
| github: row[11] || "", | ||
| discord: row[12] || "", | ||
| kpiUrl: row[13] || "", | ||
| sortingWeight: parseInt(row[14] || "0") || 0, | ||
| discover: row[15]?.toLowerCase() === "true", | ||
| highlight: row[16]?.toLowerCase() === "true", | ||
| languages: parseCommaSeparated(row[17] || ""), | ||
| parentCompany: row[18] || "", | ||
| parentCompanyURL: row[19] || "", | ||
| openSource: row[20]?.toLowerCase() === "true", | ||
| contractAddress: row[21] || "", | ||
| dateOfLaunch: row[22] || "", | ||
| lastUpdated: row[23] || "", | ||
| ready: row[24]?.toLowerCase(), | ||
| devconnect: row[25]?.toLowerCase(), | ||
| ...parseAppOfTheWeekDate(row[0], appsOfTheWeekData), | ||
| } | ||
|
|
||
| return appData as unknown as AppData | ||
| }) | ||
| .filter((app: AppData) => app.name && app.url) // Filter out apps without name or URL | ||
| .filter((app: AppData) => app.ready === "true") | ||
|
|
||
| result[sheetName] = apps | ||
| console.log(`Processed ${apps.length} apps from ${sheetName}`) | ||
| } | ||
|
|
||
| const totalApps = Object.values(result).reduce( | ||
| (sum, apps) => sum + apps.length, | ||
| 0 | ||
| ) | ||
|
|
||
| console.log( | ||
| `Successfully fetched ${totalApps} apps across ${Object.keys(result).length} categories` | ||
| ) | ||
|
|
||
| return result | ||
| } | ||
|
|
||
| // Helper function to map sheet names to AppCategoryEnum | ||
| function getCategoryFromSheetName(sheetName: string): AppCategoryEnum { | ||
| switch (sheetName) { | ||
| case "DeFi": | ||
| return AppCategoryEnum.DEFI | ||
| case "Collectibles": | ||
| return AppCategoryEnum.COLLECTIBLE | ||
| case "Social": | ||
| return AppCategoryEnum.SOCIAL | ||
| case "Gaming": | ||
| return AppCategoryEnum.GAMING | ||
| case "Bridge": | ||
| return AppCategoryEnum.BRIDGE | ||
| case "Productivity": | ||
| return AppCategoryEnum.PRODUCTIVITY | ||
| case "Privacy": | ||
| return AppCategoryEnum.PRIVACY | ||
| case "DAO": | ||
| return AppCategoryEnum.GOVERNANCE_DAO | ||
| default: | ||
| return AppCategoryEnum.DEFI // Default fallback | ||
| } | ||
| } | ||
|
|
||
| // Helper function to parse comma-separated strings into arrays | ||
| function parseCommaSeparated(value: string): string[] { | ||
| if (!value || value.trim() === "") return [] | ||
| return value | ||
| .split(",") | ||
| .map((item) => item.trim()) | ||
| .filter(Boolean) | ||
| } | ||
|
|
||
| const parseAppOfTheWeekDate = ( | ||
| appName: string, | ||
| appsOfTheWeekData: { values: Array<[string, string, string]> } | ||
| ): { appOfTheWeekStartDate: Date | null; appOfTheWeekEndDate: Date | null } => { | ||
| const appOfTheWeek = appsOfTheWeekData.values.find( | ||
| (app: [string, string, string]) => app[0] === appName | ||
| ) | ||
| return { | ||
| appOfTheWeekStartDate: appOfTheWeek ? new Date(appOfTheWeek[1]) : null, | ||
| appOfTheWeekEndDate: appOfTheWeek ? new Date(appOfTheWeek[2]) : null, | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import type { BeaconchainEpochData, EpochResponse } from "@/lib/types" | ||
|
|
||
| export const FETCH_BEACONCHAIN_EPOCH_TASK_ID = "fetch-beaconchain-epoch" | ||
|
|
||
| /** | ||
| * Fetch beaconchain epoch data from Beaconcha.in API. | ||
| * Returns the latest epoch data including total ETH staked and validator count. | ||
| */ | ||
| export async function fetchBeaconChainEpoch(): Promise<BeaconchainEpochData> { | ||
| const base = "https://beaconcha.in" | ||
| const endpoint = "api/v1/epoch/latest" | ||
| const { href } = new URL(endpoint, base) | ||
|
|
||
| console.log("Starting beaconchain epoch data fetch") | ||
|
|
||
| const response = await fetch(href) | ||
|
|
||
| if (!response.ok) { | ||
| const status = response.status | ||
| console.warn("Beaconcha.in fetch non-OK", { status, url: href }) | ||
| const error = `Beaconcha.in responded with status ${status}` | ||
| throw new Error(error) | ||
| } | ||
|
|
||
| const json: EpochResponse = await response.json() | ||
| const { validatorscount, eligibleether } = json.data | ||
| const totalEthStaked = Math.floor(eligibleether * 1e-9) // `eligibleether` value returned in `gwei` | ||
| const timestamp = Date.now() | ||
|
|
||
| console.log("Successfully fetched beaconchain epoch data", { | ||
| totalEthStaked, | ||
| validatorscount, | ||
| timestamp, | ||
| }) | ||
|
|
||
| return { | ||
| totalEthStaked: { value: totalEthStaked, timestamp }, | ||
| validatorscount: { value: validatorscount, timestamp }, | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import type { EthStoreResponse, MetricReturnData } from "@/lib/types" | ||
|
|
||
| export const FETCH_BEACONCHAIN_ETHSTORE_TASK_ID = "fetch-beaconchain-ethstore" | ||
|
|
||
| /** | ||
| * Fetch beaconchain ethstore data from Beaconcha.in API. | ||
| * Returns the latest APR data. | ||
| */ | ||
| export async function fetchBeaconChainEthstore(): Promise<MetricReturnData> { | ||
| const base = "https://beaconcha.in" | ||
| const endpoint = "api/v1/ethstore/latest" | ||
| const { href } = new URL(endpoint, base) | ||
|
|
||
| console.log("Starting beaconchain ethstore data fetch") | ||
|
|
||
| const response = await fetch(href) | ||
|
|
||
| if (!response.ok) { | ||
| const status = response.status | ||
| console.warn("Beaconcha.in fetch non-OK", { status, url: href }) | ||
| const error = `Beaconcha.in responded with status ${status}` | ||
| throw new Error(error) | ||
| } | ||
|
|
||
| const json: EthStoreResponse = await response.json() | ||
| const apr = json.data.apr | ||
| const timestamp = Date.now() | ||
|
|
||
| console.log("Successfully fetched beaconchain ethstore data", { | ||
| apr, | ||
| timestamp, | ||
| }) | ||
|
|
||
| return { value: apr, timestamp } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is probably need that we update GH action yaml file as well, or not?