feat(chat): add presence metadata and improve web chat core
Some checks failed
CI / test (push) Failing after 22s

- add user last_seen_at with alembic migration and persist on realtime disconnect
- extend chat serialization with private online/last_seen, group members/online, channel subscribers
- add Redis batch presence lookup helper
- update web chat list/header to display status counters and last-seen labels
- improve delivery receipt handling using last_delivered/last_read boundaries
- include chat info panel and related API/type updates
This commit is contained in:
2026-03-08 02:02:09 +03:00
parent 51275692ac
commit e6a271f8be
17 changed files with 564 additions and 6 deletions

View File

@@ -23,6 +23,12 @@ interface ChatState {
confirmMessageByClientId: (chatId: number, clientMessageId: string, message: Message) => void;
removeOptimisticMessage: (chatId: number, clientMessageId: string) => void;
setMessageDeliveryStatus: (chatId: number, messageId: number, status: DeliveryStatus) => void;
setMessageDeliveryStatusUpTo: (
chatId: number,
maxMessageId: number,
status: DeliveryStatus,
senderId: number
) => void;
removeMessage: (chatId: number, messageId: number) => void;
restoreMessages: (chatId: number, messages: Message[]) => void;
clearChatMessages: (chatId: number) => void;
@@ -151,6 +157,31 @@ export const useChatStore = create<ChatState>((set, get) => ({
messagesByChat: { ...state.messagesByChat, [chatId]: next }
}));
},
setMessageDeliveryStatusUpTo: (chatId, maxMessageId, status, senderId) => {
const old = get().messagesByChat[chatId] ?? [];
if (!old.length) {
return;
}
const order: Record<DeliveryStatus, number> = { sending: 1, sent: 2, delivered: 3, read: 4 };
let changed = false;
const next = old.map((message) => {
if (message.sender_id !== senderId || message.id <= 0 || message.id > maxMessageId) {
return message;
}
const currentStatus = message.delivery_status ?? "sent";
if (order[status] <= order[currentStatus]) {
return message;
}
changed = true;
return { ...message, delivery_status: status, is_pending: false };
});
if (!changed) {
return;
}
set((state) => ({
messagesByChat: { ...state.messagesByChat, [chatId]: next }
}));
},
removeMessage: (chatId, messageId) => {
const old = get().messagesByChat[chatId] ?? [];
set((state) => ({