返回 AiToEarn
workAnalyticsCacheStore.ts
根目录 / project / aitoearn-web / src / app / [lng] / accounts / workAnalyticsCacheStore.ts
1 /**
2 * workAnalyticsCacheStore - Accounts page channel work analytics cache.
3 * Caches analytics responses for 3 hours and keeps backend fetchedAt as the data update time.
4 */
5
6 import type { ChannelWorkAnalyticsVo } from '@/api/channels/channel.types'
7 import type { PlatType } from '@/app/config/platConfig'
8 import { getChannelWorkAnalyticsApi } from '@/api/channels/channel.api'
9 import { createPersistStore } from '@/utils/storage/createPersistStore'
10
11 const WORK_ANALYTICS_CACHE_TTL_MS = 3 * 60 * 60 * 1000
12
13 interface WorkAnalyticsCacheRecord {
14 data: ChannelWorkAnalyticsVo
15 cachedAt: number
16 }
17
18 interface WorkAnalyticsCacheState {
19 cache: Record<string, WorkAnalyticsCacheRecord>
20 }
21
22 const initialState: WorkAnalyticsCacheState = {
23 cache: {},
24 }
25
26 const pendingRequests = new Map<string, Promise<ChannelWorkAnalyticsVo | null>>()
27
28 function createWorkAnalyticsCacheKey(platform: PlatType, platformWorkId: string, accountId: string) {
29 return [platform, accountId, platformWorkId].map(encodeURIComponent).join(':')
30 }
31
32 function isCacheFresh(record?: WorkAnalyticsCacheRecord) {
33 return !!record && Date.now() - record.cachedAt < WORK_ANALYTICS_CACHE_TTL_MS
34 }
35
36 export const useAccountsWorkAnalyticsCacheStore = createPersistStore(
37 { ...initialState },
38 (set, get) => {
39 const methods = {
40 getAnalytics(platform: PlatType, platformWorkId: string, accountId: string) {
41 const key = createWorkAnalyticsCacheKey(platform, platformWorkId, accountId)
42 return methods.getAnalyticsByKey(key)
43 },
44
45 getAnalyticsByKey(key: string) {
46 const record = get().cache[key]
47 if (!record)
48 return null
49
50 if (!isCacheFresh(record)) {
51 methods.clearAnalyticsByKey(key)
52 return null
53 }
54
55 return record.data
56 },
57
58 setAnalyticsByKey(key: string, data: ChannelWorkAnalyticsVo) {
59 set(state => ({
60 cache: {
61 ...state.cache,
62 [key]: {
63 data,
64 cachedAt: Date.now(),
65 },
66 },
67 }))
68 },
69
70 clearAnalyticsByKey(key: string) {
71 set((state) => {
72 const cache = { ...state.cache }
73 delete cache[key]
74 return { cache }
75 })
76 },
77
78 clearExpiredAnalytics() {
79 const now = Date.now()
80 set((state) => {
81 const cache = Object.fromEntries(
82 Object.entries(state.cache).filter(([, record]) => now - record.cachedAt < WORK_ANALYTICS_CACHE_TTL_MS),
83 )
84 return { cache }
85 })
86 },
87
88 async fetchAnalytics(platform: PlatType, platformWorkId: string, accountId: string) {
89 const key = createWorkAnalyticsCacheKey(platform, platformWorkId, accountId)
90 const cached = methods.getAnalyticsByKey(key)
91 if (cached)
92 return cached
93
94 const pending = pendingRequests.get(key)
95 if (pending)
96 return pending
97
98 const request = (async () => {
99 try {
100 const res = await getChannelWorkAnalyticsApi(platform, platformWorkId, { accountId })
101 if (!res || res.code !== 0 || !res.data)
102 return null
103
104 methods.setAnalyticsByKey(key, res.data)
105 methods.clearExpiredAnalytics()
106 return res.data
107 }
108 catch {
109 return null
110 }
111 finally {
112 pendingRequests.delete(key)
113 }
114 })()
115
116 pendingRequests.set(key, request)
117 return request
118 },
119 }
120
121 return methods
122 },
123 {
124 name: 'accounts-work-analytics-cache',
125 version: 1,
126 },
127 'localStorage',
128 )
129
129 lines TYPESCRIPT