Skip to content

Commit

Permalink
feat: implement block users (#684)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dun-sin authored Aug 23, 2024
1 parent 1081b93 commit 0c37056
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 3 deletions.
63 changes: 62 additions & 1 deletion client/src/components/Anonymous.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import { createClassesFromArray, isExplicitDisconnection } from 'src/lib/utils';

import useKeyPress, { ShortcutFlags } from 'src/hooks/useKeyPress';
import useCheckTimePassed from 'src/hooks/useCheckTimePassed';
import { useAuth } from 'src/context/AuthContext';
import { api } from 'src/lib/axios';

const centerItems = `flex items-center justify-center`;

Expand All @@ -36,6 +38,7 @@ const Anonymous = ({

}) => {
const { app, endSearch } = useApp();
const { authState } = useAuth()
const { currentChatId, onlineStatus } = app;
const { clearTimer } = useCheckTimePassed();

Expand All @@ -54,7 +57,7 @@ const Anonymous = ({
const typingStatusTimeoutRef = useRef(null);

const navigate = useNavigate();
const { closeChat } = useChat();
const { messages: state, closeChat } = useChat();
const { setDialog } = useDialog();

const onDisplay = useCallback(({ isTyping, chatId }) => {
Expand Down Expand Up @@ -170,6 +173,59 @@ const Anonymous = ({
});
};

const blockUser = async () => {
// Get the other user id
const chattingPartnersId = state[currentChatId]?.userIds.find(
id => id !== authState.loginId && id !== authState.email
);

if (!chattingPartnersId) {
return { success: false, message: "could not find user to block" };
}

try {
const res = await api.post('/blockUser', {
userIdToBlock: chattingPartnersId,
currentUserId: authState.loginId
});

if (res.status === 200) {
return { success: true };
} else {
return { success: false, message: "Error reporting user" };
}
} catch (error) {
console.error("Error in reportUser:", error);
return { success: false, message: "An unexpected error occurred" };
}
}

const handleBlock = async () => {
// Check if user have an account i.e. not a anonymous user
if(authState.loginType === "anonymous") {
setDialog({
isOpen: true,
text: "You have to create an account first to access this feature!",
yesBtnText: "Create an account",
noBtnText: "Back to chat",
handler: () => navigate("/profile")
})
return
}

try {
const result = await blockUser();
if (result.success) {
alert('User blocked successfully');
closeChatHandler(false)
} else {
alert(result.message || "Error blocking user. Please try again later.");
}
} catch (err) {
console.error("Error in handleBlock:", err);
}
}

useKeyPress(['x'], () => handleClose(), ShortcutFlags.ctrl | ShortcutFlags.shift);
useKeyPress(['n'], () => handleClose(true), ShortcutFlags.ctrl | ShortcutFlags.alt);

Expand Down Expand Up @@ -279,6 +335,11 @@ const Anonymous = ({
<span className="text-gray-500 text-xs">Ctrl + Alt + N</span>
</div>
</Dropdown.Item>
<Dropdown.Item onClick={() => handleBlock()} className="sm:w-[200px]">
<div className="flex items-center justify-between gap-2 flex-wrap">
<span className='text-red'>Block User</span>
</div>
</Dropdown.Item>
</Dropdown>
</div>
<div
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/Chat.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -607,4 +607,4 @@ const Chat = () => {
);
};

export default Chat;
export default Chat;
25 changes: 25 additions & 0 deletions server/controllers/userController.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ const imageUpload = multer({ storage: storage });
const User = require('../models/UserModel');
const { emailValidator, generateObjectId } = require('../utils/helper');

const { isUserBlocked, blockUser } = require('../utils/lib.js');
const {
OK,
NOT_FOUND,
INTERNAL_SERVER_ERROR,
CONFLICT,
BAD_REQUEST,
} = require('../httpStatusCodes.js');

const createUserWithAutoId = async (email) => {
Expand Down Expand Up @@ -146,6 +148,27 @@ const deleteUser = async (req, res) => {
}
};

const blockUserHandler = async (req, res) => {
const {userIdToBlock, currentUserId} = req.body

try {
// Check if the user is already blocked
if (await isUserBlocked([userIdToBlock, currentUserId])) {
return res.status(BAD_REQUEST).json({ message: "This user is already blocked." });
}

// Block the user
await blockUser(userIdToBlock, currentUserId)

res.status(OK).json({ message: "User blocked successfully" });
} catch (error) {
console.error(error);
return res
.status(INTERNAL_SERVER_ERROR)
.json({ error: 'Internal server error' });
}
}

UserRouter.route('/login').post(emailValidator, loginUser);
UserRouter.route('/profile').post(
imageUpload.single('profileImage'),
Expand All @@ -155,4 +178,6 @@ UserRouter.route('/profile').post(
UserRouter.route('/profile/:email').get(getProfile);
UserRouter.route('/deleteUser').delete(emailValidator, deleteUser); //Email validation applied to the required request handlers

UserRouter.route("/blockUser").post(blockUserHandler)

module.exports = UserRouter;
5 changes: 5 additions & 0 deletions server/models/UserModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ const UserSchema = new Schema(
type: Schema.Types.ObjectId,
ref: 'Chat',
},
blockedUsers: {
type: [String],
default: []
}
},
{
timestamps: true,
Expand All @@ -63,6 +67,7 @@ const UserSchema = new Schema(
socketIds: [],
currentChatId: this.currentChat?._id?.toString() || null,
chatIds: [],
blockedUsers: []
};
},
},
Expand Down
11 changes: 10 additions & 1 deletion server/sockets/join.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const {
getRandomPairFromWaitingList,
createChat,
getActiveUser,
isUserBlocked,
} = require('../utils/lib');

/**
Expand All @@ -19,16 +20,24 @@ const {
*
* @param {Server} io
*/

const matchMaker = async (io) => {
while (getWaitingUserLen() > 1) {
const chat = await createChat(getRandomPairFromWaitingList());
const users = getRandomPairFromWaitingList();

// Check if either user is blocked
if ( await isUserBlocked(users) ) {
continue
}

const chat = await createChat(users);
io.to(chat.id).emit(NEW_EVENT_JOINED, {
roomId: chat.id,
userIds: chat.userIds,
});
}
};

module.exports = (io, socket) => {
socket.on(NEW_EVENT_JOIN, ({ loginId, email }) => {
/**
Expand Down
35 changes: 35 additions & 0 deletions server/utils/lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const { ObjectId } = require('mongodb');

const ActiveUser = require('../models/UserModel');
const Chat = require('../models/ChatModel');
const User = require('../models/UserModel');
const Message = require('../models/MessageModel');
const { generateObjectId } = require('./helper');

Expand Down Expand Up @@ -502,6 +503,38 @@ function getWaitingUserLen() {
return Object.keys(waitingUsers).length;
}

async function blockUser(userIdToBlock, currentUserId) {
try {
await User.findOneAndUpdate({ loginId: currentUserId }, {
$addToSet: { blockedUsers: userIdToBlock }
});
} catch (error) {
console.log(`error blocking user: ${error}`);
}
}

async function isUserBlocked(users) {
const [userOne, userTwo] = users;

try {
const [userOneData, userTwoData] = await Promise.all([
User.findOne({ loginId: userOne.loginId }),
User.findOne({ loginId: userTwo.loginId })
]);

// Using 'OR' because one of the users might be anonymously logged in,
// and in such cases, userData could be null.
if (userOneData || userTwoData) {
const userOneBlocked = userOneData?.blockedUsers.includes(userTwo.loginId);
const userTwoBlocked = userTwoData?.blockedUsers.includes(userOne.loginId);
return userOneBlocked || userTwoBlocked;
}
return false;
} catch (error) {
console.log(`error checking if users are blocked: ${error}`);
}
}

module.exports = {
init,
createChat,
Expand All @@ -521,4 +554,6 @@ module.exports = {
addToWaitingList,
delActiveUser,
seenMessage,
blockUser,
isUserBlocked
};

0 comments on commit 0c37056

Please sign in to comment.