Hey is a decentralized and permissionless social media app built with Lens Protocol 🌿

chore: update Redis dependencies and configuration in the API

Yoginth 5602b29f 96c35a81

+213 -18
+1
apps/api/.env.example
··· 1 1 NEXT_PUBLIC_LENS_NETWORK="testnet" 2 2 DATABASE_URL="" 3 + REDIS_URL="" 3 4 PRIVATE_KEY="0x1d65a3183f35ecef73ce8f7d47920d58abdf3766debc2ff0b4c653b7633707fd" # Testnet private key without funds 4 5 EVER_ACCESS_KEY="" 5 6 EVER_ACCESS_SECRET=""
+1
apps/api/env.d.ts
··· 2 2 interface ProcessEnv { 3 3 NEXT_PUBLIC_LENS_NETWORK: string; 4 4 DATABASE_URL: string; 5 + REDIS_URL: string; 5 6 PRIVATE_KEY: string; 6 7 EVER_ACCESS_KEY: string; 7 8 EVER_ACCESS_SECRET: string;
+1
apps/api/package.json
··· 29 29 "hono-rate-limiter": "^0.4.2", 30 30 "jose": "^6.0.10", 31 31 "linkedom": "^0.18.9", 32 + "redis": "^4.7.0", 32 33 "tsx": "^4.19.3", 33 34 "viem": "^2.26.3", 34 35 "xmlbuilder2": "^3.1.1",
apps/api/src/helpers/constants.ts apps/api/src/utils/constants.ts
apps/api/src/helpers/heyWalletClient.ts apps/api/src/utils/heyWalletClient.ts
+1 -6
apps/api/src/middlewares/rateLimiter.ts
··· 1 - import { createHash } from "node:crypto"; 2 - 3 1 import { rateLimiter as rateLimit } from "hono-rate-limiter"; 2 + import sha256 from "src/utils/sha256"; 4 3 5 4 const getIp = (req: Request): string => { 6 5 const ips = ( ··· 11 10 ).split(","); 12 11 13 12 return ips[0].trim(); 14 - }; 15 - 16 - const sha256 = (text: string): string => { 17 - return createHash("sha256").update(text).digest("hex"); 18 13 }; 19 14 20 15 const hashedIp = (req: Request): string => sha256(getIp(req)).slice(0, 25);
+1 -4
apps/api/src/routes/lens/lensAuthorization.ts
··· 1 1 import type { Context } from "hono"; 2 - import { 3 - CACHE_AGE_1_DAY, 4 - VERIFICATION_ENDPOINT 5 - } from "../../helpers/constants"; 2 + import { CACHE_AGE_1_DAY, VERIFICATION_ENDPOINT } from "src/utils/constants"; 6 3 7 4 const lensAuthorization = async (ctx: Context) => { 8 5 ctx.header("Cache-Control", CACHE_AGE_1_DAY);
+1 -1
apps/api/src/routes/lens/lensVerification.ts
··· 1 1 import { HEY_APP } from "@hey/data/constants"; 2 2 import { Errors } from "@hey/data/errors"; 3 3 import type { Context } from "hono"; 4 + import { heyWalletClient } from "src/utils/heyWalletClient"; 4 5 import { type Address, checksumAddress } from "viem"; 5 - import { heyWalletClient } from "../../helpers/heyWalletClient"; 6 6 7 7 const TYPES = { 8 8 SourceStamp: [
+18 -5
apps/api/src/routes/oembed/getOembed.ts
··· 1 1 import { Errors } from "@hey/data/errors"; 2 2 import type { Context } from "hono"; 3 - import { CACHE_AGE_1_DAY } from "../../helpers/constants"; 3 + import { CACHE_AGE_1_DAY } from "src/utils/constants"; 4 + import { getRedis, setRedis } from "src/utils/redis"; 5 + import sha256 from "src/utils/sha256"; 4 6 import getMetadata from "./helpers/getMetadata"; 5 7 6 8 const getOembed = async (ctx: Context) => { 7 9 try { 8 10 const { url } = ctx.req.query(); 11 + const cacheKey = `oembed:${sha256(url)}`; 12 + const cachedValue = await getRedis(cacheKey); 13 + 14 + if (cachedValue) { 15 + return ctx.json({ 16 + success: true, 17 + cached: true, 18 + data: JSON.parse(cachedValue) 19 + }); 20 + } 21 + 22 + const oembed = await getMetadata(url); 23 + await setRedis(cacheKey, JSON.stringify(oembed)); 24 + 9 25 ctx.header("Cache-Control", CACHE_AGE_1_DAY); 10 - return ctx.json({ 11 - success: true, 12 - data: await getMetadata(url) 13 - }); 26 + return ctx.json({ success: true, data: oembed }); 14 27 } catch { 15 28 return ctx.json({ success: false, error: Errors.SomethingWentWrong }, 500); 16 29 }
+1 -1
apps/api/src/routes/oembed/helpers/getMetadata.ts
··· 1 1 import { parseHTML } from "linkedom"; 2 - import { HEY_USER_AGENT } from "../../../helpers/constants"; 2 + import { HEY_USER_AGENT } from "src/utils/constants"; 3 3 import getDescription from "./meta/getDescription"; 4 4 import getTitle from "./meta/getTitle"; 5 5
+93
apps/api/src/utils/redis.ts
··· 1 + import dotenv from "dotenv"; 2 + import type { RedisClientType } from "redis"; 3 + import { createClient } from "redis"; 4 + 5 + dotenv.config({ override: true }); 6 + 7 + const noRedisError = () => 8 + console.error("[Redis] Redis client not initialized"); 9 + 10 + let redisClient: null | RedisClientType = null; 11 + 12 + if (process.env.REDIS_URL) { 13 + redisClient = createClient({ url: process.env.REDIS_URL }); 14 + 15 + redisClient.on("connect", () => console.info("[Redis] Redis connect")); 16 + redisClient.on("ready", () => console.info("[Redis] Redis ready")); 17 + redisClient.on("reconnecting", (err) => 18 + console.error("[Redis] Redis reconnecting", err) 19 + ); 20 + redisClient.on("error", (err) => console.error("[Redis] Redis error", err)); 21 + redisClient.on("end", (err) => console.error("[Redis] Redis end", err)); 22 + 23 + const connectRedis = async () => { 24 + console.info("[Redis] Connecting to Redis"); 25 + await redisClient?.connect(); 26 + }; 27 + 28 + connectRedis().catch((error) => 29 + console.error("[Redis] Connection error", error) 30 + ); 31 + } else { 32 + console.info("[Redis] REDIS_URL not set"); 33 + } 34 + 35 + const randomNumber = (min: number, max: number): number => { 36 + return Math.floor(Math.random() * (max - min) + min); 37 + }; 38 + 39 + const hoursToSeconds = (hours: number): number => { 40 + return hours * 60 * 60; 41 + }; 42 + 43 + // Generates a random expiry time between 1 and 3 hours 44 + export const generateMediumExpiry = (): number => { 45 + return randomNumber(hoursToSeconds(1), hoursToSeconds(3)); 46 + }; 47 + 48 + // Generates a random expiry time between 4 and 8 hours 49 + export const generateLongExpiry = (): number => { 50 + return randomNumber(hoursToSeconds(4), hoursToSeconds(8)); 51 + }; 52 + 53 + // Generates a random expiry time between 9 and 24 hours 54 + const generateExtraLongExpiry = (): number => { 55 + return randomNumber(hoursToSeconds(9), hoursToSeconds(24)); 56 + }; 57 + 58 + export const generateForeverExpiry = (): number => { 59 + return hoursToSeconds(5000000); 60 + }; 61 + 62 + export const setRedis = async ( 63 + key: string, 64 + value: boolean | number | Record<string, any> | string, 65 + expiry = generateExtraLongExpiry() 66 + ) => { 67 + if (!redisClient) { 68 + noRedisError(); 69 + return; 70 + } 71 + 72 + const redisValue = typeof value !== "string" ? JSON.stringify(value) : value; 73 + 74 + return await redisClient.set(key, redisValue, { EX: expiry }); 75 + }; 76 + 77 + export const getRedis = async (key: string) => { 78 + if (!redisClient) { 79 + noRedisError(); 80 + return null; 81 + } 82 + return await redisClient.get(key); 83 + }; 84 + 85 + export const delRedis = async (key: string) => { 86 + if (!redisClient) { 87 + noRedisError(); 88 + return; 89 + } 90 + await redisClient.del(key); 91 + }; 92 + 93 + export default redisClient;
+7
apps/api/src/utils/sha256.ts
··· 1 + import { createHash } from "node:crypto"; 2 + 3 + const sha256 = (text: string): string => { 4 + return createHash("sha256").update(text).digest("hex"); 5 + }; 6 + 7 + export default sha256;
+1 -1
apps/web/src/components/Shared/Sidebar/WhoToFollow.tsx
··· 1 + import Suggested from "@/components/Home/Suggested"; 1 2 import DismissRecommendedAccount from "@/components/Shared/Account/DismissRecommendedAccount"; 2 3 import SingleAccount from "@/components/Shared/Account/SingleAccount"; 3 4 import SingleAccountShimmer from "@/components/Shared/Shimmer/SingleAccountShimmer"; ··· 9 10 useAccountRecommendationsQuery 10 11 } from "@hey/indexer"; 11 12 import { useState } from "react"; 12 - import Suggested from "../../Home/Suggested"; 13 13 14 14 const Title = () => <H5>Who to Follow</H5>; 15 15
+87
pnpm-lock.yaml
··· 59 59 linkedom: 60 60 specifier: ^0.18.9 61 61 version: 0.18.9 62 + redis: 63 + specifier: ^4.7.0 64 + version: 4.7.0 62 65 tsx: 63 66 specifier: ^4.19.3 64 67 version: 4.19.3 ··· 2457 2460 peerDependencies: 2458 2461 react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 2459 2462 2463 + '@redis/bloom@1.2.0': 2464 + resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==} 2465 + peerDependencies: 2466 + '@redis/client': ^1.0.0 2467 + 2468 + '@redis/client@1.6.0': 2469 + resolution: {integrity: sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==} 2470 + engines: {node: '>=14'} 2471 + 2472 + '@redis/graph@1.1.1': 2473 + resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==} 2474 + peerDependencies: 2475 + '@redis/client': ^1.0.0 2476 + 2477 + '@redis/json@1.0.7': 2478 + resolution: {integrity: sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==} 2479 + peerDependencies: 2480 + '@redis/client': ^1.0.0 2481 + 2482 + '@redis/search@1.2.0': 2483 + resolution: {integrity: sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==} 2484 + peerDependencies: 2485 + '@redis/client': ^1.0.0 2486 + 2487 + '@redis/time-series@1.1.0': 2488 + resolution: {integrity: sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==} 2489 + peerDependencies: 2490 + '@redis/client': ^1.0.0 2491 + 2460 2492 '@repeaterjs/repeater@3.0.6': 2461 2493 resolution: {integrity: sha512-Javneu5lsuhwNCryN+pXH93VPQ8g0dBX7wItHFgYiwQmzE1sVdg5tWHiOgHywzL2W21XQopa7IwIEnNbmeUJYA==} 2462 2494 ··· 3453 3485 resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} 3454 3486 engines: {node: '>=6'} 3455 3487 3488 + cluster-key-slot@1.1.2: 3489 + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} 3490 + engines: {node: '>=0.10.0'} 3491 + 3456 3492 color-convert@2.0.1: 3457 3493 resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 3458 3494 engines: {node: '>=7.0.0'} ··· 3902 3938 3903 3939 function-bind@1.1.2: 3904 3940 resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 3941 + 3942 + generic-pool@3.9.0: 3943 + resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} 3944 + engines: {node: '>= 4'} 3905 3945 3906 3946 gensync@1.0.0-beta.2: 3907 3947 resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} ··· 5266 5306 resolution: {integrity: sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==} 5267 5307 engines: {node: '>= 12.13.0'} 5268 5308 5309 + redis@4.7.0: 5310 + resolution: {integrity: sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==} 5311 + 5269 5312 regenerator-runtime@0.14.1: 5270 5313 resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} 5271 5314 ··· 6113 6156 yallist@3.1.1: 6114 6157 resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} 6115 6158 6159 + yallist@4.0.0: 6160 + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 6161 + 6116 6162 yaml-ast-parser@0.0.43: 6117 6163 resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} 6118 6164 ··· 9155 9201 dependencies: 9156 9202 react: 19.1.0 9157 9203 9204 + '@redis/bloom@1.2.0(@redis/client@1.6.0)': 9205 + dependencies: 9206 + '@redis/client': 1.6.0 9207 + 9208 + '@redis/client@1.6.0': 9209 + dependencies: 9210 + cluster-key-slot: 1.1.2 9211 + generic-pool: 3.9.0 9212 + yallist: 4.0.0 9213 + 9214 + '@redis/graph@1.1.1(@redis/client@1.6.0)': 9215 + dependencies: 9216 + '@redis/client': 1.6.0 9217 + 9218 + '@redis/json@1.0.7(@redis/client@1.6.0)': 9219 + dependencies: 9220 + '@redis/client': 1.6.0 9221 + 9222 + '@redis/search@1.2.0(@redis/client@1.6.0)': 9223 + dependencies: 9224 + '@redis/client': 1.6.0 9225 + 9226 + '@redis/time-series@1.1.0(@redis/client@1.6.0)': 9227 + dependencies: 9228 + '@redis/client': 1.6.0 9229 + 9158 9230 '@repeaterjs/repeater@3.0.6': {} 9159 9231 9160 9232 '@rollup/rollup-android-arm-eabi@4.40.0': ··· 10617 10689 10618 10690 clsx@2.1.1: {} 10619 10691 10692 + cluster-key-slot@1.1.2: {} 10693 + 10620 10694 color-convert@2.0.1: 10621 10695 dependencies: 10622 10696 color-name: 1.1.4 ··· 11065 11139 optional: true 11066 11140 11067 11141 function-bind@1.1.2: {} 11142 + 11143 + generic-pool@3.9.0: {} 11068 11144 11069 11145 gensync@1.0.0-beta.2: {} 11070 11146 ··· 12664 12740 12665 12741 real-require@0.1.0: {} 12666 12742 12743 + redis@4.7.0: 12744 + dependencies: 12745 + '@redis/bloom': 1.2.0(@redis/client@1.6.0) 12746 + '@redis/client': 1.6.0 12747 + '@redis/graph': 1.1.1(@redis/client@1.6.0) 12748 + '@redis/json': 1.0.7(@redis/client@1.6.0) 12749 + '@redis/search': 1.2.0(@redis/client@1.6.0) 12750 + '@redis/time-series': 1.1.0(@redis/client@1.6.0) 12751 + 12667 12752 regenerator-runtime@0.14.1: {} 12668 12753 12669 12754 regex-recursion@6.0.2: ··· 13497 13582 y18n@5.0.8: {} 13498 13583 13499 13584 yallist@3.1.1: {} 13585 + 13586 + yallist@4.0.0: {} 13500 13587 13501 13588 yaml-ast-parser@0.0.43: {} 13502 13589