Skip to content

Commit

Permalink
refactor: votes with debounce
Browse files Browse the repository at this point in the history
  • Loading branch information
nunocaseiro committed Jan 14, 2023
1 parent 1fd7923 commit 9cb932e
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 72 deletions.
1 change: 1 addition & 0 deletions backend/src/modules/socket/gateway/socket.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export default class SocketGateway

sendUpdateVotes(excludedClient: string, voteDto: VoteDto) {
// voteDto.userId = 'aaaaaaaaaaaaaaaaaaaaaaaa';
voteDto.fromRequest = false;
this.server.except(excludedClient).emit(`${voteDto.boardId}vote`, voteDto);
}

Expand Down
42 changes: 12 additions & 30 deletions backend/src/modules/votes/controller/votes.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,26 +72,14 @@ export default class VotesController {
const { boardId, cardId, itemId } = params;
const { count, socketId } = data;

try {
if (count < 0) {
await this.deleteVoteApp.deleteVoteFromCard(
boardId,
cardId,
request.user._id,
itemId,
count
);
} else {
await this.createVoteApp.addVoteToCard(boardId, cardId, request.user._id, itemId, count);
}

this.socketService.sendUpdateVotes(socketId, data);
} catch (e) {
this.socketService.sendUpdatedBoard(boardId, socketId);

throw e;
if (count < 0) {
await this.deleteVoteApp.deleteVoteFromCard(boardId, cardId, request.user._id, itemId, count);
} else {
await this.createVoteApp.addVoteToCard(boardId, cardId, request.user._id, itemId, count);
}

this.socketService.sendUpdateVotes(socketId, data);

return HttpStatus.OK;
}

Expand Down Expand Up @@ -128,20 +116,14 @@ export default class VotesController {
const { boardId, cardId } = params;
const { count, socketId } = data;

try {
if (count < 0) {
await this.deleteVoteApp.deleteVoteFromCardGroup(boardId, cardId, request.user._id, count);
} else {
await this.createVoteApp.addVoteToCardGroup(boardId, cardId, request.user._id, count);
}

this.socketService.sendUpdateVotes(socketId, data);
} catch (e) {
this.socketService.sendUpdatedBoard(boardId, socketId);

throw e;
if (count < 0) {
await this.deleteVoteApp.deleteVoteFromCardGroup(boardId, cardId, request.user._id, count);
} else {
await this.createVoteApp.addVoteToCardGroup(boardId, cardId, request.user._id, count);
}

this.socketService.sendUpdateVotes(socketId, data);

return HttpStatus.OK;
}
}
2 changes: 2 additions & 0 deletions backend/src/modules/votes/dto/vote.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,6 @@ export default class VoteDto {
@IsMongoId()
@IsString()
userId: string;

fromRequest: boolean;
}
10 changes: 7 additions & 3 deletions backend/src/modules/votes/services/create.vote.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { ClientSession, Model } from 'mongoose';
import { BOARD_NOT_FOUND, INSERT_VOTE_FAILED, UPDATE_FAILED } from 'src/libs/exceptions/messages';
Expand All @@ -13,6 +13,7 @@ export default class CreateVoteServiceImpl implements CreateVoteServiceInterface
@InjectModel(BoardUser.name)
private boardUserModel: Model<BoardUserDocument>
) {}
private logger: Logger = new Logger('CreateVoteService');

private async canUserVote(
boardId: string,
Expand Down Expand Up @@ -84,7 +85,7 @@ export default class CreateVoteServiceImpl implements CreateVoteServiceInterface
if (!canUserVote) throw new BadRequestException(INSERT_VOTE_FAILED);

try {
await this.incrementVoteUser(boardId, userId, count);
await this.incrementVoteUser(boardId, userId, count, userSession);
const board = await this.boardModel
.updateOne(
{
Expand All @@ -97,7 +98,8 @@ export default class CreateVoteServiceImpl implements CreateVoteServiceInterface
}
},
{
arrayFilters: [{ 'c._id': cardId }, { 'i._id': cardItemId }]
arrayFilters: [{ 'c._id': cardId }, { 'i._id': cardItemId }],
session
}
)
.lean()
Expand All @@ -108,6 +110,7 @@ export default class CreateVoteServiceImpl implements CreateVoteServiceInterface
await userSession.commitTransaction();
await session.commitTransaction();
} catch (e) {
this.logger.error(e);
await userSession.abortTransaction();
await session.abortTransaction();
throw new BadRequestException(INSERT_VOTE_FAILED);
Expand Down Expand Up @@ -152,6 +155,7 @@ export default class CreateVoteServiceImpl implements CreateVoteServiceInterface
await session.commitTransaction();
await userSession.commitTransaction();
} catch (e) {
this.logger.error(e);
await session.abortTransaction();
await userSession.abortTransaction();
throw new BadRequestException(INSERT_VOTE_FAILED);
Expand Down
47 changes: 35 additions & 12 deletions backend/src/modules/votes/services/delete.vote.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { BadRequestException, Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { UpdateResult } from 'mongodb';
import { ClientSession, Model } from 'mongoose';
Expand All @@ -22,6 +22,8 @@ export default class DeleteVoteServiceImpl implements DeleteVoteServiceInterface
private getCardService: GetCardService
) {}

private logger: Logger = new Logger('DeleteVoteService');

private async canUserVote(
boardId: string,
userId: string,
Expand Down Expand Up @@ -104,6 +106,7 @@ export default class DeleteVoteServiceImpl implements DeleteVoteServiceInterface
await userSession.commitTransaction();
await session.commitTransaction();
} catch (e) {
this.logger.error(e);
await userSession.abortTransaction();
await session.abortTransaction();
} finally {
Expand All @@ -120,7 +123,7 @@ export default class DeleteVoteServiceImpl implements DeleteVoteServiceInterface
const canUserVote = await this.canUserVote(boardId, userId, count, session, userSession);

if (!canUserVote) throw new BadRequestException(DELETE_VOTE_FAILED);
const currentCount = Math.abs(count);
let currentCount = Math.abs(count);
let card = await this.getCardService.getCardFromBoard(boardId, cardId);

if (!card) throw new BadRequestException(DELETE_VOTE_FAILED);
Expand All @@ -132,40 +135,60 @@ export default class DeleteVoteServiceImpl implements DeleteVoteServiceInterface

if (!isEmpty(userVotes)) {
mappedVotes = mappedVotes.filter((vote) => vote.toString() !== userId.toString());

userVotes.splice(0, Math.abs(currentCount));
const votesToReduce = userVotes.length / currentCount >= 1 ? currentCount : userVotes.length;
userVotes.splice(0, Math.abs(votesToReduce));

mappedVotes = mappedVotes.concat(userVotes);

try {
await this.decrementVoteUser(boardId, userId, -currentCount, userSession);
await this.decrementVoteUser(boardId, userId, -votesToReduce, userSession);
const board = await this.setCardVotes(boardId, mappedVotes, cardId, session);

if (board.modifiedCount !== 1) throw new BadRequestException(DELETE_VOTE_FAILED);

await userSession.commitTransaction();
await session.commitTransaction();
} catch (e) {
this.logger.error(e);
await userSession.abortTransaction();
await session.abortTransaction();
} finally {
await session.endSession();
await userSession.endSession();
}

return;
currentCount -= Math.abs(votesToReduce);

if (currentCount === 0) return;
}

if (!isEmpty(currentCount)) {
card = await this.getCardService.getCardFromBoard(boardId, cardId);
while (currentCount > 0) {
card = await this.getCardService.getCardFromBoard(boardId, cardId);

const item = card.items.find(({ votes: itemVotes }) =>
arrayIdToString(itemVotes as unknown as string[]).includes(userId.toString())
);

const item = card.items.find(({ votes: itemVotes }) =>
arrayIdToString(itemVotes as unknown as string[]).includes(userId.toString())
);
if (!item) return null;

if (!item) return null;
const votesOfUser = (item.votes as unknown as string[]).filter(
(vote) => vote.toString() === userId.toString()
);

await this.deleteVoteFromCard(boardId, cardId, userId, item._id.toString(), -currentCount);
const itemVotesToReduce =
votesOfUser.length / currentCount >= 1 ? currentCount : votesOfUser.length;

await this.deleteVoteFromCard(
boardId,
cardId,
userId,
item._id.toString(),
-itemVotesToReduce
);

currentCount -= itemVotesToReduce;
}
}
}

Expand Down
50 changes: 37 additions & 13 deletions frontend/src/components/Board/Card/CardFooter.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import React, { useEffect, useMemo, useState } from 'react';

import { styled } from '@/styles/stitches/stitches.config';

Expand Down Expand Up @@ -89,11 +89,14 @@ const CardFooter = ({
const {
handleVote: { mutate, status },
toastInfoMessage,
updateVote,
} = useVotes();

const user = boardUser;
const userVotes = user?.votesCount ?? 0;

const [count, setCount] = useState(0);

const calculateVotes = () => {
const cardTyped = card as CardType;
if (Object.hasOwnProperty.call(card, 'items')) {
Expand All @@ -114,38 +117,59 @@ const CardFooter = ({

const handleDeleteVote = () => {
if ((hideCards && createdBy?._id !== userId) || status === 'loading') return;
mutate({
if (maxVotes) {
toastInfoMessage(`You have ${maxVotes! - (userVotes - 1)} votes left.`);
}
updateVote({
boardId,
cardId: card._id,
socketId,
cardItemId,
isCardGroup: cardItemId === undefined,
count: -1,
count: count - 1,
userId,
fromRequest: true,
});

if (maxVotes) {
toastInfoMessage(`You have ${maxVotes! - (userVotes - 1)} votes left.`);
}
setCount((prev) => prev - 1);
};

const handleAddVote = () => {
if (status === 'loading') return;
mutate({
if (maxVotes) {
toastInfoMessage(`You have ${maxVotes - (userVotes + 1)} votes left.`);
}
updateVote({
boardId,
cardId: card._id,
socketId,
cardItemId,
isCardGroup: cardItemId === undefined,
count: 1,
count: count + 1,
userId,
fromRequest: true,
});

if (maxVotes) {
toastInfoMessage(`You have ${maxVotes - (userVotes + 1)} votes left.`);
}
setCount((prev) => prev + 1);
};

useEffect(() => {
const timer = setTimeout(() => {
if (count === 0) return;
mutate({
boardId,
cardId: card._id,
socketId,
cardItemId,
isCardGroup: cardItemId === undefined,
count,
userId,
fromRequest: true,
});
setCount(0);
}, 300);
return () => clearTimeout(timer);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [count]);

return (
<Flex align="center" gap="6" justify={!anonymous || createdByTeam ? 'between' : 'end'}>
{!anonymous && !createdByTeam && (
Expand Down
17 changes: 15 additions & 2 deletions frontend/src/helper/board/transformBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,13 @@ export const handleMergeCard = (board: BoardType, changes: MergeCardsDto) => {

if (cardGroup && selectedCard && sourceColumn && currentCardPosition !== undefined) {
try {
cardGroup.comments = cardGroup.comments.concat(selectedCard.comments);
cardGroup.votes = cardGroup.votes.concat(selectedCard.votes);
sourceColumn.cards = removeElementAtIndex(sourceColumn.cards, currentCardPosition);
cardGroup.items = addElementAtIndex(cardGroup.items, cardGroup.items.length, {
...selectedCard,
selectedCard.items.forEach((_, idx) => {
cardGroup.items = addElementAtIndex(cardGroup.items, cardGroup.items.length, {
...selectedCard.items[idx],
});
});
} catch (e) {
return boardData;
Expand All @@ -120,9 +124,18 @@ export const handleUnMergeCard = (board: BoardType, changes: RemoveFromCardGroup
cardGroup.items = cardGroup.items.filter((item) => item._id !== selectedCard._id);
if (cardGroup.items.length === 1) {
cardGroup.text = cardGroup.items[0].text;
cardGroup.items[0].comments = cardGroup.items[0].comments.concat(cardGroup.comments);
cardGroup.items[0].votes = cardGroup.items[0].votes.concat(cardGroup.votes);
cardGroup.anonymous = cardGroup.items[0].anonymous;
cardGroup.createdBy = cardGroup.items[0].createdBy;
cardGroup.createdByTeam = cardGroup.items[0].createdByTeam;
cardGroup.comments = [];
cardGroup.votes = [];
}
column.cards = addElementAtIndex(column.cards, newPosition, {
...selectedCard,
comments: [],
votes: [],
items: [selectedCard],
});
} catch (e) {
Expand Down
Loading

0 comments on commit 9cb932e

Please sign in to comment.