forked from polkassembly/polkassembly
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f3f8f93
commit d5c196a
Showing
8 changed files
with
272 additions
and
4 deletions.
There are no files selected for viewing
58 changes: 58 additions & 0 deletions
58
pages/api/v1/trackLevelAnalytics/all-track-analytics-stats.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// Copyright 2019-2025 @polkassembly/polkassembly authors & contributors | ||
// This software may be modified and distributed under the terms | ||
// of the Apache-2.0 license. See the LICENSE file for details. | ||
|
||
import type { NextApiHandler } from 'next'; | ||
import withErrorHandling from '~src/api-middlewares/withErrorHandling'; | ||
import { isValidNetwork } from '~src/api-utils'; | ||
import { MessageType } from '~src/auth/types'; | ||
import messages from '~src/auth/utils/messages'; | ||
import storeApiKeyUsage from '~src/api-middlewares/storeApiKeyUsage'; | ||
import fetchSubsquid from '~src/util/fetchSubsquid'; | ||
import { GET_ALL_TRACK_LEVEL_ANALYTICS_STATS } from '~src/queries'; | ||
import dayjs from 'dayjs'; | ||
import apiErrorWithStatusCode from '~src/util/apiErrorWithStatusCode'; | ||
import { ITrackAnalyticsStats } from '~src/redux/trackLevelAnalytics/@types'; | ||
|
||
export const getAllTrackAnalyticsStats = async ({ network }: { network: string }) => { | ||
if (!network || !isValidNetwork(network)) { | ||
throw apiErrorWithStatusCode(messages.INVALID_NETWORK, 400); | ||
} | ||
|
||
try { | ||
const subsquidRes = await fetchSubsquid({ | ||
network, | ||
query: GET_ALL_TRACK_LEVEL_ANALYTICS_STATS, | ||
variables: { | ||
before: dayjs().subtract(7, 'days').toISOString() | ||
} | ||
}); | ||
const data = subsquidRes['data']; | ||
const diffActiveProposals = (data?.diffActiveProposals?.totalCount * 100) / data?.totalActiveProposals?.totalCount; | ||
const diffProposalCount = (data?.diffProposalCount?.totalCount * 100) / data?.totalProposalCount.totalCount; | ||
return { | ||
data: { | ||
activeProposals: { diff: diffActiveProposals.toFixed(2) || 0, total: data?.totalActiveProposals?.totalCount || 0 }, | ||
allProposals: { diff: diffProposalCount.toFixed(2) || 0, total: data?.totalProposalCount?.totalCount || 0 } | ||
}, | ||
error: null | ||
}; | ||
} catch (err) { | ||
return { data: null, error: err || messages.API_FETCH_ERROR }; | ||
} | ||
}; | ||
|
||
const handler: NextApiHandler<ITrackAnalyticsStats | MessageType> = async (req, res) => { | ||
storeApiKeyUsage(req); | ||
|
||
const network = String(req.headers['x-network']); | ||
|
||
const { data, error } = await getAllTrackAnalyticsStats({ network }); | ||
|
||
if (data) { | ||
return res.status(200).json(data as ITrackAnalyticsStats); | ||
} else { | ||
return res.status(500).json({ message: error || 'Activities count not found!' }); | ||
} | ||
}; | ||
export default withErrorHandling(handler); |
108 changes: 108 additions & 0 deletions
108
pages/api/v1/trackLevelAnalytics/all-track-delegation-analytics-stats.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// Copyright 2019-2025 @polkassembly/polkassembly authors & contributors | ||
// This software may be modified and distributed under the terms | ||
// of the Apache-2.0 license. See the LICENSE file for details. | ||
import type { NextApiRequest, NextApiResponse } from 'next'; | ||
import withErrorHandling from '~src/api-middlewares/withErrorHandling'; | ||
import { isValidNetwork } from '~src/api-utils'; | ||
import { MessageType } from '~src/auth/types'; | ||
import fetchSubsquid from '~src/util/fetchSubsquid'; | ||
import { GET_ALL_TRACK_LEVEL_ANALYTICS_DELEGATION_DATA } from '~src/queries'; | ||
import BN from 'bn.js'; | ||
import storeApiKeyUsage from '~src/api-middlewares/storeApiKeyUsage'; | ||
import messages from '~src/auth/utils/messages'; | ||
import apiErrorWithStatusCode from '~src/util/apiErrorWithStatusCode'; | ||
import { IDelegationAnalytics, IDelegatorsAndDelegatees } from '~src/redux/trackLevelAnalytics/@types'; | ||
|
||
const ZERO_BN = new BN(0); | ||
|
||
export const getTrackDelegationAnalyticsStats = async ({ network }: { network: string }) => { | ||
try { | ||
if (!network || !isValidNetwork(network)) throw apiErrorWithStatusCode(messages.INVALID_NETWORK, 400); | ||
|
||
const data = await fetchSubsquid({ | ||
network, | ||
query: GET_ALL_TRACK_LEVEL_ANALYTICS_DELEGATION_DATA | ||
}); | ||
|
||
let totalCapital = ZERO_BN; | ||
let totalVotesBalance = ZERO_BN; | ||
const totalDelegatorsObj: IDelegatorsAndDelegatees = {}; | ||
const totalDelegateesObj: IDelegatorsAndDelegatees = {}; | ||
|
||
if (data['data']?.votingDelegations?.length) { | ||
data['data']?.votingDelegations.map((delegation: { lockPeriod: number; balance: string; from: string; to: string }) => { | ||
const bnBalance = new BN(delegation?.balance); | ||
const bnConviction = new BN(delegation?.lockPeriod || 1); | ||
const vote = delegation?.lockPeriod ? bnBalance.mul(bnConviction) : bnBalance.div(new BN('10')); | ||
|
||
totalVotesBalance = totalVotesBalance.add(vote); | ||
|
||
totalCapital = totalCapital.add(bnBalance); | ||
|
||
if (totalDelegateesObj[delegation?.to] === undefined) { | ||
totalDelegateesObj[delegation?.to] = { | ||
count: 1, | ||
data: [{ capital: delegation.balance, from: delegation?.from, lockedPeriod: delegation.lockPeriod || 0.1, to: delegation?.to, votingPower: vote.toString() }] | ||
}; | ||
} else { | ||
totalDelegateesObj[delegation?.to] = { | ||
count: totalDelegateesObj[delegation?.to]?.count + 1, | ||
data: [ | ||
...(totalDelegateesObj[delegation?.to]?.data || []), | ||
{ capital: delegation.balance, from: delegation?.from, lockedPeriod: delegation.lockPeriod || 0.1, to: delegation?.to, votingPower: vote.toString() } | ||
] | ||
}; | ||
} | ||
if (totalDelegatorsObj[delegation?.from] === undefined) { | ||
totalDelegatorsObj[delegation?.from] = { | ||
count: 1, | ||
data: [{ capital: delegation.balance, from: delegation?.from, lockedPeriod: delegation.lockPeriod || 0.1, to: delegation?.to, votingPower: vote.toString() }] | ||
}; | ||
} else { | ||
totalDelegatorsObj[delegation?.from] = { | ||
count: totalDelegatorsObj[delegation?.to]?.count + 1, | ||
data: [ | ||
...(totalDelegatorsObj[delegation?.to]?.data || []), | ||
{ capital: delegation.balance, from: delegation?.from, lockedPeriod: delegation.lockPeriod || 0.1, to: delegation.to, votingPower: vote.toString() } | ||
] | ||
}; | ||
} | ||
}); | ||
} | ||
|
||
const delegationStats: IDelegationAnalytics = { | ||
delegateesData: totalDelegateesObj, | ||
delegatorsData: totalDelegatorsObj, | ||
totalCapital: totalCapital.toString(), | ||
totalDelegates: Object.keys(totalDelegateesObj)?.length, | ||
totalDelegators: Object.keys(totalDelegatorsObj)?.length, | ||
totalVotesBalance: totalVotesBalance.toString() | ||
}; | ||
return { | ||
data: delegationStats, | ||
error: null, | ||
status: 200 | ||
}; | ||
} catch (error) { | ||
return { | ||
data: null, | ||
error: error || messages.API_FETCH_ERROR, | ||
status: 500 | ||
}; | ||
} | ||
}; | ||
|
||
async function handler(req: NextApiRequest, res: NextApiResponse<IDelegationAnalytics | MessageType>) { | ||
storeApiKeyUsage(req); | ||
|
||
const network = String(req.headers['x-network']); | ||
|
||
const { data, error } = await getTrackDelegationAnalyticsStats({ network }); | ||
|
||
if (data) { | ||
return res.status(200).json(data); | ||
} | ||
return res.status(500).json({ message: error || messages.API_FETCH_ERROR }); | ||
} | ||
|
||
export default withErrorHandling(handler); |
76 changes: 76 additions & 0 deletions
76
pages/api/v1/trackLevelAnalytics/all-track-votes-analytics.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
// Copyright 2019-2025 @polkassembly/polkassembly authors & contributors | ||
// This software may be modified and distributed under the terms | ||
// of the Apache-2.0 license. See the LICENSE file for details. | ||
|
||
import { NextApiRequest, NextApiResponse } from 'next'; | ||
import storeApiKeyUsage from '~src/api-middlewares/storeApiKeyUsage'; | ||
import withErrorHandling from '~src/api-middlewares/withErrorHandling'; | ||
import { isValidNetwork } from '~src/api-utils'; | ||
import { networkDocRef } from '~src/api-utils/firestore_refs'; | ||
import { redisGet, redisSetex } from '~src/auth/redis'; | ||
import { MessageType } from '~src/auth/types'; | ||
import messages from '~src/auth/utils/messages'; | ||
import { IAnalyticsVoteTrends } from '~src/components/TrackLevelAnalytics/types'; | ||
import { networkTrackInfo } from '~src/global/post_trackInfo'; | ||
import { ProposalType } from '~src/global/proposalType'; | ||
import apiErrorWithStatusCode from '~src/util/apiErrorWithStatusCode'; | ||
import { generateKey } from '~src/util/getRedisKeys'; | ||
|
||
const getAllTrackLevelVotesAnalytics = async ({ network }: { network: string }) => { | ||
const TTL_DURATION = 3600 * 23; // 23 Hours or 82800 seconds | ||
|
||
try { | ||
if (!network || !isValidNetwork(network)) throw apiErrorWithStatusCode(messages.INVALID_NETWORK, 400); | ||
|
||
const trackNumbers = Object.entries(networkTrackInfo[network]).map(([, value]) => value.trackId); | ||
const votes: IAnalyticsVoteTrends[] = []; | ||
|
||
const dataPromise = trackNumbers.map(async (trackNumber) => { | ||
const trackSnapshot = await networkDocRef(network).collection('track_level_analytics').doc(String(trackNumber))?.collection('votes').get(); | ||
|
||
trackSnapshot.docs.map((doc) => { | ||
const data = doc.data(); | ||
votes.push(data as IAnalyticsVoteTrends); | ||
}); | ||
}); | ||
|
||
await Promise.allSettled(dataPromise); | ||
|
||
if (process.env.IS_CACHING_ALLOWED == '1') { | ||
const redisKey = generateKey({ govType: 'OpenGov', keyType: 'votesAnalytics', network, proposalType: ProposalType.REFERENDUM_V2 }); | ||
const redisData = await redisGet(redisKey); | ||
|
||
if (redisData) { | ||
return { | ||
data: { votes: JSON.parse(redisData) }, | ||
error: null, | ||
status: 200 | ||
}; | ||
} | ||
} | ||
|
||
if (process.env.IS_CACHING_ALLOWED == '1') { | ||
await redisSetex(generateKey({ govType: 'OpenGov', keyType: 'votesAnalytics', network, proposalType: ProposalType.REFERENDUM_V2 }), TTL_DURATION, JSON.stringify(votes)); | ||
} | ||
return { data: { votes: votes || [] }, error: null, status: 200 }; | ||
} catch (err) { | ||
return { data: null, error: err || messages.API_FETCH_ERROR, status: err.name }; | ||
} | ||
}; | ||
|
||
async function handler(req: NextApiRequest, res: NextApiResponse<{ votes: IAnalyticsVoteTrends[] } | MessageType>) { | ||
storeApiKeyUsage(req); | ||
|
||
const network = String(req.headers['x-network']); | ||
|
||
const { data, error } = await getAllTrackLevelVotesAnalytics({ | ||
network | ||
}); | ||
if (data) { | ||
return res.status(200).json(data); | ||
} else { | ||
return res.status(400).json({ message: error }); | ||
} | ||
} | ||
|
||
export default withErrorHandling(handler); |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters