diff --git a/apps/api/src/helpers/middlewares/rateLimiter.ts b/apps/api/src/helpers/middlewares/rateLimiter.ts index 28576076db86..f2b0bf0a786e 100644 --- a/apps/api/src/helpers/middlewares/rateLimiter.ts +++ b/apps/api/src/helpers/middlewares/rateLimiter.ts @@ -6,7 +6,7 @@ import rateLimit from 'express-rate-limit'; import RedisStore from 'rate-limit-redis'; import catchedError from '../catchedError'; -import redisClient from '../redis'; +import redisClient from '../redisClient'; const hashedIp = (req: Request): string => sha256(getIp(req)).slice(0, 25); diff --git a/apps/api/src/helpers/redis.ts b/apps/api/src/helpers/redisClient.ts similarity index 100% rename from apps/api/src/helpers/redis.ts rename to apps/api/src/helpers/redisClient.ts diff --git a/apps/api/src/routes/internal/features/assign.ts b/apps/api/src/routes/internal/features/assign.ts index a89128f71484..a9db964175a7 100644 --- a/apps/api/src/routes/internal/features/assign.ts +++ b/apps/api/src/routes/internal/features/assign.ts @@ -1,13 +1,23 @@ import type { Request, Response } from 'express'; +import { VERIFIED_FEATURE_ID } from '@hey/data/constants'; import logger from '@hey/helpers/logger'; import heyPg from 'src/db/heyPg'; import catchedError from 'src/helpers/catchedError'; import validateIsStaff from 'src/helpers/middlewares/validateIsStaff'; import validateLensAccount from 'src/helpers/middlewares/validateLensAccount'; +import redisClient from 'src/helpers/redisClient'; import { invalidBody, noBody } from 'src/helpers/responses'; import { boolean, object, string } from 'zod'; +const clearCache = async (profileId: string, featureId: string) => { + await redisClient.del(`preference:${profileId}`); + await redisClient.del(`profile:${profileId}`); + if (featureId === VERIFIED_FEATURE_ID) { + await redisClient.del('verified'); + } +}; + type ExtensionRequest = { enabled: boolean; id: string; @@ -49,6 +59,7 @@ export const post = [ [id, profile_id] ); + await clearCache(profile_id, id); logger.info(`Enabled features for ${profile_id}`); return res.status(200).json({ enabled, success: true }); @@ -62,6 +73,7 @@ export const post = [ [profile_id, id] ); + await clearCache(profile_id, id); logger.info(`Disabled features for ${profile_id}`); return res.status(200).json({ enabled, success: true }); diff --git a/apps/api/src/routes/internal/preferences/get.ts b/apps/api/src/routes/internal/preferences/get.ts index 9c7537d7ab5a..c98905080634 100644 --- a/apps/api/src/routes/internal/preferences/get.ts +++ b/apps/api/src/routes/internal/preferences/get.ts @@ -6,6 +6,7 @@ import heyPg from 'src/db/heyPg'; import catchedError from 'src/helpers/catchedError'; import validateIsStaff from 'src/helpers/middlewares/validateIsStaff'; import validateLensAccount from 'src/helpers/middlewares/validateLensAccount'; +import redisClient from 'src/helpers/redisClient'; import { noBody } from 'src/helpers/responses'; export const get = [ @@ -18,6 +19,16 @@ export const get = [ return noBody(res); } + const cacheKey = `preference:${id}`; + const cachedPreference = await redisClient.get(cacheKey); + + if (cachedPreference) { + logger.info(`(cached) Internal profile preferences fetched for ${id}`); + return res + .status(200) + .json({ result: JSON.parse(cachedPreference), success: true }); + } + try { const [preference, features, email, membershipNft] = await heyPg.multi( ` @@ -50,7 +61,8 @@ export const get = [ ) }; - logger.info(`Profile preferences fetched for ${id}`); + await redisClient.set(cacheKey, JSON.stringify(response)); + logger.info(`Internal profile preferences fetched for ${id}`); return res.status(200).json({ result: response, success: true }); } catch (error) { diff --git a/apps/api/src/routes/internal/tokens/create.ts b/apps/api/src/routes/internal/tokens/create.ts index 1e04ca4c892d..db01497c084b 100644 --- a/apps/api/src/routes/internal/tokens/create.ts +++ b/apps/api/src/routes/internal/tokens/create.ts @@ -6,6 +6,7 @@ import heyPg from 'src/db/heyPg'; import catchedError from 'src/helpers/catchedError'; import validateIsStaff from 'src/helpers/middlewares/validateIsStaff'; import validateLensAccount from 'src/helpers/middlewares/validateLensAccount'; +import redisClient from 'src/helpers/redisClient'; import { invalidBody, noBody } from 'src/helpers/responses'; import { number, object, string } from 'zod'; @@ -55,6 +56,7 @@ export const post = [ [contractAddress, decimals, name, symbol] ); + await redisClient.del(`allowedTokens`); logger.info(`Created a token ${token[0]?.id}`); return res.status(200).json({ success: true, token: token[0] }); diff --git a/apps/api/src/routes/internal/tokens/delete.ts b/apps/api/src/routes/internal/tokens/delete.ts index 33975e45d09b..b51832b1889c 100644 --- a/apps/api/src/routes/internal/tokens/delete.ts +++ b/apps/api/src/routes/internal/tokens/delete.ts @@ -5,6 +5,7 @@ import heyPg from 'src/db/heyPg'; import catchedError from 'src/helpers/catchedError'; import validateIsStaff from 'src/helpers/middlewares/validateIsStaff'; import validateLensAccount from 'src/helpers/middlewares/validateLensAccount'; +import redisClient from 'src/helpers/redisClient'; import { invalidBody, noBody } from 'src/helpers/responses'; import { object, string } from 'zod'; @@ -36,6 +37,7 @@ export const post = [ try { await heyPg.query(`DELETE FROM "AllowedToken" WHERE id = $1`, [id]); + await redisClient.del(`allowedTokens`); logger.info(`Deleted a token ${id}`); return res.status(200).json({ success: true }); diff --git a/apps/api/src/routes/lens/internal/stats/heyRevenue.ts b/apps/api/src/routes/lens/internal/stats/heyRevenue.ts index 4d854ff437e1..f6f0466b64c3 100644 --- a/apps/api/src/routes/lens/internal/stats/heyRevenue.ts +++ b/apps/api/src/routes/lens/internal/stats/heyRevenue.ts @@ -47,7 +47,7 @@ export const get = [ signups_count: Number(row.signups_count) })); - logger.info('Lens: Fetched signup and membership NFT stats'); + logger.info('[Lens] Fetched signup and membership NFT stats'); return res.status(200).json({ result: formattedResult, success: true }); } catch (error) { diff --git a/apps/api/src/routes/lens/internal/stats/publication.ts b/apps/api/src/routes/lens/internal/stats/publication.ts index ebb22729941d..c814f12a6a75 100644 --- a/apps/api/src/routes/lens/internal/stats/publication.ts +++ b/apps/api/src/routes/lens/internal/stats/publication.ts @@ -29,7 +29,7 @@ export const get = [ GROUP BY app; `); - logger.info('Lens: Fetched publication stats'); + logger.info('[Lens] Fetched publication stats'); return res.status(200).json({ result: result[0], success: true }); } catch (error) { diff --git a/apps/api/src/routes/lens/internal/stats/revenue.ts b/apps/api/src/routes/lens/internal/stats/revenue.ts index c363958b46df..cd1a852210ce 100644 --- a/apps/api/src/routes/lens/internal/stats/revenue.ts +++ b/apps/api/src/routes/lens/internal/stats/revenue.ts @@ -28,7 +28,7 @@ export const get = [ ORDER BY month, revenue DESC; `); - logger.info('Lens: Fetched app revenue'); + logger.info('[Lens] Fetched app revenue'); return res.status(200).json({ result, success: true }); } catch (error) { diff --git a/apps/api/src/routes/lens/rate.ts b/apps/api/src/routes/lens/rate.ts index 1b6ae1177c0e..ee233e062d87 100644 --- a/apps/api/src/routes/lens/rate.ts +++ b/apps/api/src/routes/lens/rate.ts @@ -4,10 +4,22 @@ import logger from '@hey/helpers/logger'; import lensPg from 'src/db/lensPg'; import catchedError from 'src/helpers/catchedError'; import { CACHE_AGE_30_MINS } from 'src/helpers/constants'; +import redisClient from 'src/helpers/redisClient'; // TODO: add tests -export const get: Handler = async (req, res) => { +export const get: Handler = async (_, res) => { try { + const cacheKey = 'rates'; + const cachedRates = await redisClient.get(cacheKey); + + if (cachedRates) { + logger.info('(cached) [Lens] Fetched USD conversion rates'); + return res + .status(200) + .setHeader('Cache-Control', CACHE_AGE_30_MINS) + .json({ result: JSON.parse(cachedRates), success: true }); + } + const response = await lensPg.query(` SELECT ec.name AS name, ec.symbol AS symbol, @@ -27,7 +39,8 @@ export const get: Handler = async (req, res) => { symbol: row.symbol })); - logger.info('Lens: Fetched USD conversion rates'); + await redisClient.set(cacheKey, JSON.stringify(result)); + logger.info('[Lens] Fetched USD conversion rates'); return res .status(200) diff --git a/apps/api/src/routes/lens/stats/profile/all.ts b/apps/api/src/routes/lens/stats/profile/all.ts index 2eb91a2c8472..ab1b805ce7b7 100644 --- a/apps/api/src/routes/lens/stats/profile/all.ts +++ b/apps/api/src/routes/lens/stats/profile/all.ts @@ -45,7 +45,7 @@ export const get: Handler = async (req, res) => { total_notifications: Number(notificationStats[0]?.total_notifications) }; - logger.info(`Lens: Fetched global profile stats for ${id}`); + logger.info(`[Lens] Fetched global profile stats for ${id}`); return res .status(200) diff --git a/apps/api/src/routes/meta.ts b/apps/api/src/routes/meta.ts index f76fdcb4fd0f..a2c7366fa177 100644 --- a/apps/api/src/routes/meta.ts +++ b/apps/api/src/routes/meta.ts @@ -7,7 +7,7 @@ import catchedError from 'src/helpers/catchedError'; import { SCORE_WORKER_URL } from 'src/helpers/constants'; import createClickhouseClient from 'src/helpers/createClickhouseClient'; import { rateLimiter } from 'src/helpers/middlewares/rateLimiter'; -import redisClient from 'src/helpers/redis'; +import redisClient from 'src/helpers/redisClient'; const measureQueryTime = async ( queryFunction: () => Promise diff --git a/apps/api/src/routes/misc/verified.ts b/apps/api/src/routes/misc/verified.ts index ab6bcbee6589..61953dd0a8a1 100644 --- a/apps/api/src/routes/misc/verified.ts +++ b/apps/api/src/routes/misc/verified.ts @@ -4,9 +4,21 @@ import logger from '@hey/helpers/logger'; import heyPg from 'src/db/heyPg'; import catchedError from 'src/helpers/catchedError'; import { CACHE_AGE_30_MINS, VERIFIED_FEATURE_ID } from 'src/helpers/constants'; +import redisClient from 'src/helpers/redisClient'; export const get: Handler = async (_, res) => { try { + const cacheKey = 'verified'; + const verifiedIds = await redisClient.get(cacheKey); + + if (verifiedIds) { + logger.info('(cached) Verified profiles fetched'); + return res + .status(200) + .setHeader('Cache-Control', CACHE_AGE_30_MINS) + .json({ result: JSON.parse(verifiedIds), success: true }); + } + const data = await heyPg.query( ` SELECT "profileId" @@ -18,6 +30,7 @@ export const get: Handler = async (_, res) => { ); const ids = data.map(({ profileId }) => profileId); + await redisClient.set(cacheKey, JSON.stringify(ids)); logger.info('Verified profiles fetched'); return res diff --git a/apps/api/src/routes/preferences/get.ts b/apps/api/src/routes/preferences/get.ts index 65a3cf918c15..7a92a0a9dd00 100644 --- a/apps/api/src/routes/preferences/get.ts +++ b/apps/api/src/routes/preferences/get.ts @@ -6,6 +6,7 @@ import parseJwt from '@hey/helpers/parseJwt'; import heyPg from 'src/db/heyPg'; import catchedError from 'src/helpers/catchedError'; import validateLensAccount from 'src/helpers/middlewares/validateLensAccount'; +import redisClient from 'src/helpers/redisClient'; import { noBody } from 'src/helpers/responses'; export const get = [ @@ -20,6 +21,16 @@ export const get = [ return noBody(res); } + const cacheKey = `preference:${id}`; + const cachedPreference = await redisClient.get(cacheKey); + + if (cachedPreference) { + logger.info(`(cached) Profile preferences fetched for ${id}`); + return res + .status(200) + .json({ result: JSON.parse(cachedPreference), success: true }); + } + const [preference, features, email, membershipNft] = await heyPg.multi( ` SELECT * FROM "Preference" WHERE id = $1; @@ -51,6 +62,7 @@ export const get = [ ) }; + await redisClient.set(cacheKey, JSON.stringify(response)); logger.info(`Profile preferences fetched for ${id}`); return res.status(200).json({ result: response, success: true }); diff --git a/apps/api/src/routes/preferences/update.ts b/apps/api/src/routes/preferences/update.ts index e12c588aa9e1..867738500c6b 100644 --- a/apps/api/src/routes/preferences/update.ts +++ b/apps/api/src/routes/preferences/update.ts @@ -5,6 +5,7 @@ import parseJwt from '@hey/helpers/parseJwt'; import catchedError from 'src/helpers/catchedError'; import validateLensAccount from 'src/helpers/middlewares/validateLensAccount'; import prisma from 'src/helpers/prisma'; +import redisClient from 'src/helpers/redisClient'; import { invalidBody, noBody } from 'src/helpers/responses'; import { boolean, number, object, string } from 'zod'; @@ -47,6 +48,7 @@ export const post = [ where: { id: payload.id } }); + await redisClient.del(`preference:${payload.id}`); logger.info(`Updated preferences for ${payload.id}`); return res.status(200).json({ result: data, success: true }); diff --git a/apps/api/src/routes/profile/get.ts b/apps/api/src/routes/profile/get.ts index 8118cf0d64a9..d69e88de05c0 100644 --- a/apps/api/src/routes/profile/get.ts +++ b/apps/api/src/routes/profile/get.ts @@ -5,6 +5,7 @@ import logger from '@hey/helpers/logger'; import heyPg from 'src/db/heyPg'; import catchedError from 'src/helpers/catchedError'; import { SUSPENDED_FEATURE_ID } from 'src/helpers/constants'; +import redisClient from 'src/helpers/redisClient'; import { noBody } from 'src/helpers/responses'; export const get: Handler = async (req, res) => { @@ -15,6 +16,16 @@ export const get: Handler = async (req, res) => { } try { + const cacheKey = `profile:${id}`; + const cachedProfile = await redisClient.get(cacheKey); + + if (cachedProfile) { + logger.info(`(cached) Profile details fetched for ${id}`); + return res + .status(200) + .json({ result: JSON.parse(cachedProfile), success: true }); + } + const [profileFeature, pinnedPublication] = await heyPg.multi( ` SELECT * FROM "ProfileFeature" @@ -30,6 +41,7 @@ export const get: Handler = async (req, res) => { pinnedPublication: pinnedPublication[0]?.publicationId || null }; + await redisClient.set(cacheKey, JSON.stringify(response)); logger.info(`Profile details fetched for ${id}`); return res.status(200).json({ result: response, success: true }); diff --git a/apps/api/src/routes/score/allocations.ts b/apps/api/src/routes/score/allocations.ts index b638d4ee13f3..d3d3c0d40503 100644 --- a/apps/api/src/routes/score/allocations.ts +++ b/apps/api/src/routes/score/allocations.ts @@ -31,7 +31,7 @@ export const get: Handler = async (req, res) => { })); logger.info( - `Lens: Fetched ${adjustedProfileScore.length} allocations for ${id}` + `[Lens] Fetched ${adjustedProfileScore.length} allocations for ${id}` ); return res.status(200).json({ result, success: true }); diff --git a/apps/api/src/routes/score/get.ts b/apps/api/src/routes/score/get.ts index d140f0f54b91..6199fc9c6738 100644 --- a/apps/api/src/routes/score/get.ts +++ b/apps/api/src/routes/score/get.ts @@ -78,7 +78,7 @@ export const get: Handler = async (req, res) => { new Date() < cachedProfileData.expiresAt ) { logger.info( - `Lens: Fetched profile score from cache for ${id} - ${cachedProfileData.score}` + `[Lens] Fetched profile score from cache for ${id} - ${cachedProfileData.score}` ); return res .status(200) @@ -117,7 +117,7 @@ export const get: Handler = async (req, res) => { ); logger.info( - `Lens: Fetched profile score for ${id} - ${newCachedProfile[0]?.score} - Expires at: ${newCachedProfile[0]?.expiresAt}` + `[Lens] Fetched profile score for ${id} - ${newCachedProfile[0]?.score} - Expires at: ${newCachedProfile[0]?.expiresAt}` ); return res diff --git a/apps/api/src/routes/sitemap/others.xml.ts b/apps/api/src/routes/sitemap/others.xml.ts index 822f7bff1d66..918ac96c1aa3 100644 --- a/apps/api/src/routes/sitemap/others.xml.ts +++ b/apps/api/src/routes/sitemap/others.xml.ts @@ -21,7 +21,7 @@ export const get: Handler = (req, res) => { const entries = sitemaps.map((sitemap) => ({ loc: sitemap })); const xml = buildUrlsetXml(entries); - logger.info(`Lens: Fetched other sitemaps from user-agent: ${user_agent}`); + logger.info(`[Lens] Fetched other sitemaps from user-agent: ${user_agent}`); return res.status(200).setHeader('Content-Type', 'text/xml').send(xml); } catch (error) { diff --git a/apps/api/src/routes/sitemap/profiles.xml.ts b/apps/api/src/routes/sitemap/profiles.xml.ts index 9bf6c820caca..ac0b0cfbf8b2 100644 --- a/apps/api/src/routes/sitemap/profiles.xml.ts +++ b/apps/api/src/routes/sitemap/profiles.xml.ts @@ -27,7 +27,7 @@ export const get: Handler = async (req, res) => { const xml = buildSitemapXml(entries); logger.info( - `Lens: Fetched all profiles sitemap index having ${totalBatches} batches from user-agent: ${user_agent}` + `[Lens] Fetched all profiles sitemap index having ${totalBatches} batches from user-agent: ${user_agent}` ); return res.status(200).setHeader('Content-Type', 'text/xml').send(xml); diff --git a/apps/api/src/routes/sitemap/profiles/[id].xml.ts b/apps/api/src/routes/sitemap/profiles/[id].xml.ts index 81ea32d4ff18..8950a4d9e5c5 100644 --- a/apps/api/src/routes/sitemap/profiles/[id].xml.ts +++ b/apps/api/src/routes/sitemap/profiles/[id].xml.ts @@ -49,7 +49,7 @@ export const get: Handler = async (req, res) => { const xml = buildUrlsetXml(entries); logger.info( - `Lens: Fetched profiles sitemap for batch ${batch} having ${response.length} entries from user-agent: ${user_agent}` + `[Lens] Fetched profiles sitemap for batch ${batch} having ${response.length} entries from user-agent: ${user_agent}` ); return res.status(200).setHeader('Content-Type', 'text/xml').send(xml); diff --git a/apps/api/src/routes/sitemap/sitemap.xml.ts b/apps/api/src/routes/sitemap/sitemap.xml.ts index ccab5d52545c..057f0d32a2a6 100644 --- a/apps/api/src/routes/sitemap/sitemap.xml.ts +++ b/apps/api/src/routes/sitemap/sitemap.xml.ts @@ -19,7 +19,7 @@ export const get: Handler = (req, res) => { const xml = buildSitemapXml(entries); logger.info( - `Lens: Fetched all sitemaps index from user-agent: ${user_agent}` + `[Lens] Fetched all sitemaps index from user-agent: ${user_agent}` ); return res.status(200).setHeader('Content-Type', 'text/xml').send(xml); diff --git a/apps/api/src/routes/tokens/all.ts b/apps/api/src/routes/tokens/all.ts index d15ada3d8fd3..048abf4378b0 100644 --- a/apps/api/src/routes/tokens/all.ts +++ b/apps/api/src/routes/tokens/all.ts @@ -4,15 +4,28 @@ import logger from '@hey/helpers/logger'; import heyPg from 'src/db/heyPg'; import catchedError from 'src/helpers/catchedError'; import { CACHE_AGE_1_DAY } from 'src/helpers/constants'; +import redisClient from 'src/helpers/redisClient'; export const get: Handler = async (_, res) => { try { + const cacheKey = 'allowedTokens'; + const cachedTokens = await redisClient.get(cacheKey); + + if (cachedTokens) { + logger.info('(cached) All tokens fetched'); + return res + .status(200) + .setHeader('Cache-Control', CACHE_AGE_1_DAY) + .json({ result: JSON.parse(cachedTokens), success: true }); + } + const data = await heyPg.query(` SELECT * FROM "AllowedToken" ORDER BY priority DESC; `); + await redisClient.set(cacheKey, JSON.stringify(data)); logger.info('All tokens fetched'); return res diff --git a/apps/web/src/components/Staff/Users/Overview/Tool/index.tsx b/apps/web/src/components/Staff/Users/Overview/Tool/index.tsx index 4e44612c4b7f..d39496125f5c 100644 --- a/apps/web/src/components/Staff/Users/Overview/Tool/index.tsx +++ b/apps/web/src/components/Staff/Users/Overview/Tool/index.tsx @@ -42,7 +42,7 @@ const ProfileStaffTool: FC = ({ profile }) => { const { data: preferences } = useQuery({ queryFn: getPreferences, - queryKey: ['getPreferences', profile.id || ''] + queryKey: ['getInternalPreferences', profile.id || ''] }); return (