diff --git a/scripts/refresh_spaces_counters.ts b/scripts/refresh_spaces_counters.ts new file mode 100644 index 00000000..7f7c46c8 --- /dev/null +++ b/scripts/refresh_spaces_counters.ts @@ -0,0 +1,40 @@ +import 'dotenv/config'; +import db from '../src/helpers/mysql'; + +// Usage: yarn ts-node scripts/refresh_spaces_counters.ts +async function main() { + const spaces = await db.queryAsync(`SELECT id FROM spaces`); + + console.log(`Found ${spaces.length} spaces`); + + for (const i in spaces) { + const stats = await db.queryAsync( + `SELECT COUNT(voter) as vote_count FROM votes WHERE space = ?`, + [spaces[i].id] + ); + const stat = stats[0]; + await db.queryAsync(`UPDATE spaces SET vote_count = ? WHERE id = ?`, [ + stat.vote_count, + spaces[i].id + ]); + console.log(`${i} / ${spaces.length}`); + } + + await db.queryAsync( + 'UPDATE spaces SET proposal_count = (SELECT count(id) from proposals WHERE space = spaces.id)' + ); + + await db.queryAsync( + 'UPDATE spaces SET follower_count = (SELECT count(id) from follows WHERE space = spaces.id)' + ); +} + +(async () => { + try { + await main(); + process.exit(0); + } catch (e) { + console.error(e); + process.exit(1); + } +})(); diff --git a/src/writer/delete-proposal.ts b/src/writer/delete-proposal.ts index f218600e..d26a29bc 100644 --- a/src/writer/delete-proposal.ts +++ b/src/writer/delete-proposal.ts @@ -21,6 +21,7 @@ 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 voters = await db.queryAsync(`SELECT voter FROM votes WHERE proposal = ?`, [ msg.payload.proposal ]); @@ -33,9 +34,12 @@ export async function action(body): Promise { SET proposal_count = GREATEST(proposal_count - 1, 0) WHERE user = ? AND space = ? LIMIT 1; + UPDATE spaces + SET proposal_count = GREATEST(proposal_count - 1, 0), vote_count = GREATEST(vote_count - ?, 0) + WHERE id = ?; `; - const parameters = [id, id, proposal.author, msg.space]; + const parameters = [id, id, proposal.author, msg.space, voters.length, msg.space]; if (voters.length > 0) { queries += ` diff --git a/src/writer/follow.ts b/src/writer/follow.ts index ec3f0829..62e2c1e7 100644 --- a/src/writer/follow.ts +++ b/src/writer/follow.ts @@ -11,6 +11,11 @@ export const getFollowsCount = async (follower: string): Promise => { }; export async function verify(message): Promise { + const query = `SELECT * FROM follows WHERE follower = ? AND space = ? LIMIT 1`; + const follows = await db.queryAsync(query, [message.from, message.space]); + + if (follows.length !== 0) return Promise.reject('you are already following this space'); + const count = await getFollowsCount(message.from); if (count >= FOLLOWS_LIMIT_PER_USER) { @@ -24,7 +29,11 @@ export async function verify(message): Promise { return true; } -export async function action(message, ipfs, receipt, id): Promise { +export async function action(message, ipfs, _receipt, id): Promise { + const query = ` + INSERT INTO follows SET ?; + UPDATE spaces SET follower_count = follower_count + 1 WHERE id = ?; + `; const params = { id, ipfs, @@ -34,5 +43,5 @@ export async function action(message, ipfs, receipt, id): Promise { created: message.timestamp }; - await db.queryAsync('INSERT INTO follows SET ?', params); + await db.queryAsync(query, [params, message.space]); } diff --git a/src/writer/proposal.ts b/src/writer/proposal.ts index bd0082b3..f85ebd49 100644 --- a/src/writer/proposal.ts +++ b/src/writer/proposal.ts @@ -253,9 +253,10 @@ export async function action(body, ipfs, receipt, id): Promise { const query = ` INSERT INTO proposals SET ?; INSERT INTO leaderboard (space, user, proposal_count) - VALUES(?, ?, 1) - ON DUPLICATE KEY UPDATE proposal_count = proposal_count + 1 + VALUES(?, ?, 1) + ON DUPLICATE KEY UPDATE proposal_count = proposal_count + 1; + UPDATE spaces SET proposal_count = proposal_count + 1 WHERE id = ?; `; - await db.queryAsync(query, [proposal, space, author]); + await db.queryAsync(query, [proposal, space, author, space]); } diff --git a/src/writer/unfollow.ts b/src/writer/unfollow.ts index f8319e76..bcf3b54f 100644 --- a/src/writer/unfollow.ts +++ b/src/writer/unfollow.ts @@ -1,11 +1,27 @@ import db from '../helpers/mysql'; import { DEFAULT_NETWORK_ID } from '../helpers/utils'; -export async function verify(): Promise { +export async function verify(message): Promise { + const query = `SELECT * FROM follows WHERE follower = ? AND space = ? AND network = ? LIMIT 1`; + const follows = await db.queryAsync(query, [ + message.from, + message.space, + message.network || DEFAULT_NETWORK_ID + ]); + + if (follows.length === 0) return Promise.reject('you can only unfollow a space you follow'); + return true; } export async function action(message): Promise { - const query = 'DELETE FROM follows WHERE follower = ? AND space = ? AND network = ? LIMIT 1'; - await db.queryAsync(query, [message.from, message.space, message.network || DEFAULT_NETWORK_ID]); + const query = ` + DELETE FROM follows WHERE follower = ? AND space = ? AND network = ? LIMIT 1; + UPDATE spaces SET follower_count = GREATEST(follower_count - 1, 0) WHERE id = ?;`; + await db.queryAsync(query, [ + message.from, + message.space, + message.network || DEFAULT_NETWORK_ID, + message.space + ]); } diff --git a/src/writer/vote.ts b/src/writer/vote.ts index 135c1968..cdf342dc 100644 --- a/src/writer/vote.ts +++ b/src/writer/vote.ts @@ -183,9 +183,10 @@ export async function action(body, ipfs, receipt, id, context): Promise { INSERT INTO votes SET ?; INSERT INTO leaderboard (space, user, vote_count, last_vote) VALUES(?, ?, 1, ?) - ON DUPLICATE KEY UPDATE vote_count = vote_count + 1, last_vote = ? + ON DUPLICATE KEY UPDATE vote_count = vote_count + 1, last_vote = ?; + UPDATE spaces SET vote_count = vote_count + 1 WHERE id = ?; `, - [params, msg.space, voter, created, created] + [params, msg.space, voter, created, created, msg.space] ); } diff --git a/test/integration/writer/follows.test.ts b/test/integration/writer/follows.test.ts index 52c7f13a..4fecccdd 100644 --- a/test/integration/writer/follows.test.ts +++ b/test/integration/writer/follows.test.ts @@ -1,10 +1,15 @@ import { verify, action } from '../../../src/writer/follow'; import { FOLLOWS_LIMIT_PER_USER } from '../../../src/helpers/limits'; import db, { sequencerDB } from '../../../src/helpers/mysql'; +import { spacesSqlFixtures } from '../../fixtures/space'; describe('writer/follow', () => { + const TEST_PREFIX = 'test-follow-'; + const space = spacesSqlFixtures[1]; + afterAll(async () => { await db.queryAsync('DELETE FROM follows'); + await db.queryAsync('DELETE FROM spaces WHERE id = ?', [`${TEST_PREFIX}-${space.id}`]); await db.endAsync(); await sequencerDB.endAsync(); }); @@ -102,5 +107,27 @@ describe('writer/follow', () => { ]); }); }); + + it('should increment the follower count of the space', async () => { + await db.queryAsync('INSERT INTO spaces SET ?', { + ...space, + id: `${TEST_PREFIX}-${space.id}`, + settings: JSON.stringify(space.settings) + }); + + const id = '3'; + const ipfs = '4'; + const message = { + from: '0x4', + space: `${TEST_PREFIX}-${space.id}`, + timestamp: 1 + }; + + await action(message, ipfs, 1, id); + + return expect( + db.queryAsync('SELECT follower_count FROM spaces WHERE id = ?', [message.space]) + ).resolves.toEqual([{ follower_count: 1 }]); + }); }); }); diff --git a/test/integration/writer/unfollows.test.ts b/test/integration/writer/unfollows.test.ts new file mode 100644 index 00000000..9defc658 --- /dev/null +++ b/test/integration/writer/unfollows.test.ts @@ -0,0 +1,84 @@ +import { verify, action } from '../../../src/writer/unfollow'; +import db, { sequencerDB } from '../../../src/helpers/mysql'; +import { spacesSqlFixtures } from '../../fixtures/space'; + +describe('writer/unfollow', () => { + const TEST_PREFIX = 'test-unfollow-'; + const space = spacesSqlFixtures[0]; + const followerId = '0x0'; + + afterAll(async () => { + await db.queryAsync('DELETE FROM follows'); + await db.queryAsync('DELETE FROM spaces WHERE id LIKE ?', [`${TEST_PREFIX}-%`]); + await db.endAsync(); + await sequencerDB.endAsync(); + }); + + describe('verify()', () => { + beforeAll(async () => { + let i = 0; + const promises: Promise[] = []; + + while (i <= 2) { + promises.push( + db.queryAsync( + 'INSERT INTO follows SET id = ?, ipfs = ?, follower = ?, space = ?, network = ?, created = ?', + [i, i, followerId, `${TEST_PREFIX}-test-${i}.eth`, 's', i] + ) + ); + + i++; + } + + await Promise.all(promises); + }); + + it('rejects when the user has not followed the space', () => { + return expect(verify({ from: '0x1', space: `${TEST_PREFIX}-test-2.eth` })).rejects.toEqual( + 'you can only unfollow a space you follow' + ); + }); + + it('returns true when the user has followed the space', () => { + return expect( + verify({ from: followerId, space: `${TEST_PREFIX}-test-0.eth`, network: 's' }) + ).resolves.toEqual(true); + }); + }); + + describe('action()', () => { + it('should unfollow and decrement the follower count of the space', async () => { + await db.queryAsync('INSERT INTO spaces SET ?', { + ...space, + id: `${TEST_PREFIX}-test-0.eth`, + settings: JSON.stringify(space.settings), + follower_count: 2 + }); + + const message = { + from: followerId, + space: `${TEST_PREFIX}-test-0.eth`, + network: 's' + }; + + expect( + db.queryAsync('SELECT id FROM follows WHERE follower = ? AND space = ?', [ + message.from, + message.space + ]) + ).resolves.not.toEqual([]); + + await action(message); + + expect( + db.queryAsync('SELECT * FROM follows WHERE follower = ? AND space = ?', [ + message.from, + message.space + ]) + ).resolves.toEqual([]); + return expect( + db.queryAsync('SELECT follower_count FROM spaces WHERE id = ?', [message.space]) + ).resolves.toEqual([{ follower_count: 1 }]); + }); + }); +});