Skip to content

Commit

Permalink
Merge pull request #683 from dappforce/deploy/cache
Browse files Browse the repository at this point in the history
Add placeholder datas and Cache profile query
  • Loading branch information
teodorus-nathaniel authored Jul 4, 2024
2 parents caef18e + 40a4263 commit eccebf4
Show file tree
Hide file tree
Showing 12 changed files with 248 additions and 34 deletions.
4 changes: 2 additions & 2 deletions src/modules/points/PointsWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ const UserStatsSection = ({
<div className='flex items-center justify-between gap-2'>
<div className='flex items-center gap-2'>
<AddressAvatar address={myAddress ?? ''} className='h-16 w-16' />
<div className='flex flex-col gap-2'>
<div className='flex flex-col gap-1'>
<div className='flex items-center gap-3'>
<Name
address={myAddress ?? ''}
Expand All @@ -244,7 +244,7 @@ const UserStatsSection = ({
</div>
<LinkText
variant='primary'
className='flex items-center gap-1 hover:no-underline focus:no-underline'
className='flex items-center gap-2 hover:no-underline focus:no-underline'
>
<IoIosStats /> See the Leaderboard
</LinkText>
Expand Down
125 changes: 125 additions & 0 deletions src/pages/api/profiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { redisCallWrapper } from '@/server/cache'
import { ApiResponse, handlerWrapper } from '@/server/common'
import {
SubsocialProfile,
getProfiles,
} from '@/services/datahub/profiles/fetcher'
import { parseJSONData } from '@/utils/strings'
import { NextApiRequest, NextApiResponse } from 'next'
import { z } from 'zod'

const querySchema = z.object({
addresses: z.array(z.string()).or(z.string()),
})
export type ApiProfilesParams = z.infer<typeof querySchema>

const bodySchema = z.object({
address: z.string(),
})
export type ApiProfilesInvalidationBody = z.infer<typeof bodySchema>

type ResponseData = {
data?: SubsocialProfile[]
}
export type ApiProfilesResponse = ApiResponse<ResponseData>
export type ApiProfilesInvalidationResponse = ApiResponse<{}>

const INVALIDATED_MAX_AGE = 1 * 60 // 1 minute
const getInvalidatedProfileRedisKey = (id: string) => {
return `profiles-invalidated:${id}`
}

const GET_handler = handlerWrapper({
inputSchema: querySchema,
dataGetter: (req: NextApiRequest) => req.query,
})<ResponseData>({
errorLabel: 'profiles',
allowedMethods: ['GET'],
handler: async (data, _, res) => {
const addresses = Array.isArray(data.addresses)
? data.addresses
: [data.addresses]
const profiles = await getProfilesServer(addresses)
return res
.status(200)
.send({ success: true, message: 'OK', data: profiles })
},
})

const POST_handler = handlerWrapper({
inputSchema: bodySchema,
dataGetter: (req: NextApiRequest) => req.body,
})<{}>({
errorLabel: 'posts',
allowedMethods: ['POST'],
handler: async (data, _, res) => {
redisCallWrapper(async (redis) => {
return redis?.set(
getInvalidatedProfileRedisKey(data.address),
data.address,
'EX',
INVALIDATED_MAX_AGE
)
})

return res.status(200).send({ success: true, message: 'OK' })
},
})

export default function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'GET') {
return GET_handler(req, res)
} else if (req.method === 'POST') {
return POST_handler(req, res)
}
}

const PROFILE_MAX_AGE = 5 * 60 // 5 minutes
const getProfileRedisKey = (id: string) => {
return `profiles:${id}`
}
export async function getProfilesServer(
addresses: string[]
): Promise<SubsocialProfile[]> {
if (addresses.length === 0) return []

const profiles: SubsocialProfile[] = []
const needToFetch: string[] = []
const promises = addresses.map(async (address) => {
redisCallWrapper(async (redis) => {
const [profile, isInvalidated] = await Promise.all([
redis?.get(getProfileRedisKey(address)),
redis?.get(getInvalidatedProfileRedisKey(address)),
] as const)
const parsed =
profile && parseJSONData<{ data: SubsocialProfile | null }>(profile)
if (parsed && !isInvalidated) {
if (parsed.data) profiles.push(parsed.data)
// if null, we don't need to fetch it
} else {
needToFetch.push(address)
}
})
})
await Promise.allSettled(promises)

const fetchedProfiles = await getProfiles(needToFetch)
const profilesMap = new Map<string, SubsocialProfile>()
fetchedProfiles.forEach(async (profile) => {
profilesMap.set(profile.address, profile)
})

needToFetch.map((address) => {
const profile = profilesMap.get(address) ?? null
redisCallWrapper(async (redis) => {
await redis?.set(
getProfileRedisKey(address),
JSON.stringify({ data: profile }),
'EX',
PROFILE_MAX_AGE
)
})
})

return [...profiles, ...fetchedProfiles]
}
30 changes: 24 additions & 6 deletions src/pages/tg/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,30 @@ import { env } from '@/env.mjs'
import MemesPage from '@/modules/telegram/MemesPage'
import { AppCommonProps } from '@/pages/_app'
import { prefetchBlockedEntities } from '@/server/moderation/prefetch'
import { getPostQuery } from '@/services/api/query'
import { getPaginatedPostIdsByPostId } from '@/services/datahub/posts/query'
import { getProfileQuery } from '@/services/datahub/profiles/query'
import { getCommonStaticProps } from '@/utils/page'
import { QueryClient, dehydrate } from '@tanstack/react-query'
import { getProfilesServer } from '../api/profiles'

async function prefetchChatData(client: QueryClient) {
const firstPageData = await getPaginatedPostIdsByPostId.fetchFirstPageQuery(
client,
env.NEXT_PUBLIC_MAIN_CHAT_ID,
1
)
const ownerIds = firstPageData.data
.map((id) => {
const post = getPostQuery.getQueryData(client, id)
return post?.struct.ownerId
})
.filter(Boolean)
const profiles = await getProfilesServer(ownerIds)
profiles.forEach((profile) => {
getProfileQuery.setQueryData(client, profile.address, profile)
})
}

export const getStaticProps = getCommonStaticProps<AppCommonProps>(
() => ({
Expand All @@ -17,17 +38,14 @@ export const getStaticProps = getCommonStaticProps<AppCommonProps>(
async () => {
const client = new QueryClient()
await Promise.all([
getPaginatedPostIdsByPostId.fetchFirstPageQuery(
client,
env.NEXT_PUBLIC_MAIN_CHAT_ID,
1
),
prefetchChatData(client),
prefetchBlockedEntities(
client,
[env.NEXT_PUBLIC_MAIN_SPACE_ID].filter(Boolean),
[env.NEXT_PUBLIC_MAIN_CHAT_ID].filter(Boolean)
),
])
] as const)

getPaginatedPostIdsByPostId.invalidateFirstQuery(
client,
env.NEXT_PUBLIC_MAIN_CHAT_ID
Expand Down
6 changes: 5 additions & 1 deletion src/services/datahub/balances/subscription.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { gql } from 'graphql-request'
import { useRouter } from 'next/router'
import { useEffect, useRef } from 'react'
import { SubscribeBalancesSubscription } from '../generated-query'
import { getBalanceQuery } from '../leaderboard/points-balance/query'
import {
getBalanceQuery,
getMyBalanceCache,
} from '../leaderboard/points-balance/query'
import { datahubSubscription } from '../utils'

export function useDatahubBalancesSubscriber() {
Expand Down Expand Up @@ -95,4 +98,5 @@ async function processSubscriptionEvent(
mainAddress,
Number(eventData.entity.activeStakingPoints)
)
getMyBalanceCache.set(eventData.entity.activeStakingPoints)
}
18 changes: 15 additions & 3 deletions src/services/datahub/content-staking/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { ApiDatahubSuperLikeGetResponse } from '@/pages/api/datahub/super-like'
import { apiInstance } from '@/services/api/utils'
import { getSubIdRequest } from '@/services/external'
import { createQuery, poolQuery } from '@/subsocial-query'
import { LocalStorage } from '@/utils/storage'
import { parseJSONData } from '@/utils/strings'
import { AxiosResponse } from 'axios'
import dayjs from 'dayjs'
import { gql } from 'graphql-request'
Expand Down Expand Up @@ -645,19 +647,29 @@ export const GET_TOKENOMICS_METADATA = gql`
}
}
`
const getTokenomicsMetadataCache = new LocalStorage(
() => 'tokenomics-metadata-cache'
)
export async function getTokenomicsMetadata() {
const res = await datahubQueryRequest<
GetTokenomicsMetadataQuery,
GetTokenomicsMetadataQueryVariables
>({
document: GET_TOKENOMICS_METADATA,
})
getTokenomicsMetadataCache.set(
JSON.stringify(res.activeStakingTokenomicMetadata)
)
return res.activeStakingTokenomicMetadata
}
export const getTokenomicsMetadataQuery = createQuery({
key: 'getTokenomicsMetadata',
fetcher: getTokenomicsMetadata,
defaultConfigGenerator: () => ({
enabled: true,
}),
defaultConfigGenerator: () => {
const cache = getTokenomicsMetadataCache.get()
return {
placeholderData:
parseJSONData<Awaited<ReturnType<typeof getTokenomicsMetadata>>>(cache),
}
},
})
29 changes: 25 additions & 4 deletions src/services/datahub/identity/query.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
import { apiInstance } from '@/services/api/utils'
import { useMyAccount } from '@/stores/my-account'
import { createQuery } from '@/subsocial-query'
import { LocalStorage } from '@/utils/storage'
import { parseJSONData } from '@/utils/strings'
import { getLinkedIdentity } from './fetcher'

export const getMyLinkedIdentityCache = new LocalStorage(
() => 'my-linked-identity-cache'
)
export const getLinkedIdentityQuery = createQuery({
key: 'getLinkedIdentity',
fetcher: (address: string) => getLinkedIdentity({ sessionAddress: address }),
defaultConfigGenerator: (data) => ({
enabled: !!data,
}),
fetcher: async (address: string) => {
const res = await getLinkedIdentity({ sessionAddress: address })
if (address === useMyAccount.getState().address) {
getMyLinkedIdentityCache.set(JSON.stringify(res))
}
return res
},
defaultConfigGenerator: (data) => {
let cache: Awaited<ReturnType<typeof getLinkedIdentity>> | undefined =
undefined
if (data === useMyAccount.getState().address) {
const cacheData = getMyLinkedIdentityCache.get()
cache = parseJSONData(cacheData)
}
return {
enabled: !!data,
placeholderData: cache,
}
},
})

export const getLinkedIdentityFromMainAddressQuery = createQuery({
Expand Down
10 changes: 7 additions & 3 deletions src/services/datahub/identity/subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
SubscribeIdentitySubscription,
} from '../generated-query'
import { datahubSubscription, isDatahubAvailable } from '../utils'
import { getLinkedIdentityQuery } from './query'
import { Identity } from './fetcher'
import { getLinkedIdentityQuery, getMyLinkedIdentityCache } from './query'

export function useDatahubIdentitySubscriber() {
const queryClient = useQueryClient()
Expand Down Expand Up @@ -134,15 +135,18 @@ async function processSessionCreated(
session: SubscribeIdentitySubscription['linkedIdentitySubscription']['entity']['session']
) {
if (!session?.id) return
getLinkedIdentityQuery.setQueryData(queryClient, session.id, {
const newIdentity: Identity = {
mainAddress: session.linkedIdentity.id,
externalProviders:
session.linkedIdentity.externalProviders?.map((p) => ({
externalId: p.externalId,
provider: p.provider,
username: p.username,
})) ?? [],
})
}
if (session.id === useMyAccount.getState().address)
getMyLinkedIdentityCache.set(JSON.stringify(newIdentity))
getLinkedIdentityQuery.setQueryData(queryClient, session.id, newIdentity)
}

async function processExternalProviderUpdate(
Expand Down
2 changes: 2 additions & 0 deletions src/services/datahub/leaderboard/points-balance/optimistic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
FULL_ENERGY_VALUE,
getBalanceQuery,
getEnergyStateQuery,
getMyBalanceCache,
} from './query'

dayjs.extend(utc)
Expand All @@ -24,6 +25,7 @@ export const increasePointsBalance = ({
getBalanceQuery.setQueryData(client, address, (oldData) => {
if (!oldData) return oldData

getMyBalanceCache.set((oldData + pointsByClick).toString())
return oldData + pointsByClick
})
}
Expand Down
24 changes: 19 additions & 5 deletions src/services/datahub/leaderboard/points-balance/query.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { getMyMainAddress } from '@/stores/my-account'
import { createQuery } from '@/subsocial-query'
import { LocalStorage } from '@/utils/storage'
import { gql } from 'graphql-request'
import {
GetBalanceQuery,
Expand All @@ -15,6 +17,7 @@ const GET_BALANCE = gql`
}
}
`
export const getMyBalanceCache = new LocalStorage(() => 'my-balance-cache')
async function getBalance(address: string): Promise<number> {
const res = await datahubQueryRequest<
GetBalanceQuery,
Expand All @@ -24,15 +27,26 @@ async function getBalance(address: string): Promise<number> {
variables: { address },
})

return Number(res.socialProfileBalances?.activeStakingPoints ?? 0) || 0
const balance =
Number(res.socialProfileBalances?.activeStakingPoints ?? 0) || 0
if (address === getMyMainAddress()) {
getMyBalanceCache.set(balance + '')
}
return balance
}

export const getBalanceQuery = createQuery({
key: 'getBalance',
fetcher: getBalance,
defaultConfigGenerator: (address) => ({
enabled: !!address,
}),
defaultConfigGenerator: (address) => {
const cache =
getMyMainAddress() === address
? Number(getMyBalanceCache.get())
: undefined
return {
enabled: !!address,
placeholderData: cache || undefined,
}
},
})

const GET_ENERGY_STATE = gql`
Expand Down
Loading

0 comments on commit eccebf4

Please sign in to comment.