mirror of
https://github.com/temetro/temetro.git
synced 2026-08-18 22:26:19 +00:00
backend: per-conversation unread counts
Count messages from others newer than the caller's read pointer alongside the existing last-message lookup, expose it as unreadCount on ConversationSummary, and derive the unread boolean from it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { and, asc, desc, eq, inArray } from "drizzle-orm";
|
import { and, asc, count, desc, eq, gt, inArray, ne } from "drizzle-orm";
|
||||||
|
|
||||||
import { db } from "../db/index.js";
|
import { db } from "../db/index.js";
|
||||||
import { member, user } from "../db/schema/auth.js";
|
import { member, user } from "../db/schema/auth.js";
|
||||||
@@ -96,8 +96,9 @@ async function buildSummaries(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const lastByConv = new Map<string, ConversationMessage | null>();
|
const lastByConv = new Map<string, ConversationMessage | null>();
|
||||||
|
const unreadByConv = new Map<string, number>();
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
convIds.map(async (convId) => {
|
base.map(async (b) => {
|
||||||
const [row] = await db
|
const [row] = await db
|
||||||
.select({
|
.select({
|
||||||
id: messages.id,
|
id: messages.id,
|
||||||
@@ -109,13 +110,25 @@ async function buildSummaries(
|
|||||||
})
|
})
|
||||||
.from(messages)
|
.from(messages)
|
||||||
.innerJoin(user, eq(user.id, messages.senderId))
|
.innerJoin(user, eq(user.id, messages.senderId))
|
||||||
.where(eq(messages.conversationId, convId))
|
.where(eq(messages.conversationId, b.convId))
|
||||||
.orderBy(desc(messages.createdAt))
|
.orderBy(desc(messages.createdAt))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
lastByConv.set(
|
lastByConv.set(
|
||||||
convId,
|
b.convId,
|
||||||
row ? { ...row, createdAt: row.createdAt.toISOString() } : null,
|
row ? { ...row, createdAt: row.createdAt.toISOString() } : null,
|
||||||
);
|
);
|
||||||
|
// Messages from others newer than the caller's read pointer.
|
||||||
|
const [cnt] = await db
|
||||||
|
.select({ value: count() })
|
||||||
|
.from(messages)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(messages.conversationId, b.convId),
|
||||||
|
ne(messages.senderId, userId),
|
||||||
|
b.lastReadAt ? gt(messages.createdAt, b.lastReadAt) : undefined,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
unreadByConv.set(b.convId, cnt?.value ?? 0);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -128,17 +141,15 @@ async function buildSummaries(
|
|||||||
? others.map((p) => p.name).join(", ") || "Group"
|
? others.map((p) => p.name).join(", ") || "Group"
|
||||||
: (others[0]?.name ?? "Conversation"));
|
: (others[0]?.name ?? "Conversation"));
|
||||||
const lastMessage = lastByConv.get(b.convId) ?? null;
|
const lastMessage = lastByConv.get(b.convId) ?? null;
|
||||||
const unread =
|
const unreadCount = unreadByConv.get(b.convId) ?? 0;
|
||||||
!!lastMessage &&
|
|
||||||
lastMessage.senderId !== userId &&
|
|
||||||
(!b.lastReadAt || new Date(lastMessage.createdAt) > b.lastReadAt);
|
|
||||||
return {
|
return {
|
||||||
id: b.convId,
|
id: b.convId,
|
||||||
name: displayName,
|
name: displayName,
|
||||||
isGroup: b.isGroup,
|
isGroup: b.isGroup,
|
||||||
participants,
|
participants,
|
||||||
lastMessage,
|
lastMessage,
|
||||||
unread,
|
unread: unreadCount > 0,
|
||||||
|
unreadCount,
|
||||||
updatedAt: b.updatedAt.toISOString(),
|
updatedAt: b.updatedAt.toISOString(),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,5 +21,7 @@ export type ConversationSummary = {
|
|||||||
participants: Participant[];
|
participants: Participant[];
|
||||||
lastMessage: ConversationMessage | null;
|
lastMessage: ConversationMessage | null;
|
||||||
unread: boolean;
|
unread: boolean;
|
||||||
|
// Messages from others newer than the caller's read pointer.
|
||||||
|
unreadCount: number;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user