Skip to content

Commit 2869080

Browse files
bonustrackwa0x6eChaituVR
authored
feat: store proposal, vote and follower count on space (#292)
* feat: store proposal, vote and follower count on space * fix: ensure positive counters * fix: fix missing arg * fix: use total number of votes, instead of number of valid votes * fix: add init script to populate spaces counters * chore: add tests for follow and unfollow * Update scripts/refresh_spaces_counters.ts Co-authored-by: Chaitanya <[email protected]> * fix: fix proposals counter being skipped when no votes * fix: fix missing follows_count initializer --------- Co-authored-by: Wan Qi Chen <[email protected]> Co-authored-by: Chaitanya <[email protected]>
1 parent daa929f commit 2869080

File tree

8 files changed

+193
-11
lines changed

8 files changed

+193
-11
lines changed

scripts/refresh_spaces_counters.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import 'dotenv/config';
2+
import db from '../src/helpers/mysql';
3+
4+
// Usage: yarn ts-node scripts/refresh_spaces_counters.ts
5+
async function main() {
6+
const spaces = await db.queryAsync(`SELECT id FROM spaces`);
7+
8+
console.log(`Found ${spaces.length} spaces`);
9+
10+
for (const i in spaces) {
11+
const stats = await db.queryAsync(
12+
`SELECT COUNT(voter) as vote_count FROM votes WHERE space = ?`,
13+
[spaces[i].id]
14+
);
15+
const stat = stats[0];
16+
await db.queryAsync(`UPDATE spaces SET vote_count = ? WHERE id = ?`, [
17+
stat.vote_count,
18+
spaces[i].id
19+
]);
20+
console.log(`${i} / ${spaces.length}`);
21+
}
22+
23+
await db.queryAsync(
24+
'UPDATE spaces SET proposal_count = (SELECT count(id) from proposals WHERE space = spaces.id)'
25+
);
26+
27+
await db.queryAsync(
28+
'UPDATE spaces SET follower_count = (SELECT count(id) from follows WHERE space = spaces.id)'
29+
);
30+
}
31+
32+
(async () => {
33+
try {
34+
await main();
35+
process.exit(0);
36+
} catch (e) {
37+
console.error(e);
38+
process.exit(1);
39+
}
40+
})();

src/writer/delete-proposal.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export async function verify(body): Promise<any> {
2121
export async function action(body): Promise<void> {
2222
const msg = jsonParse(body.msg);
2323
const proposal = await getProposal(msg.space, msg.payload.proposal);
24+
2425
const voters = await db.queryAsync(`SELECT voter FROM votes WHERE proposal = ?`, [
2526
msg.payload.proposal
2627
]);
@@ -33,9 +34,12 @@ export async function action(body): Promise<void> {
3334
SET proposal_count = GREATEST(proposal_count - 1, 0)
3435
WHERE user = ? AND space = ?
3536
LIMIT 1;
37+
UPDATE spaces
38+
SET proposal_count = GREATEST(proposal_count - 1, 0), vote_count = GREATEST(vote_count - ?, 0)
39+
WHERE id = ?;
3640
`;
3741

38-
const parameters = [id, id, proposal.author, msg.space];
42+
const parameters = [id, id, proposal.author, msg.space, voters.length, msg.space];
3943

4044
if (voters.length > 0) {
4145
queries += `

src/writer/follow.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ export const getFollowsCount = async (follower: string): Promise<number> => {
1111
};
1212

1313
export async function verify(message): Promise<any> {
14+
const query = `SELECT * FROM follows WHERE follower = ? AND space = ? LIMIT 1`;
15+
const follows = await db.queryAsync(query, [message.from, message.space]);
16+
17+
if (follows.length !== 0) return Promise.reject('you are already following this space');
18+
1419
const count = await getFollowsCount(message.from);
1520

1621
if (count >= FOLLOWS_LIMIT_PER_USER) {
@@ -24,7 +29,11 @@ export async function verify(message): Promise<any> {
2429
return true;
2530
}
2631

27-
export async function action(message, ipfs, receipt, id): Promise<void> {
32+
export async function action(message, ipfs, _receipt, id): Promise<void> {
33+
const query = `
34+
INSERT INTO follows SET ?;
35+
UPDATE spaces SET follower_count = follower_count + 1 WHERE id = ?;
36+
`;
2837
const params = {
2938
id,
3039
ipfs,
@@ -34,5 +43,5 @@ export async function action(message, ipfs, receipt, id): Promise<void> {
3443
created: message.timestamp
3544
};
3645

37-
await db.queryAsync('INSERT INTO follows SET ?', params);
46+
await db.queryAsync(query, [params, message.space]);
3847
}

src/writer/proposal.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,10 @@ export async function action(body, ipfs, receipt, id): Promise<void> {
253253
const query = `
254254
INSERT INTO proposals SET ?;
255255
INSERT INTO leaderboard (space, user, proposal_count)
256-
VALUES(?, ?, 1)
257-
ON DUPLICATE KEY UPDATE proposal_count = proposal_count + 1
256+
VALUES(?, ?, 1)
257+
ON DUPLICATE KEY UPDATE proposal_count = proposal_count + 1;
258+
UPDATE spaces SET proposal_count = proposal_count + 1 WHERE id = ?;
258259
`;
259260

260-
await db.queryAsync(query, [proposal, space, author]);
261+
await db.queryAsync(query, [proposal, space, author, space]);
261262
}

src/writer/unfollow.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,27 @@
11
import db from '../helpers/mysql';
22
import { DEFAULT_NETWORK_ID } from '../helpers/utils';
33

4-
export async function verify(): Promise<any> {
4+
export async function verify(message): Promise<any> {
5+
const query = `SELECT * FROM follows WHERE follower = ? AND space = ? AND network = ? LIMIT 1`;
6+
const follows = await db.queryAsync(query, [
7+
message.from,
8+
message.space,
9+
message.network || DEFAULT_NETWORK_ID
10+
]);
11+
12+
if (follows.length === 0) return Promise.reject('you can only unfollow a space you follow');
13+
514
return true;
615
}
716

817
export async function action(message): Promise<void> {
9-
const query = 'DELETE FROM follows WHERE follower = ? AND space = ? AND network = ? LIMIT 1';
10-
await db.queryAsync(query, [message.from, message.space, message.network || DEFAULT_NETWORK_ID]);
18+
const query = `
19+
DELETE FROM follows WHERE follower = ? AND space = ? AND network = ? LIMIT 1;
20+
UPDATE spaces SET follower_count = GREATEST(follower_count - 1, 0) WHERE id = ?;`;
21+
await db.queryAsync(query, [
22+
message.from,
23+
message.space,
24+
message.network || DEFAULT_NETWORK_ID,
25+
message.space
26+
]);
1127
}

src/writer/vote.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,10 @@ export async function action(body, ipfs, receipt, id, context): Promise<void> {
183183
INSERT INTO votes SET ?;
184184
INSERT INTO leaderboard (space, user, vote_count, last_vote)
185185
VALUES(?, ?, 1, ?)
186-
ON DUPLICATE KEY UPDATE vote_count = vote_count + 1, last_vote = ?
186+
ON DUPLICATE KEY UPDATE vote_count = vote_count + 1, last_vote = ?;
187+
UPDATE spaces SET vote_count = vote_count + 1 WHERE id = ?;
187188
`,
188-
[params, msg.space, voter, created, created]
189+
[params, msg.space, voter, created, created, msg.space]
189190
);
190191
}
191192

test/integration/writer/follows.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import { verify, action } from '../../../src/writer/follow';
22
import { FOLLOWS_LIMIT_PER_USER } from '../../../src/helpers/limits';
33
import db, { sequencerDB } from '../../../src/helpers/mysql';
4+
import { spacesSqlFixtures } from '../../fixtures/space';
45

56
describe('writer/follow', () => {
7+
const TEST_PREFIX = 'test-follow-';
8+
const space = spacesSqlFixtures[1];
9+
610
afterAll(async () => {
711
await db.queryAsync('DELETE FROM follows');
12+
await db.queryAsync('DELETE FROM spaces WHERE id = ?', [`${TEST_PREFIX}-${space.id}`]);
813
await db.endAsync();
914
await sequencerDB.endAsync();
1015
});
@@ -102,5 +107,27 @@ describe('writer/follow', () => {
102107
]);
103108
});
104109
});
110+
111+
it('should increment the follower count of the space', async () => {
112+
await db.queryAsync('INSERT INTO spaces SET ?', {
113+
...space,
114+
id: `${TEST_PREFIX}-${space.id}`,
115+
settings: JSON.stringify(space.settings)
116+
});
117+
118+
const id = '3';
119+
const ipfs = '4';
120+
const message = {
121+
from: '0x4',
122+
space: `${TEST_PREFIX}-${space.id}`,
123+
timestamp: 1
124+
};
125+
126+
await action(message, ipfs, 1, id);
127+
128+
return expect(
129+
db.queryAsync('SELECT follower_count FROM spaces WHERE id = ?', [message.space])
130+
).resolves.toEqual([{ follower_count: 1 }]);
131+
});
105132
});
106133
});
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { verify, action } from '../../../src/writer/unfollow';
2+
import db, { sequencerDB } from '../../../src/helpers/mysql';
3+
import { spacesSqlFixtures } from '../../fixtures/space';
4+
5+
describe('writer/unfollow', () => {
6+
const TEST_PREFIX = 'test-unfollow-';
7+
const space = spacesSqlFixtures[0];
8+
const followerId = '0x0';
9+
10+
afterAll(async () => {
11+
await db.queryAsync('DELETE FROM follows');
12+
await db.queryAsync('DELETE FROM spaces WHERE id LIKE ?', [`${TEST_PREFIX}-%`]);
13+
await db.endAsync();
14+
await sequencerDB.endAsync();
15+
});
16+
17+
describe('verify()', () => {
18+
beforeAll(async () => {
19+
let i = 0;
20+
const promises: Promise<any>[] = [];
21+
22+
while (i <= 2) {
23+
promises.push(
24+
db.queryAsync(
25+
'INSERT INTO follows SET id = ?, ipfs = ?, follower = ?, space = ?, network = ?, created = ?',
26+
[i, i, followerId, `${TEST_PREFIX}-test-${i}.eth`, 's', i]
27+
)
28+
);
29+
30+
i++;
31+
}
32+
33+
await Promise.all(promises);
34+
});
35+
36+
it('rejects when the user has not followed the space', () => {
37+
return expect(verify({ from: '0x1', space: `${TEST_PREFIX}-test-2.eth` })).rejects.toEqual(
38+
'you can only unfollow a space you follow'
39+
);
40+
});
41+
42+
it('returns true when the user has followed the space', () => {
43+
return expect(
44+
verify({ from: followerId, space: `${TEST_PREFIX}-test-0.eth`, network: 's' })
45+
).resolves.toEqual(true);
46+
});
47+
});
48+
49+
describe('action()', () => {
50+
it('should unfollow and decrement the follower count of the space', async () => {
51+
await db.queryAsync('INSERT INTO spaces SET ?', {
52+
...space,
53+
id: `${TEST_PREFIX}-test-0.eth`,
54+
settings: JSON.stringify(space.settings),
55+
follower_count: 2
56+
});
57+
58+
const message = {
59+
from: followerId,
60+
space: `${TEST_PREFIX}-test-0.eth`,
61+
network: 's'
62+
};
63+
64+
expect(
65+
db.queryAsync('SELECT id FROM follows WHERE follower = ? AND space = ?', [
66+
message.from,
67+
message.space
68+
])
69+
).resolves.not.toEqual([]);
70+
71+
await action(message);
72+
73+
expect(
74+
db.queryAsync('SELECT * FROM follows WHERE follower = ? AND space = ?', [
75+
message.from,
76+
message.space
77+
])
78+
).resolves.toEqual([]);
79+
return expect(
80+
db.queryAsync('SELECT follower_count FROM spaces WHERE id = ?', [message.space])
81+
).resolves.toEqual([{ follower_count: 1 }]);
82+
});
83+
});
84+
});

0 commit comments

Comments
 (0)