From 39c94cca11db8a97d9add3ac05c1623755ecd2b3 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Mon, 9 Dec 2024 15:59:12 +0500 Subject: [PATCH] feat(cow-fi): cache data and pages (#5169) --- apps/cow-fi/const/meta.ts | 2 +- apps/cow-fi/next.config.js | 15 ++++++++ apps/cow-fi/pages/_app.tsx | 21 ++++++----- apps/cow-fi/services/ashByHq/index.ts | 3 +- apps/cow-fi/services/cms/index.ts | 53 +++++---------------------- apps/cow-fi/services/cow/index.tsx | 17 ++++++--- apps/cow-fi/services/dune/index.tsx | 6 ++- apps/cow-fi/services/tokens/index.ts | 6 ++- package.json | 2 +- yarn.lock | 24 +++++------- 10 files changed, 69 insertions(+), 80 deletions(-) diff --git a/apps/cow-fi/const/meta.ts b/apps/cow-fi/const/meta.ts index b30ac93756..dc2f8b528e 100644 --- a/apps/cow-fi/const/meta.ts +++ b/apps/cow-fi/const/meta.ts @@ -3,7 +3,7 @@ import { TokenInfo } from 'types' const API_BASE_URL = 'https://api.cow.fi' export const IMAGE_PATH = 'images/' -export const DATA_CACHE_TIME_SECONDS = 5 * 60 // Cache 5min +export const DATA_CACHE_TIME_SECONDS = 60 * 60 // Cache 1 hour export const CONFIG = { title: 'CoW DAO', diff --git a/apps/cow-fi/next.config.js b/apps/cow-fi/next.config.js index eea085aaa7..914f6503b2 100644 --- a/apps/cow-fi/next.config.js +++ b/apps/cow-fi/next.config.js @@ -1,6 +1,7 @@ const { composePlugins, withNx } = require('@nx/next') const nextConfig = { + reactStrictMode: true, nx: { svgr: false, }, @@ -91,6 +92,20 @@ const nextConfig = { images: { domains: ['celebrated-gift-f83e5c9419.media.strapiapp.com'], }, + async headers() { + return [ + // Cache all pages for 60 seconds + { + source: '/:path*', + headers: [ + { + key: 'Cache-Control', + value: 'public, s-maxage=60, stale-while-revalidate=600', + }, + ], + }, + ] + }, } const plugins = [withNx] diff --git a/apps/cow-fi/pages/_app.tsx b/apps/cow-fi/pages/_app.tsx index 49bbe0b0dd..9aedb90832 100644 --- a/apps/cow-fi/pages/_app.tsx +++ b/apps/cow-fi/pages/_app.tsx @@ -10,6 +10,7 @@ import { WithLDProvider } from '@/components/WithLDProvider' import { ThemeProvider } from '../theme' import { CowAnalyticsProvider } from '@cowprotocol/analytics' import { cowAnalytics } from 'modules/analytics' +import CacheProvider from 'react-inlinesvg/provider' export default function App(props: AppProps) { const { Component, pageProps } = props @@ -54,15 +55,17 @@ export default function App(props: AppProps) { - - - - - - - - - + + + + + + + + + + + ) } diff --git a/apps/cow-fi/services/ashByHq/index.ts b/apps/cow-fi/services/ashByHq/index.ts index 140aae86fa..d5136e698a 100644 --- a/apps/cow-fi/services/ashByHq/index.ts +++ b/apps/cow-fi/services/ashByHq/index.ts @@ -1,4 +1,4 @@ -import { CONFIG } from '@/const/meta' +import { CONFIG, DATA_CACHE_TIME_SECONDS } from '@/const/meta' interface AshbyResponse { data: { @@ -25,6 +25,7 @@ export async function getJobs() { try { console.log('Fetching data from Ashby HQ API...') const response = await fetch(ashbyHqApi, { + next: { revalidate: DATA_CACHE_TIME_SECONDS }, method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/apps/cow-fi/services/cms/index.ts b/apps/cow-fi/services/cms/index.ts index d1a1bb6c24..ef79552118 100644 --- a/apps/cow-fi/services/cms/index.ts +++ b/apps/cow-fi/services/cms/index.ts @@ -1,9 +1,10 @@ -import { CmsClient, components } from '@cowprotocol/cms' +import { components } from '@cowprotocol/cms' import { PaginationParam } from 'types' import qs from 'qs' import { toQueryParams } from 'util/queryParams' import { getCmsClient } from '@cowprotocol/core' +import { DATA_CACHE_TIME_SECONDS } from '@/const/meta' const PAGE_SIZE = 50 @@ -22,56 +23,17 @@ export type ArticleListResponse = { } } -export type SharedMediaComponent = Schemas['SharedMediaComponent'] -export type SharedQuoteComponent = Schemas['SharedQuoteComponent'] export type SharedRichTextComponent = Schemas['SharedRichTextComponent'] -export type SharedSliderComponent = Schemas['SharedSliderComponent'] -export type SharedVideoEmbedComponent = Schemas['SharedVideoEmbedComponent'] export type Category = Schemas['CategoryListResponseDataItem'] -export type ArticleCover = Schemas['Article']['cover'] -export type ArticleBlocks = Schemas['Article']['blocks'] - -export type ArticleBlock = - | SharedMediaComponent - | SharedQuoteComponent - | SharedRichTextComponent - | SharedSliderComponent - | SharedVideoEmbedComponent - -export function isSharedMediaComponent(component: ArticleBlock): component is SharedMediaComponent { - return component.__component === 'SharedMediaComponent' -} - -export function isSharedQuoteComponent(component: ArticleBlock): component is SharedQuoteComponent { - return component.__component === 'SharedQuoteComponent' -} - -export function isSharedRichTextComponent(component: ArticleBlock): component is SharedRichTextComponent { - return component.__component === 'shared.rich-text' -} - -export function isSharedSliderComponent(component: ArticleBlock): component is SharedMediaComponent { - return component.__component === 'SharedSliderComponent' -} - -export function isSharedVideoEmbedComponent(component: ArticleBlock): component is SharedVideoEmbedComponent { - return component.__component === 'SharedVideoEmbedComponent' -} /** * Open API Fetch client. See docs for usage https://openapi-ts.pages.dev/openapi-fetch/ */ export const client = getCmsClient() -/** - * Returns the article slugs for the given page. - * - * @param params pagination params - * @returns Slugs - */ -async function getArticlesSlugs(params: PaginationParam = {}): Promise { - const articlesResponse = await getArticles(params) - return articlesResponse.data.map((article: Article) => article.attributes!.slug!) +const clientAddons = { + // https://github.com/openapi-ts/openapi-typescript/issues/1569#issuecomment-1982247959 + fetch: (request: unknown) => fetch(request as Request, { next: { revalidate: DATA_CACHE_TIME_SECONDS } }), } /** @@ -92,6 +54,7 @@ export async function getAllArticleSlugs(): Promise { }, }, querySerializer, + ...clientAddons, }) if (error) { @@ -119,6 +82,7 @@ export async function getCategories(): Promise { }, sort: 'name:asc', }, + ...clientAddons, }) if (error) { @@ -174,6 +138,7 @@ export async function getArticles({ }, }, querySerializer, + ...clientAddons, }) if (error) { @@ -211,6 +176,7 @@ export async function getArticleBySlug(slug: string): Promise
{ }, }, querySerializer, + ...clientAddons, }) if (error) { @@ -303,6 +269,7 @@ async function getBySlugAux(slug: string, endpoint: '/categories' | '/articles') params: { query, }, + ...clientAddons, }) if (error) { diff --git a/apps/cow-fi/services/cow/index.tsx b/apps/cow-fi/services/cow/index.tsx index 3dd5d53c85..d4585c8756 100644 --- a/apps/cow-fi/services/cow/index.tsx +++ b/apps/cow-fi/services/cow/index.tsx @@ -1,18 +1,23 @@ +import { DATA_CACHE_TIME_SECONDS } from '@/const/meta' + const CONFIG_PATH = 'https://raw.githubusercontent.com/cowprotocol/cow-fi/configuration/config/' export interface CowStats { - totalTrades: number, // https://dune.com/queries/1034337 - surplus: { // https://dune.com/queries/270604 - reasonable: number, + totalTrades: number // https://dune.com/queries/1034337 + surplus: { + // https://dune.com/queries/270604 + reasonable: number unusual: number - }, + } lastModified: Date } type CowStatsConfig = Omit & { lastModified: string } async function getFromConfig(configFilePath: string): Promise { - const response = await fetch(CONFIG_PATH + `${configFilePath}`) + const response = await fetch(CONFIG_PATH + `${configFilePath}`, { + next: { revalidate: DATA_CACHE_TIME_SECONDS }, + }) return await response.json() } @@ -20,6 +25,6 @@ export async function getCowStats(): Promise { const statsConfig = await getFromConfig('stats.json') return { ...statsConfig, - lastModified: new Date(statsConfig.lastModified) + lastModified: new Date(statsConfig.lastModified), } } diff --git a/apps/cow-fi/services/dune/index.tsx b/apps/cow-fi/services/dune/index.tsx index b4447831e6..ec292c090c 100644 --- a/apps/cow-fi/services/dune/index.tsx +++ b/apps/cow-fi/services/dune/index.tsx @@ -1,3 +1,4 @@ +import { DATA_CACHE_TIME_SECONDS } from '@/const/meta' import { strict as assert } from 'node:assert' const DUNE_API_KEY = process.env.DUNE_API_KEY! @@ -21,6 +22,7 @@ interface GetFromDuneResult { export async function getFromDune(queryId: number): Promise> { const response = await fetch(`https://api.dune.com/api/v0/query/${queryId}/results`, { + next: { revalidate: DATA_CACHE_TIME_SECONDS }, headers: { accept: 'application/json', 'X-DUNE-API-KEY': DUNE_API_KEY, @@ -47,7 +49,7 @@ export async function _getTotalCount(queryId: number): Promise { // Expect one row assert( queryResut.rows.length === 1, - `Total Count Dune query (${queryId}) must return just one row. Returned ${queryResut.rows.length}` + `Total Count Dune query (${queryId}) must return just one row. Returned ${queryResut.rows.length}`, ) return { @@ -66,7 +68,7 @@ export const getTotalTrades = () => _getTotalCount(TOTAL_TRADES_COUNT_QUERY_ID) */ export const getTotalSurplus = async (): Promise => { const queryResut = await getFromDune<{ surplus_type: string; total_surplus_usd: number }>( - TOTAL_SURPLUS_COUNT_QUERY_ID + TOTAL_SURPLUS_COUNT_QUERY_ID, ) const totalCount = queryResut.rows.reduce((totalSurplus, surplus) => { diff --git a/apps/cow-fi/services/tokens/index.ts b/apps/cow-fi/services/tokens/index.ts index 4a242fa992..75f0719e2a 100644 --- a/apps/cow-fi/services/tokens/index.ts +++ b/apps/cow-fi/services/tokens/index.ts @@ -2,6 +2,7 @@ import fs from 'fs' import path from 'path' import { PlatformData, Platforms, TokenDetails, TokenInfo } from 'types' import { backOff } from 'exponential-backoff' +import { DATA_CACHE_TIME_SECONDS } from '@/const/meta' const NETWORKS = ['ethereum', 'xdai'] const COW_TOKEN_ID = 'cow-protocol' @@ -72,8 +73,9 @@ function _getDescriptionFilePaths(): string[] { async function fetchWithBackoff(url: string) { return backOff( () => { - console.log(`Fetching ${url}`) - return fetch(url).then((res) => { + return fetch(url, { + next: { revalidate: DATA_CACHE_TIME_SECONDS }, + }).then((res) => { if (!res.ok) { throw new Error(`Error fetching list ${url}: Error ${res.status}, ${res.statusText}`) } diff --git a/package.json b/package.json index 8b05424032..3f16f55971 100644 --- a/package.json +++ b/package.json @@ -201,7 +201,7 @@ "react-ga4": "^1.4.1", "react-helmet": "^6.1.0", "react-icons": "^5.2.1", - "react-inlinesvg": "^3.0.1", + "react-inlinesvg": "^4.1.5", "react-intersection-observer": "^9.10.1", "react-is": "19.0.0-rc-66855b96-20241106", "react-markdown": "^9.0.0", diff --git a/yarn.lock b/yarn.lock index 006525c475..87a89909cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16514,11 +16514,6 @@ executable@^4.1.0, executable@^4.1.1: dependencies: pify "^2.2.0" -exenv@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" - integrity sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw== - exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -26133,10 +26128,10 @@ react-focus-lock@2.5.2: use-callback-ref "^1.2.5" use-sidecar "^1.0.5" -react-from-dom@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/react-from-dom/-/react-from-dom-0.6.2.tgz#9da903a508c91c013b55afcd59348b8b0a39bdb4" - integrity sha512-qvWWTL/4xw4k/Dywd41RBpLQUSq97csuv15qrxN+izNeLYlD9wn5W8LspbfYe5CWbaSdkZ72BsaYBPQf2x4VbQ== +react-from-dom@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/react-from-dom/-/react-from-dom-0.7.3.tgz#60e75fde2369ceb0a8f87d88f9cfbeb67b730e43" + integrity sha512-9IK6R7+eD1wOAMC2ZCrENev0eK1625cb7vX+cnnOR9LBRNbjKiaJk4ij2zQbcefEXTWjXFhA7CTO1cd8wMONnw== react-ga4@^1.4.1: version "1.4.1" @@ -26158,13 +26153,12 @@ react-icons@^5.2.1: resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.2.1.tgz#28c2040917b2a2eda639b0f797bff1888e018e4a" integrity sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw== -react-inlinesvg@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/react-inlinesvg/-/react-inlinesvg-3.0.2.tgz#5c59799966ae7926057091b2ac230ddcee01bea0" - integrity sha512-BEzkpMGQwEY68fgaouY7ZWvAUPb8jbj7dE9iDbWZxstDhMuz9qfpxNgvGSENKcDMdpq/XHduSk/LAmNKin4nKw== +react-inlinesvg@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/react-inlinesvg/-/react-inlinesvg-4.1.5.tgz#5d6b4f9008d442e4f184ec25e135360d993dc47e" + integrity sha512-DcCnmHhpKAUNp6iLPEEB2HJP3simDlyiy8JPZ1DwGCynrQQGQD04GJTFtai8JK8vRhCmoiBV6hSgj31D42Z3Lg== dependencies: - exenv "^1.2.2" - react-from-dom "^0.6.2" + react-from-dom "^0.7.3" react-inspector@^5.1.0: version "5.1.1"