diff --git a/scripts/refresh_leaderboard_counters.ts b/scripts/refresh_leaderboard_counters.ts new file mode 100644 index 00000000..2da75828 --- /dev/null +++ b/scripts/refresh_leaderboard_counters.ts @@ -0,0 +1,44 @@ +import 'dotenv/config'; +import { refreshProposalsCount, refreshVotesCount } from '../src/helpers/actions'; +import db from '../src/helpers/mysql'; + +// Usage: yarn ts-node scripts/refresh_leaderboard_counters.ts [OPTIONAL-SPACE-ID] +async function main() { + const query = `SELECT id, name FROM spaces ${ + process.argv[2] ? `WHERE id = '${process.argv[2]}'` : '' + }`; + + const spaces: { id: string; name: string }[] = await db.queryAsync(query); + + for (const index in spaces) { + console.log( + `Processing space #${spaces[index].id} (${spaces[index].name}) - ${+index + 1}/${ + spaces.length + }` + ); + + const votesCountRes = await refreshVotesCount([spaces[index].id]); + console.log( + 'Inserting/Updating vote_count - ', + `Affected: ${votesCountRes.affectedRows}`, + `Changed: ${votesCountRes.changedRows}` + ); + + const proposalsCountRes = await refreshProposalsCount([spaces[index].id]); + console.log( + 'Inserting/Updating proposal_count', + `Affected: ${proposalsCountRes.affectedRows}`, + `Changed: ${proposalsCountRes.changedRows}` + ); + } +} + +(async () => { + try { + await main(); + process.exit(0); + } catch (e) { + console.error(e); + process.exit(1); + } +})(); diff --git a/src/helpers/actions.ts b/src/helpers/actions.ts index 0e4d587c..71689e1f 100644 --- a/src/helpers/actions.ts +++ b/src/helpers/actions.ts @@ -53,8 +53,37 @@ export async function getSpace(id: string, includeDeleted = false) { }; } -export async function markSpaceAsDeleted(space: string) { - const query = 'UPDATE spaces SET deleted = ? WHERE id = ?'; +export function refreshProposalsCount(spaces?: string[]) { + return db.queryAsync( + ` + INSERT INTO leaderboard (proposal_count, user, space) + (SELECT * FROM ( + SELECT COUNT(proposals.id) AS proposal_count, author, space + FROM proposals + JOIN spaces ON spaces.id = proposals.space + WHERE spaces.deleted = 0 + ${spaces ? ' AND space IN (?)' : ''} + GROUP BY author, space + ) AS t) + ON DUPLICATE KEY UPDATE proposal_count = t.proposal_count + `, + spaces + ); +} - await db.queryAsync(query, [1, space]); +export function refreshVotesCount(spaces: string[]) { + return db.queryAsync( + ` + INSERT INTO leaderboard (vote_count, user, space) + (SELECT * FROM ( + SELECT COUNT(votes.id) AS vote_count, voter, space + FROM votes + JOIN spaces ON spaces.id = votes.space + WHERE spaces.deleted = 0 AND space IN (?) + GROUP BY voter, space + ) AS t) + ON DUPLICATE KEY UPDATE vote_count = t.vote_count + `, + spaces + ); } diff --git a/src/writer/delete-proposal.ts b/src/writer/delete-proposal.ts index 02639ba4..d795c1c4 100644 --- a/src/writer/delete-proposal.ts +++ b/src/writer/delete-proposal.ts @@ -1,4 +1,4 @@ -import { getProposal, getSpace } from '../helpers/actions'; +import { getProposal, getSpace, refreshVotesCount } from '../helpers/actions'; import { jsonParse } from '../helpers/utils'; import db from '../helpers/mysql'; @@ -20,11 +20,20 @@ export async function verify(body): Promise { export async function action(body): Promise { const msg = jsonParse(body.msg); + const proposal = await getProposal(msg.space, msg.payload.proposal); const id = msg.payload.proposal; - const query = ` - DELETE FROM proposals WHERE id = ? LIMIT 1; - DELETE FROM votes WHERE proposal = ?; - `; - await db.queryAsync(query, [id, id]); + await db.queryAsync( + ` + DELETE FROM proposals WHERE id = ? LIMIT 1; + DELETE FROM votes WHERE proposal = ?; + UPDATE leaderboard + SET proposal_count = proposal_count - 1 + WHERE user = ? AND space = ? + LIMIT 1; + `, + [id, id, proposal.author, msg.space] + ); + + await refreshVotesCount([msg.space]); } diff --git a/src/writer/delete-space.ts b/src/writer/delete-space.ts index 6002f553..95057e31 100644 --- a/src/writer/delete-space.ts +++ b/src/writer/delete-space.ts @@ -1,5 +1,6 @@ import snapshot from '@snapshot-labs/snapshot.js'; -import { getSpace, markSpaceAsDeleted } from '../helpers/actions'; +import db from '../helpers/mysql'; +import { getSpace } from '../helpers/actions'; import { jsonParse, DEFAULT_NETWORK } from '../helpers/utils'; import { capture } from '@snapshot-labs/snapshot-sentry'; import log from '../helpers/log'; @@ -24,7 +25,12 @@ export async function action(body): Promise { const space = msg.space; try { - await markSpaceAsDeleted(space); + const query = ` + UPDATE spaces SET deleted = 1 WHERE id = ? LIMIT 1; + DELETE FROM leaderboard WHERE space = ?; + `; + + await db.queryAsync(query, [space, space]); } catch (e) { capture(e, { space }); log.warn('[writer] Failed to store settings', space, e); diff --git a/src/writer/proposal.ts b/src/writer/proposal.ts index fdad9c21..4bbf21d5 100644 --- a/src/writer/proposal.ts +++ b/src/writer/proposal.ts @@ -250,6 +250,12 @@ export async function action(body, ipfs, receipt, id): Promise { flagged: +containsFlaggedLinks(msg.payload.body) }; - const query = 'INSERT INTO proposals SET ?; '; - await db.queryAsync(query, proposal); + const query = ` + INSERT INTO proposals SET ?; + INSERT INTO leaderboard (space, user, proposal_count) + VALUES(?, ?, 1) + ON DUPLICATE KEY UPDATE proposal_count = proposal_count + 1 + `; + + await db.queryAsync(query, [proposal, space, author]); } diff --git a/src/writer/vote.ts b/src/writer/vote.ts index 9f88c4d0..580298fa 100644 --- a/src/writer/vote.ts +++ b/src/writer/vote.ts @@ -175,7 +175,15 @@ export async function action(body, ipfs, receipt, id, context): Promise { ); } else { // Store vote in dedicated table - await db.queryAsync('INSERT INTO votes SET ?', params); + await db.queryAsync( + ` + INSERT INTO votes SET ?; + INSERT INTO leaderboard (space, user, vote_count) + VALUES(?, ?, 1) + ON DUPLICATE KEY UPDATE vote_count = vote_count + 1 + `, + [params, msg.space, voter] + ); } // Update proposal scores and voters vp diff --git a/test/schema.sql b/test/schema.sql index e844732e..1524ac01 100644 --- a/test/schema.sql +++ b/test/schema.sql @@ -179,3 +179,14 @@ CREATE TABLE messages ( INDEX type (type), INDEX receipt (receipt) ); + +CREATE TABLE leaderboard ( + user VARCHAR(64) NOT NULL, + space VARCHAR(64) NOT NULL, + vote_count SMALLINT UNSIGNED NOT NULL DEFAULT '0', + proposal_count SMALLINT UNSIGNED NOT NULL DEFAULT '0', + PRIMARY KEY user_space (user,space), + INDEX space (space), + INDEX vote_count (vote_count), + INDEX proposal_count (proposal_count) +);