diff --git a/frontend/src/pages/server/channel/index.vue b/frontend/src/pages/server/channel/index.vue index 3c2e34b..d51cce8 100644 --- a/frontend/src/pages/server/channel/index.vue +++ b/frontend/src/pages/server/channel/index.vue @@ -274,7 +274,7 @@ watch(isAtBottom, async (atBottom) => { watch(channelId, async (newChannelId) => { if (newChannelId) { - if (props.serverId) await emojiStore.fetchEmojis(props.serverId); + await emojiStore.fetchEmojis(props.serverId ?? null); await messageStore.fetchMessages(newChannelId); await scrollToBottom(); await markCurrentChannelRead(newChannelId); diff --git a/frontend/src/stores/conversation.ts b/frontend/src/stores/conversation.ts index f81ce43..1d79163 100644 --- a/frontend/src/stores/conversation.ts +++ b/frontend/src/stores/conversation.ts @@ -43,6 +43,14 @@ export const useConversationStore = defineStore('conversation', { const conversation = this.conversations.find(item => item.id === id) if (conversation) conversation.unread_count = unreadCount }, + applyIncomingMessage(message: { channel_id: string; content: string }) { + const conversation = this.conversations.find(item => item.id === message.channel_id) + if (!conversation) return + conversation.last_message = message.content + conversation.unread_count += 1 + conversation.updated_at = new Date().toISOString() + this.conversations = [conversation, ...this.conversations.filter(item => item.id !== conversation.id)] + }, reset() { this.conversations = []; this.loading = false } } }) diff --git a/frontend/src/stores/emoji.ts b/frontend/src/stores/emoji.ts index 6a97460..bc6d228 100644 --- a/frontend/src/stores/emoji.ts +++ b/frontend/src/stores/emoji.ts @@ -23,11 +23,14 @@ export const useEmojiStore = defineStore("emoji", { emojisById: (state): Record => Object.fromEntries(state.emojis.map(emoji => [emoji.id, emoji])), }, actions: { - async fetchEmojis(serverId: string, force = false) { + async fetchEmojis(serverId: string | null = null, force = false) { if (!force && this.activeServerId === serverId && this.emojis.length) return; this.loading = true; try { - const response = await useApi().get(`/emojis?server_id=${encodeURIComponent(serverId)}`); + const url = serverId === null + ? '/emojis' + : `/emojis?server_id=${encodeURIComponent(serverId)}`; + const response = await useApi().get(url); if (!response.ok) throw new Error(`Emoji loading failed (${response.status})`); this.emojis = await response.json() as Emoji[]; this.activeServerId = serverId; diff --git a/frontend/src/stores/message.ts b/frontend/src/stores/message.ts index 6bc6d6d..a327a50 100644 --- a/frontend/src/stores/message.ts +++ b/frontend/src/stores/message.ts @@ -5,6 +5,7 @@ import {useServerStore} from "@/stores/server.ts"; import {useAuthStore} from "@/stores/auth.ts"; import {useNotificationStore} from "@/stores/notification.ts"; import {useEmojiStore} from "@/stores/emoji.ts"; +import {useConversationStore} from "@/stores/conversation.ts"; // Change this value to adjust the maximum number of messages kept in the DOM. // Directional loads automatically use half of this window. @@ -350,7 +351,8 @@ export const useMessageStore = defineStore("message", { const isActiveChannel = message.channel_id === this.activeChannelId; if (!isActiveChannel) { if (fromGateway && !isOwnMessage) { - serverStore.applyIncomingMessage(message.server_id, message.channel_id); + if (message.server_id) serverStore.applyIncomingMessage(message.server_id, message.channel_id); + else useConversationStore().applyIncomingMessage(message); notificationStore.show("Nouveau message", "Un nouveau message est arrivé dans un autre canal."); } return; @@ -365,7 +367,8 @@ export const useMessageStore = defineStore("message", { if (!this.isAtBottom && this.newestId && message.id > this.newestId) { this.hasMoreAfter = true; if (fromGateway && !isOwnMessage) { - serverStore.applyIncomingMessage(message.server_id, message.channel_id); + if (message.server_id) serverStore.applyIncomingMessage(message.server_id, message.channel_id); + else useConversationStore().applyIncomingMessage(message); } return; } @@ -376,7 +379,8 @@ export const useMessageStore = defineStore("message", { if (this.isAtBottom) { this.scrollToBottomRequested = true; } else if (fromGateway && !isOwnMessage) { - serverStore.applyIncomingMessage(message.server_id, message.channel_id); + if (message.server_id) serverStore.applyIncomingMessage(message.server_id, message.channel_id); + else useConversationStore().applyIncomingMessage(message); } }, diff --git a/src/routes/conversation/handlers.rs b/src/routes/conversation/handlers.rs index 32ef342..d4549cc 100644 --- a/src/routes/conversation/handlers.rs +++ b/src/routes/conversation/handlers.rs @@ -36,6 +36,7 @@ async fn create_channel(state: &AppState, ids: &[Uuid]) -> Result = dm_channels.into_iter().map(|item| item.id).collect(); + if !dm_ids.is_empty() { + let members = channel_user::Entity::find() + .filter(channel_user::Column::ChannelId.is_in(dm_ids)) + .all(&repositories.channel.context.db) + .await?; + for member in members { + channel_users.entry(member.channel_id).or_default().insert(member.user_id); + user_channels.entry(member.user_id).or_default().insert(member.channel_id); + } + } + *self.channel_users.write() = channel_users; *self.user_channels.write() = user_channels; Ok(()) @@ -50,6 +69,24 @@ impl RealtimeRegistry { .unwrap_or_default() } + pub fn set_channel_users(&self, channel_id: Uuid, users: impl IntoIterator) { + let users: HashSet<_> = users.into_iter().collect(); + let old = { + let mut by_channel = self.channel_users.write(); + by_channel.insert(channel_id, users.clone()).unwrap_or_default() + }; + let mut by_user = self.user_channels.write(); + for user_id in old.difference(&users) { + if let Some(channels) = by_user.get_mut(user_id) { + channels.remove(&channel_id); + if channels.is_empty() { by_user.remove(user_id); } + } + } + for user_id in users { + by_user.entry(user_id).or_default().insert(channel_id); + } + } + pub fn set_user_channels(&self, user_id: Uuid, channels: impl IntoIterator) { let channels: HashSet<_> = channels.into_iter().collect(); let old = self @@ -157,3 +194,27 @@ impl RealtimeRegistry { ); } } + +#[cfg(test)] +mod tests { + use super::RealtimeRegistry; + use uuid::Uuid; + + #[test] + fn indexes_dm_members_by_channel_and_user() { + let registry = RealtimeRegistry::default(); + let channel_id = Uuid::new_v4(); + let first = Uuid::new_v4(); + let second = Uuid::new_v4(); + + registry.set_channel_users(channel_id, [first, second]); + + assert_eq!(registry.users_for_channel(channel_id).len(), 2); + assert!(registry.user_channels.read().get(&first).unwrap().contains(&channel_id)); + assert!(registry.user_channels.read().get(&second).unwrap().contains(&channel_id)); + + registry.set_channel_users(channel_id, [second]); + assert!(!registry.user_channels.read().contains_key(&first)); + assert!(registry.users_for_channel(channel_id).contains(&second)); + } +}