Skip to content

Commit

Permalink
fix: unread count mismatch
Browse files Browse the repository at this point in the history
  • Loading branch information
aseerkt committed Aug 15, 2024
1 parent ad4d982 commit db53471
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 106 deletions.
84 changes: 51 additions & 33 deletions server/src/modules/groups/groups.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ import {
isNull,
like,
lt,
ne,
notExists,
notInArray,
or,
sql,
} from 'drizzle-orm'
import { unionAll } from 'drizzle-orm/pg-core'
import { union } from 'drizzle-orm/pg-core'
import { RequestHandler } from 'express'
import { membersTable } from '../members/members.schema'
import { addMembers } from '../members/members.service'
Expand Down Expand Up @@ -146,12 +147,23 @@ export const listGroups: RequestHandler = async (req, res, next) => {
}
}

export const listUserGroups: RequestHandler = async (req, res, next) => {
export const listUserChats: RequestHandler = async (req, res, next) => {
try {
const userGroups = db
.$with('user_groups')
.as(
db
.select(getTableColumns(groupsTable))
.from(groupsTable)
.innerJoin(membersTable, eq(membersTable.groupId, groupsTable.id))
.where(eq(membersTable.userId, req.user!.id)),
)

const groupMessagesWithRowNumber = db
.$with('group_messages_with_row_number')
.as(
db
.with(userGroups)
.select({
...getTableColumns(messagesTable),
rowNumber: rowNumber().over<number>({
Expand All @@ -161,14 +173,14 @@ export const listUserGroups: RequestHandler = async (req, res, next) => {
}),
})
.from(messagesTable)
.innerJoin(userGroups, eq(userGroups.id, messagesTable.groupId))
.where(isNotNull(messagesTable.groupId)),
)

const groupsWithLastMessage = db.$with('groups_with_last_message').as(
const userGroupsWithLastMessage = db.$with('groups_with_last_message').as(
db
.with(groupMessagesWithRowNumber)
.select({
chatName: groupsTable.name,
chatName: userGroups.name,
groupId: groupMessagesWithRowNumber.groupId,
partnerId: groupMessagesWithRowNumber.receiverId,
lastMessage: {
Expand All @@ -177,19 +189,17 @@ export const listUserGroups: RequestHandler = async (req, res, next) => {
},
lastActivity: coalesce(
groupMessagesWithRowNumber.createdAt,
groupsTable.createdAt,
userGroups.createdAt,
).as('last_activity'),
})
.from(groupsTable)
.from(userGroups)
.leftJoin(
groupMessagesWithRowNumber,
and(
eq(groupsTable.id, groupMessagesWithRowNumber.groupId),
eq(userGroups.id, groupMessagesWithRowNumber.groupId),
eq(groupMessagesWithRowNumber.rowNumber, 1),
),
)
.innerJoin(membersTable, eq(membersTable.groupId, groupsTable.id))
.where(eq(membersTable.userId, req.user!.id)),
),
)

const directMessagesWithPartner = db
Expand Down Expand Up @@ -231,7 +241,6 @@ export const listUserGroups: RequestHandler = async (req, res, next) => {
.$with('direct_messages_with_last_activity')
.as(
db
.with(directMessagesWithPartner)
.select({
chatName: usersTable.username,
groupId: directMessagesWithPartner.groupId,
Expand All @@ -251,15 +260,21 @@ export const listUserGroups: RequestHandler = async (req, res, next) => {
)

const unreadCounts = db.$with('unread_counts').as(
unionAll(
union(
db
.select({
groupId: sql`${groupsTable.id}`.as('unread_group_id'),
receiverId: nullAs('unread_receiver_id'),
groupId: sql`${userGroups.id}`.as('unread_group_id'),
partnerId: nullAs('unread_partner_id'),
unreadCount: count(messagesTable.id).as('unread_count'),
})
.from(groupsTable)
.leftJoin(messagesTable, eq(groupsTable.id, messagesTable.groupId))
.from(userGroups)
.leftJoin(
messagesTable,
and(
eq(messagesTable.groupId, userGroups.id),
ne(messagesTable.senderId, req.user!.id),
),
)
.leftJoin(
messageRecipientsTable,
and(
Expand All @@ -268,13 +283,11 @@ export const listUserGroups: RequestHandler = async (req, res, next) => {
),
)
.where(isNull(messageRecipientsTable.messageId))
.groupBy(groupsTable.id),
.groupBy(userGroups.id),
db
.select({
groupId: nullAs('unread_group_id'),
receiverId: sql`${messagesTable.receiverId}`.as(
'unread_receiver_id',
),
partnerId: sql`${messagesTable.senderId}`.as('unread_partner_id'),
unreadCount: count(messagesTable.id).as('unread_count'),
})
.from(messagesTable)
Expand All @@ -295,24 +308,29 @@ export const listUserGroups: RequestHandler = async (req, res, next) => {
),
),
)
.groupBy(messagesTable.receiverId),
.groupBy(messagesTable.senderId),
),
)

const combinedChats = db
.$with('combined_chats')
.as(
unionAll(
db.with(groupsWithLastMessage).select().from(groupsWithLastMessage),
db
.with(directMessagesWithLastActivity)
.select()
.from(directMessagesWithLastActivity),
union(
db.select().from(userGroupsWithLastMessage),
db.select().from(directMessagesWithLastActivity),
),
)

const qb = db
.with(combinedChats, unreadCounts)
.with(
userGroups,
groupMessagesWithRowNumber,
userGroupsWithLastMessage,
directMessagesWithPartner,
directMessagesWithLastActivity,
combinedChats,
unreadCounts,
)
.select({
groupId: combinedChats.groupId,
partnerId: combinedChats.partnerId,
Expand All @@ -326,9 +344,9 @@ export const listUserGroups: RequestHandler = async (req, res, next) => {
.from(combinedChats)
.leftJoin(
unreadCounts,
and(
or(
eq(combinedChats.groupId, unreadCounts.groupId),
eq(combinedChats.partnerId, unreadCounts.receiverId),
eq(combinedChats.partnerId, unreadCounts.partnerId),
),
)
.$dynamic()
Expand All @@ -337,9 +355,9 @@ export const listUserGroups: RequestHandler = async (req, res, next) => {

const result = await withPagination(qb, {
cursorSelect: 'lastActivity',
orderBy: [desc(groupsWithLastMessage.lastActivity)],
orderBy: [desc(userGroupsWithLastMessage.lastActivity)],
where: cursor
? lt(groupsWithLastMessage.lastActivity, cursor)
? lt(userGroupsWithLastMessage.lastActivity, cursor)
: undefined,
limit,
})
Expand Down
6 changes: 5 additions & 1 deletion server/src/modules/messages/messages.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,12 @@ export const listMessages: RequestHandler = async (req, res, next) => {
groupId ? eq(messagesTable.groupId, groupId) : undefined,
partnerId
? or(
eq(messagesTable.receiverId, partnerId),
and(
eq(messagesTable.receiverId, partnerId),
eq(messagesTable.senderId, req.user!.id),
),
and(
eq(messagesTable.receiverId, req.user!.id),
eq(messagesTable.senderId, partnerId),
isNull(messagesTable.groupId),
),
Expand Down
13 changes: 9 additions & 4 deletions server/src/modules/messages/messages.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,14 @@ export const markMessageAsRead = async (

export const markChatMessagesAsRead = async ({
groupId,
receiverId,
partnerId,
recipientId,
}: {
groupId?: number
receiverId?: number
partnerId?: number
recipientId: number
}) => {
if (!groupId && !receiverId) {
if (!groupId && !partnerId) {
throw new Error(
'markChatMessagesAsRead: message does not belongs to either group or dm',
)
Expand Down Expand Up @@ -169,7 +169,12 @@ export const markChatMessagesAsRead = async ({
.where(
and(
groupId ? eq(messagesTable.groupId, groupId) : undefined,
receiverId ? eq(messagesTable.receiverId, receiverId) : undefined,
partnerId
? and(
eq(messagesTable.senderId, partnerId),
eq(messagesTable.receiverId, recipientId),
)
: undefined,
isNull(messageRecipientsTable.messageId),
),
)
Expand Down
4 changes: 2 additions & 2 deletions server/src/modules/users/users.routes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { auth } from '@/middlewares'
import { Router } from 'express'
import { listUserGroups } from '../groups/groups.controller'
import { listUserChats } from '../groups/groups.controller'
import { getUser, getUsers, loginUser, signUpUser } from './users.controller'

export const router = Router()
Expand All @@ -11,4 +11,4 @@ router.post('/login', loginUser)
router.get('/', auth, getUsers)
router.get('/:userId', auth, getUser)

router.get('/:userId/groups', auth, listUserGroups)
router.get('/:userId/groups', auth, listUserChats)
4 changes: 2 additions & 2 deletions server/src/scripts/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { hash } from 'argon2'
import 'colors'

const USER_PASSWORD = 'bob@123'
const USER_COUNT = 50
const USER_COUNT = 100

const GROUP_COUNT_PER_USER = 5
const GROUP_COUNT_PER_USER = 20
const MEMBER_COUNT_PER_GROUP = 5
const MESSAGE_PER_MEMBER = 5
const BATCH_SIZE = 100
Expand Down
6 changes: 3 additions & 3 deletions server/src/socket/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,17 +145,17 @@ export const registerSocketEvents = (io: TypedIOServer) => {
io.to(roomKeys.USER_KEY(messageSenderId)).emit('messageRead', messageId)
})

socket.on('markChatMessagesAsRead', async ({ groupId, receiverId }) => {
socket.on('markChatMessagesAsRead', async ({ groupId, partnerId }) => {
const unreadMessages = await markChatMessagesAsRead({
groupId,
receiverId,
partnerId,
recipientId: socket.data.user.id,
})

// let the current user know that the unread messages of the group is marked as read
io.to(roomKeys.USER_KEY(socket.data.user.id)).emit('chatMarkedAsRead', {
groupId,
receiverId,
partnerId,
})
// let the message senders know their message is read
for (const message of unreadMessages) {
Expand Down
4 changes: 2 additions & 2 deletions server/src/socket/socket.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface ServerToClientEvents {
groupDeleted: (groupId: number) => void
messageRead: (messageId: number) => void
messageDeleted: (messageId: number) => void
chatMarkedAsRead: (args: { groupId?: number; receiverId?: number }) => void
chatMarkedAsRead: (args: { groupId?: number; partnerId?: number }) => void
typingUsers: (users: { id: number; username: string }[]) => void
}

Expand All @@ -37,7 +37,7 @@ export interface ClientToServerEvents {
markMessageAsRead: (messageId: number) => void
markChatMessagesAsRead: (args: {
groupId?: number
receiverId?: number
partnerId?: number
}) => void
typing: (args: { chatId: number; mode: ChatMode; isTyping: boolean }) => void
}
Expand Down
22 changes: 1 addition & 21 deletions web/src/features/group/components/UserChatList.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,19 @@
import { Alert } from '@/components/Alert'
import { Skeleton } from '@/components/Skeleton'
import { useAuth } from '@/hooks/useAuth'
import { useInView } from '@/hooks/useInView'
import { useInfiniteQuery } from '@tanstack/react-query'
import { Fragment, useRef } from 'react'
import { fetchUserGroups } from '../group.service'
import { useChatSocketHandle } from '../hooks/useChatSocketHandle'
import { JoinGroupsForm } from './JoinGroupForm'
import { UserChatItem } from './UserChatItem'

export const UserChatList = () => {
const { auth } = useAuth()
const { data, isLoading, isSuccess, hasNextPage, fetchNextPage, error } =
useInfiniteQuery({
queryKey: ['userGroups', auth],
queryFn: async ({ pageParam }) => {
return fetchUserGroups({
userId: auth!.id,
limit: 15,
cursor: pageParam,
})
},
initialPageParam: null as number | null,
getNextPageParam(lastPage) {
return lastPage.cursor ? lastPage.cursor : undefined
},
enabled: Boolean(auth?.id),
})
useChatSocketHandle()

const listRef = useRef<HTMLUListElement>(null)

const watchElement = useInView(listRef, fetchNextPage, hasNextPage)

useChatSocketHandle()

let content

if (error) {
Expand Down
2 changes: 1 addition & 1 deletion web/src/features/group/group.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
TGetUserGroupsQueryVariables,
} from './group.interface'

export const fetchUserGroups = async ({
export const fetchUserChats = async ({
userId,
...params
}: TGetUserGroupsQueryVariables): Promise<IPaginatedResult<IChat>> =>
Expand Down
Loading

0 comments on commit db53471

Please sign in to comment.