Track per-employee chat read state and surface real unread counts
Adds chat_read_state migration (employee_id + space_id, last read message + timestamp) wired through ChatService.markSpaceRead, IPC markChatSpaceRead, and an effect in ChatPage that flushes the active space whenever the latest message id changes. Shell.computeBadges now derives the Messages badge from (message.createdAt > read_state.last_read_at) minus messages the viewer sent, so the sidebar shows true unread instead of total throughput.
This commit is contained in:
1 parent
b44e0e3657
commit
1f78ae6f2e
11 files changed
+100
-3
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import type { CreateChatSpaceInput, SendMessageInput } from "../../shared/commands";
|
||||
import type { CreateChatSpaceInput, MarkChatSpaceReadInput, SendMessageInput } from "../../shared/commands";
|
||||
import { createId } from "../../shared/ids";
|
||||
import { AuditService } from "./audit-service";
|
||||
|
||||
@@ -101,6 +101,28 @@ export class ChatService {
|
||||
return id;
|
||||
}
|
||||
|
||||
markSpaceRead(input: MarkChatSpaceReadInput): void {
|
||||
const space = this.db.prepare("select id from chat_spaces where id = ? and org_id = ?")
|
||||
.get(input.spaceId, input.orgId) as { id: string } | undefined;
|
||||
if (!space) throw new Error("Chat space was not found.");
|
||||
const employee = this.db.prepare("select id from employees where id = ? and org_id = ?")
|
||||
.get(input.employeeId, input.orgId) as { id: string } | undefined;
|
||||
if (!employee) throw new Error("Employee was not found.");
|
||||
const latest = this.db.prepare(`
|
||||
select id from chat_messages
|
||||
where space_id = ? and org_id = ?
|
||||
order by created_at desc
|
||||
limit 1
|
||||
`).get(input.spaceId, input.orgId) as { id: string } | undefined;
|
||||
const now = new Date().toISOString();
|
||||
this.db.prepare(`
|
||||
insert into chat_read_state(org_id, employee_id, space_id, last_read_message_id, last_read_at)
|
||||
values (?, ?, ?, ?, ?)
|
||||
on conflict(employee_id, space_id)
|
||||
do update set last_read_message_id = excluded.last_read_message_id, last_read_at = excluded.last_read_at
|
||||
`).run(input.orgId, input.employeeId, input.spaceId, latest?.id ?? null, now);
|
||||
}
|
||||
|
||||
private recordMentions(messageId: string, body: string): void {
|
||||
const seen = new Set<string>();
|
||||
for (const match of body.matchAll(/(^|\s)@([A-Za-z][A-Za-z0-9_-]{1,63})/g)) {
|
||||
|
||||
@@ -64,6 +64,7 @@ export class SnapshotService {
|
||||
chatThreads: list(this.db, "chat_threads", "updated_at desc"),
|
||||
chatAttachments: list(this.db, "chat_attachments", "message_id asc"),
|
||||
chatPresence: list(this.db, "chat_presence", "last_seen_at desc"),
|
||||
chatReadStates: list(this.db, "chat_read_state", "last_read_at desc"),
|
||||
files,
|
||||
scheduledPosts: list(this.db, "scheduled_posts", "scheduled_for asc"),
|
||||
providers,
|
||||
|
||||
Reference in new issue
Block a user