Showing unread indicators / counts for a chat app using Phoenix channels

I’m using Phoenix channels to build a chat app similar to Discord. I’m new to Elixir and Phoenix and wouldn’t consider myself to be a backend wizard. I’m wondering how you might handle showing unread indicators / counts.

Let’s assume a user belongs to multiple organizations and each of those has multiple rooms. Similar to Discord, I’d like be able to show:

  1. An unread dot next to each organization with unreads
  2. Bold the name of the unread room within an organization

I’ve set up my channels like this:

channel "user:*", AppWeb.UserChannel

channel "org:*", AppWeb.OrgChannel

channel "room:*", AppWeb.RoomChannel

I have a somewhat working solution but it feels a bit hacky so I’m wondering what a best practice solution might look like. Currently, I’m doing this:

  1. I have organizations, rooms, messages, org_memberships and room_memberships tables.
  2. I store a last_read_message_id on the room_memberships table and use it in my ecto queries.
  3. When the user logs in, I join them to the UserChannel which fetches their organizations and each has a virtualized boolean field for unread.
  4. When the user navigates to an organization, I join them to the OrgChannel which fetches the rooms for that organization and each has a virtualized field for unread_count. I assign the rooms to the socket.
  5. When the user navigates to a room, I join them to the RoomChannel which fetches the messages for that room.
  6. When a user sends a message, I determine which users should receive that message and use Phoenix.PubSub to broadcast that back to their respective UserChannel to update the organization boolean unread field and to the OrgChannel to increment the unread_count for that room if it’s found in the socket’s rooms. These are both pushed to the client so that I can show the unread indicators as described above.

My solution also seems to kind of hit a roadblock when taking into account direct messages because they aren’t tied to a particular organization. Right now a direct message is treated the same as a regular room but with type: “dm” and organization_id: null. Open to ideas here too.

I considered sending the unread_count for all rooms a user is a member of after they log in but that seemed like a bad idea if they are in say 20+ organizations each with 20+ rooms. Maybe my intuition here is wrong and it’s ok as long as the payload is small with just the room_id, organization_id, and the unread_count?

I’m guessing this is well-trodden territory and would appreciate any tips you might have.