init
This commit is contained in:
@@ -274,7 +274,7 @@ watch(isAtBottom, async (atBottom) => {
|
|||||||
|
|
||||||
watch(channelId, async (newChannelId) => {
|
watch(channelId, async (newChannelId) => {
|
||||||
if (newChannelId) {
|
if (newChannelId) {
|
||||||
if (props.serverId) await emojiStore.fetchEmojis(props.serverId);
|
await emojiStore.fetchEmojis(props.serverId ?? null);
|
||||||
await messageStore.fetchMessages(newChannelId);
|
await messageStore.fetchMessages(newChannelId);
|
||||||
await scrollToBottom();
|
await scrollToBottom();
|
||||||
await markCurrentChannelRead(newChannelId);
|
await markCurrentChannelRead(newChannelId);
|
||||||
|
|||||||
@@ -43,6 +43,14 @@ export const useConversationStore = defineStore('conversation', {
|
|||||||
const conversation = this.conversations.find(item => item.id === id)
|
const conversation = this.conversations.find(item => item.id === id)
|
||||||
if (conversation) conversation.unread_count = unreadCount
|
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 }
|
reset() { this.conversations = []; this.loading = false }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -23,11 +23,14 @@ export const useEmojiStore = defineStore("emoji", {
|
|||||||
emojisById: (state): Record<string, Emoji> => Object.fromEntries(state.emojis.map(emoji => [emoji.id, emoji])),
|
emojisById: (state): Record<string, Emoji> => Object.fromEntries(state.emojis.map(emoji => [emoji.id, emoji])),
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async fetchEmojis(serverId: string, force = false) {
|
async fetchEmojis(serverId: string | null = null, force = false) {
|
||||||
if (!force && this.activeServerId === serverId && this.emojis.length) return;
|
if (!force && this.activeServerId === serverId && this.emojis.length) return;
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
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})`);
|
if (!response.ok) throw new Error(`Emoji loading failed (${response.status})`);
|
||||||
this.emojis = await response.json() as Emoji[];
|
this.emojis = await response.json() as Emoji[];
|
||||||
this.activeServerId = serverId;
|
this.activeServerId = serverId;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {useServerStore} from "@/stores/server.ts";
|
|||||||
import {useAuthStore} from "@/stores/auth.ts";
|
import {useAuthStore} from "@/stores/auth.ts";
|
||||||
import {useNotificationStore} from "@/stores/notification.ts";
|
import {useNotificationStore} from "@/stores/notification.ts";
|
||||||
import {useEmojiStore} from "@/stores/emoji.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.
|
// Change this value to adjust the maximum number of messages kept in the DOM.
|
||||||
// Directional loads automatically use half of this window.
|
// Directional loads automatically use half of this window.
|
||||||
@@ -350,7 +351,8 @@ export const useMessageStore = defineStore("message", {
|
|||||||
const isActiveChannel = message.channel_id === this.activeChannelId;
|
const isActiveChannel = message.channel_id === this.activeChannelId;
|
||||||
if (!isActiveChannel) {
|
if (!isActiveChannel) {
|
||||||
if (fromGateway && !isOwnMessage) {
|
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.");
|
notificationStore.show("Nouveau message", "Un nouveau message est arrivé dans un autre canal.");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -365,7 +367,8 @@ export const useMessageStore = defineStore("message", {
|
|||||||
if (!this.isAtBottom && this.newestId && message.id > this.newestId) {
|
if (!this.isAtBottom && this.newestId && message.id > this.newestId) {
|
||||||
this.hasMoreAfter = true;
|
this.hasMoreAfter = true;
|
||||||
if (fromGateway && !isOwnMessage) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
@@ -376,7 +379,8 @@ export const useMessageStore = defineStore("message", {
|
|||||||
if (this.isAtBottom) {
|
if (this.isAtBottom) {
|
||||||
this.scrollToBottomRequested = true;
|
this.scrollToBottomRequested = true;
|
||||||
} else if (fromGateway && !isOwnMessage) {
|
} 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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ async fn create_channel(state: &AppState, ids: &[Uuid]) -> Result<channel::Model
|
|||||||
channel_user::ActiveModel { channel_id: Set(channel.id), user_id: Set(*user_id), role: Set("member".into()), joined_at: Set(Utc::now()), ..Default::default() }.insert(&txn).await?;
|
channel_user::ActiveModel { channel_id: Set(channel.id), user_id: Set(*user_id), role: Set("member".into()), joined_at: Set(Utc::now()), ..Default::default() }.insert(&txn).await?;
|
||||||
}
|
}
|
||||||
txn.commit().await?;
|
txn.commit().await?;
|
||||||
|
state.services.realtime_registry.set_channel_users(channel.id, ids.iter().copied());
|
||||||
state.event_bus.emit("channel_created", channel.clone());
|
state.event_bus.emit("channel_created", channel.clone());
|
||||||
Ok(channel)
|
Ok(channel)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::models::computed_permission::PermissionScopeType;
|
use crate::models::{channel, channel_user, computed_permission::PermissionScopeType};
|
||||||
use crate::permissions::ChannelPermission;
|
use crate::permissions::ChannelPermission;
|
||||||
use crate::repositories::Repositories;
|
use crate::repositories::Repositories;
|
||||||
use event_bus::EventBus;
|
use event_bus::EventBus;
|
||||||
@@ -6,6 +6,7 @@ use parking_lot::RwLock;
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
||||||
|
|
||||||
/// In-memory index of the users that can receive events for each channel.
|
/// In-memory index of the users that can receive events for each channel.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
@@ -37,6 +38,24 @@ impl RealtimeRegistry {
|
|||||||
.insert(permission.resource_id);
|
.insert(permission.resource_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Les DM n'utilisent pas les permissions calculées : leur audience est
|
||||||
|
// définie directement par la table d'appartenance channel_user.
|
||||||
|
let dm_channels = channel::Entity::find()
|
||||||
|
.filter(channel::Column::ChannelType.eq(channel::ChannelType::DM))
|
||||||
|
.all(&repositories.channel.context.db)
|
||||||
|
.await?;
|
||||||
|
let dm_ids: Vec<_> = 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.channel_users.write() = channel_users;
|
||||||
*self.user_channels.write() = user_channels;
|
*self.user_channels.write() = user_channels;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -50,6 +69,24 @@ impl RealtimeRegistry {
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_channel_users(&self, channel_id: Uuid, users: impl IntoIterator<Item = Uuid>) {
|
||||||
|
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<Item = Uuid>) {
|
pub fn set_user_channels(&self, user_id: Uuid, channels: impl IntoIterator<Item = Uuid>) {
|
||||||
let channels: HashSet<_> = channels.into_iter().collect();
|
let channels: HashSet<_> = channels.into_iter().collect();
|
||||||
let old = self
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user