29. Show unread messages counter

In the previous tutorial, we created functionality to send messages in group chat. Now we need to show the number of unread messages to all the other members of the group.

When you open the group’s page where you see all your joined groups, you should also see the counter of unread messages. First, we need to add another nested array of “groups” inside the user’s collection. We can add that in our “registration” route in server.js:

groups: [],

But this will only add the array for newly registered users. To add in all the previous users, we will be using seeders. Seeders are used to populate the data in the database, so you do not have to write each entry manually. You can create a script and run it once.

Create a seeder

To create a seeder, first, create a file named “seeders.js” inside the api/modules folder. Write the following code in that file:

module.exports = {
	init: function (app, express) {
		const router = express.Router();

		router.get("/groups", async function (request, result) {
			// get all groups
			const groups = await db.collection("groups").find({}).toArray();

			const usersUpdated = [];

			// get all members
			for (let a = 0; a < groups.length; a++) {
				for (let b = 0; b < groups[a].members.length; b++) {
					// check if each user has groups array
					const userMember = await db.collection("users").findOne({
						$and: [{
							_id: groups[a].members[b].user._id
						}, {
							"groups._id": {
								$ne: groups[a]._id
							}
						}]
					});

					// if not, then push
					if (userMember != null) {
						usersUpdated.push(userMember);
						
						await db.collection("users").findOneAndUpdate({
							_id: groups[a].members[b].user._id
						}, {
							$push: {
								groups: {
									_id: groups[a]._id,
									unreadMessages: 0
								}
							}
						});
					}
				}

				// do the same for admin too
				const userMember = await db.collection("users").findOne({
					$and: [{
						_id: groups[a].createdBy._id
					}, {
						"groups._id": {
							$ne: groups[a]._id
						}
					}]
				});

				// if not, then push
				if (userMember != null) {
					usersUpdated.push(userMember);
					
					await db.collection("users").findOneAndUpdate({
						_id: groups[a].createdBy._id
					}, {
						$push: {
							groups: {
								_id: groups[a]._id,
								unreadMessages: 0
							}
						}
					});
				}
			}

			result.json(JSON.stringify(usersUpdated));
		});

		app.use("/seeder", router);
	}
};

Comments have been added with each line for an explanation. We have used MongoDB $ne operator which stands for negation. In simple words, it says “where not equal to”. You can learn more about it from MongoDB’s official website.

Now include that seeder in your server.js:

const seeders = require("./modules/seeders");

And, after the database is connected, we need to call its init method.

seeders.init(app, express);

After that, you need to access the following URL in your web browser.

http://localhost:3000/seeder/groups

As soon as you run it, you will see the users to whom the “groups” array has been added. You only need to run it once. Even if you run twice, you will see an empty array because all users have already been updated. You can check from MongoDB compass as well.

Change in create group API

When a new group is created, we need to set the “groups” array in the admin’s collection as well. So paste the following code in your api/modules/groups.js file in the “/add” POST route:

// add in admin groups nested array
const userMember = await db.collection("users").findOne({
    $and: [{
        _id: user._id
    }, {
        "groups._id": {
            $ne: object._id
        }
    }]
});

if (userMember != null) {
    await db.collection("users").findOneAndUpdate({
        _id: user._id
    }, {
        $push: {
            groups: {
                _id: object._id,
                unreadMessages: 0
            }
        }
    });
}

Change in invite member API

The same goes when you send an invitation to someone to join the group. The following code goes in the “/inviteMember” POST route:

// add in user's groups array if not exists
const userMember = await db.collection("users").findOne({
    $and: [{
        _id: member._id
    }, {
        "groups._id": {
            $ne: group._id
        }
    }]
});

if (userMember != null) {
    await db.collection("users").findOneAndUpdate({
        _id: member._id
    }, {
        $push: {
            groups: {
                _id: group._id,
                unreadMessages: 0
            }
        }
    });
}

This will check if the user to whom I am sending an invitation already has this group in his document ? If not, then it will simply push it into that user’s document.

Update unread messages counter

When someone sends a message in that group, we need to increment the unreadMessages value of each member’s as well as the admin’s document. Except for the person who sends a message, of course.

So write the following code in your “/sendMessage” POST route in the “groups.js” module:

// add in all members nested array
const membersIds = [];
for (let a = 0; a < group.members.length; a++) {
    if (group.members[a].user._id.toString() != user._id.toString()) {
        membersIds.push(ObjectId(group.members[a].user._id));
    }
}

if (membersIds.length > 0) {
    await db.collection("users").updateMany({
        $and: [{
            _id: {
                $in: membersIds
            }
        }, {
            "groups._id": group._id
        }]
    }, {
        $inc: {
            "groups.$.unreadMessages": 1
        }
    }, {
        upsert: true
    });
}

// add in admin nested array
if (group.createdBy._id.toString() != user._id.toString()) {
    await db.collection("users").updateMany({
        $and: [{
            _id: group.createdBy._id
        }, {
            "groups._id": group._id
        }]
    }, {
        $inc: {
            "groups.$.unreadMessages": 1
        }
    }, {
        upsert: true
    });
}

Try sending a message in a group. And you will see in MongoDB compass that the unread messages counter will be incremented each you send a message. But for other members only.

Show unread messages counter

Now we need to show that counter in the group’s list. So open your GroupsComponent.vue and create the following method in the methods object:

unreadMessages: function (group) {
	if (this.user == null) {
		return 0
	}
	
	for (let a = 0; a < this.user.groups.length; a++) {
		if (this.user.groups[a]._id.toString() == group._id.toString()) {
			return this.user.groups[a].unreadMessages
		}
	}
	return 0
},

This will return the number of unread messages the logged-in user has from a specific group. We will pass the group object as an argument.

After that, in the for loop where we are displaying all groups in a <tbody>. In the first <td> tag, after we are displaying the group name, write the following line:

<!-- unread messages I have from this group -->
<span v-if="unreadMessages(group) > 0" v-text="' (' + unreadMessages(group) + ')'" class="text-danger"></span>

This will display a red text along with each group that you have at least one unread message.

group unread messages
group unread messages

The next thing we need to do is to mark all messages as read when the user opens that group.

Mark group messages as read

To mark the messages as read when the group page is opened, open your GroupDetailComponent.vue and call the function markAsRead() when the component is mounted:

// get data when component is loaded
mounted: function () {
	this.getData()
	this.markAsRead()
},

Then we need to create this method in our methods object:

markAsRead: async function () {
	const formData = new FormData()
	formData.append("_id", this._id)

	const response = await axios.post(
		this.$apiURL + "/groups/markAsRead",
		formData,
		{
			headers: this.$headers
		}
	)
	console.log(response)
},

After that, we need to create an API that will handle this request. Open your api/modules/groups.js file and create the following POST route in it:

router.post("/markAsRead", auth, async function (request, result) {
    const user = request.user;
    const _id = request.fields._id;

    const group = await db.collection("groups").findOne({
        _id: ObjectId(_id)
    });

    if (group == null) {
        result.status(404).json({
            status: "error",
            message: "Group does not exists."
        });
        return;
    }

    await db.collection("users").findOneAndUpdate({
        $and: [{
            _id: user._id
        }, {
            "groups._id": group._id
        }]
    }, {
        $set: {
            "groups.$.unreadMessages": 0
        }
    });

    result.json({
        status: "success",
        message: "Messages has been marked as read."
    });
});

This will mark the unread messages as zero for that group and for the logged-in user only.





Please disable your adblocker or whitelist this site!