This commit is contained in:
2026-08-29 18:39:28 +02:00
parent 88ad3c389f
commit 9551e90678
17 changed files with 642 additions and 269 deletions
+6 -1
View File
@@ -87,6 +87,11 @@ impl CategoryService {
let txn = db.begin().await?;
let existing = category::Entity::find_by_id(id)
.one(&txn)
.await?
.ok_or_else(|| anyhow::anyhow!("Category not found"))?;
self.service_context
.services
.get()
@@ -102,7 +107,7 @@ impl CategoryService {
txn.commit().await?;
if deleted {
event_bus.emit("category_deleted", id);
event_bus.emit("category_deleted", existing);
}
Ok(deleted)
+23 -3
View File
@@ -1,4 +1,7 @@
use crate::domain::dto::channel::{CreateChannelRequest, UpdateChannelRequest};
use crate::domain::events::channel::{
ChannelCreatedEvent, ChannelDeletedEvent, ChannelUpdatedEvent,
};
use crate::models::server_item_order::OrderedResourceType;
use crate::models::{channel, role};
use crate::permissions::PermissionSet;
@@ -88,7 +91,12 @@ impl ChannelService {
.await?;
// Post-commit event emission
event_bus.emit("channel_created", channel.clone());
event_bus.emit(
"channel_created",
ChannelCreatedEvent {
channel: channel.clone(),
},
);
Ok(channel)
}
@@ -108,6 +116,7 @@ impl ChannelService {
.await?
.ok_or_else(|| anyhow::anyhow!("Channel not found"))?;
let previous = existing.clone();
let mut active: channel::ActiveModel = existing.into();
active.server_id = Set(payload.server_id);
active.category_id = Set(payload.category_id);
@@ -132,7 +141,13 @@ impl ChannelService {
txn.commit().await?;
event_bus.emit("channel_updated", channel.clone());
event_bus.emit(
"channel_updated",
ChannelUpdatedEvent {
previous,
channel: channel.clone(),
},
);
Ok(channel)
}
@@ -143,6 +158,11 @@ impl ChannelService {
let txn = db.begin().await?;
let existing = channel::Entity::find_by_id(id)
.one(&txn)
.await?
.ok_or_else(|| anyhow::anyhow!("Channel not found"))?;
self.service_context
.services
.get()
@@ -158,7 +178,7 @@ impl ChannelService {
txn.commit().await?;
if deleted {
event_bus.emit("channel_deleted", id);
event_bus.emit("channel_deleted", ChannelDeletedEvent { channel: existing });
}
Ok(deleted)
+48 -6
View File
@@ -1,3 +1,5 @@
use crate::domain::events::channel::{ChannelCreatedEvent, ChannelDeletedEvent};
use crate::models::server;
use crate::repositories::Repositories;
use crate::services::ServicesContext;
use std::sync::Arc;
@@ -71,8 +73,8 @@ impl PermissionSyncService {
event_bus.on_async_with(
"server_created",
repositories.clone(),
move |repositories, server_id: Uuid| async move {
Self::sync_server(repositories, server_id).await;
move |repositories, server: server::Model| async move {
Self::sync_server(repositories, server.id).await;
},
);
@@ -139,16 +141,20 @@ impl PermissionSyncService {
event_bus.on_async_with(
"channel_created",
repositories.clone(),
move |repositories, server_id: Uuid| async move {
Self::sync_server(repositories, server_id).await;
move |repositories, event: ChannelCreatedEvent| async move {
if let Some(server_id) = event.channel.server_id {
Self::sync_server(repositories, server_id).await;
}
},
);
event_bus.on_async_with(
"channel_deleted",
repositories.clone(),
move |repositories, server_id: Uuid| async move {
Self::sync_server(repositories, server_id).await;
move |repositories, event: ChannelDeletedEvent| async move {
if let Some(server_id) = event.channel.server_id {
Self::sync_server(repositories, server_id).await;
}
},
);
@@ -167,6 +173,42 @@ impl PermissionSyncService {
Self::sync_user(repositories, user_id, server_id).await;
},
);
event_bus.on_async_with(
"channel_user_permission_created",
repositories.clone(),
move |repositories, (channel_id, user_id, _permissions): (Uuid, Uuid, u64)| async move {
if let Some(channel) = repositories
.channel
.get_by_id(channel_id)
.await
.ok()
.flatten()
{
if let Some(server_id) = channel.server_id {
Self::sync_user(repositories, user_id, server_id).await;
}
}
},
);
event_bus.on_async_with(
"channel_user_permission_deleted",
repositories,
move |repositories, (channel_id, user_id): (Uuid, Uuid)| async move {
if let Some(channel) = repositories
.channel
.get_by_id(channel_id)
.await
.ok()
.flatten()
{
if let Some(server_id) = channel.server_id {
Self::sync_user(repositories, user_id, server_id).await;
}
}
},
);
}
// -------------------------------------------------------------------------
+115 -34
View File
@@ -3,10 +3,10 @@ use crate::permissions::ChannelPermission;
use crate::repositories::Repositories;
use event_bus::EventBus;
use parking_lot::RwLock;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use uuid::Uuid;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
/// In-memory index of the users that can receive events for each channel.
#[derive(Debug, Default)]
@@ -16,6 +16,58 @@ pub struct RealtimeRegistry {
}
impl RealtimeRegistry {
/// Rebuilds one channel audience after a committed structural change.
pub async fn refresh_channel(
&self,
repositories: &Repositories,
channel_id: Uuid,
) -> anyhow::Result<()> {
let channel = channel::Entity::find_by_id(channel_id)
.one(&repositories.channel.context.db)
.await?
.ok_or_else(|| anyhow::anyhow!("Channel not found"))?;
if channel.channel_type == channel::ChannelType::DM {
let users = channel_user::Entity::find()
.filter(channel_user::Column::ChannelId.eq(channel_id))
.all(&repositories.channel.context.db)
.await?
.into_iter()
.map(|member| member.user_id);
self.set_channel_users(channel_id, users);
} else {
let permissions = repositories.computed_permission.get_all().await?;
self.set_channel_users(
channel_id,
permissions.into_iter().filter_map(|permission| {
(permission.scope_type == PermissionScopeType::Channel
&& permission.resource_id == channel_id
&& ChannelPermission::from_bits_retain(permission.permissions as u64)
.contains(ChannelPermission::READ_CHANNEL))
.then_some(permission.user_id)
}),
);
}
Ok(())
}
async fn refresh_user(&self, repositories: &Repositories, user_id: Uuid) -> anyhow::Result<()> {
let channels = repositories
.computed_permission
.get_all()
.await?
.into_iter()
.filter(|permission| {
permission.user_id == user_id
&& permission.scope_type == PermissionScopeType::Channel
&& ChannelPermission::from_bits_retain(permission.permissions as u64)
.contains(ChannelPermission::READ_CHANNEL)
})
.map(|permission| permission.resource_id);
self.set_user_channels(user_id, channels);
Ok(())
}
pub async fn initialize(&self, repositories: &Repositories) -> anyhow::Result<()> {
let permissions = repositories.computed_permission.get_all().await?;
let mut channel_users = HashMap::<Uuid, HashSet<Uuid>>::new();
@@ -51,8 +103,14 @@ impl RealtimeRegistry {
.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);
channel_users
.entry(member.channel_id)
.or_default()
.insert(member.user_id);
user_channels
.entry(member.user_id)
.or_default()
.insert(member.channel_id);
}
}
@@ -73,13 +131,17 @@ impl RealtimeRegistry {
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()
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); }
if channels.is_empty() {
by_user.remove(user_id);
}
}
}
for user_id in users {
@@ -148,21 +210,36 @@ impl RealtimeRegistry {
move |repositories, (_channel_id, user_id, _permissions): (Uuid, Uuid, u64)| {
let registry = Arc::clone(&registry);
async move {
match repositories.computed_permission.get_all().await {
Ok(all) => registry.set_user_channels(
user_id,
all.into_iter()
.filter(|p| {
p.user_id == user_id
&& p.scope_type == PermissionScopeType::Channel
&& ChannelPermission::from_bits_retain(p.permissions as u64)
.contains(ChannelPermission::READ_CHANNEL)
})
.map(|p| p.resource_id),
),
Err(error) => {
tracing::error!(%user_id, ?error, "Unable to refresh realtime registry")
}
if let Err(error) = registry.refresh_user(&repositories, user_id).await {
tracing::error!(%user_id, ?error, "Unable to refresh realtime registry")
}
}
},
);
let registry = Arc::clone(self);
event_bus.on_async_with(
"channel_user_permission_created",
repositories.clone(),
move |repositories, (_channel_id, user_id, _permissions): (Uuid, Uuid, u64)| {
let registry = Arc::clone(&registry);
async move {
if let Err(error) = registry.refresh_user(&repositories, user_id).await {
tracing::error!(%user_id, ?error, "Unable to refresh realtime registry")
}
}
},
);
let registry = Arc::clone(self);
event_bus.on_async_with(
"channel_user_permission_deleted",
repositories.clone(),
move |repositories, (_channel_id, user_id): (Uuid, Uuid)| {
let registry = Arc::clone(&registry);
async move {
if let Err(error) = registry.refresh_user(&repositories, user_id).await {
tracing::error!(%user_id, ?error, "Unable to refresh realtime registry")
}
}
},
@@ -176,18 +253,8 @@ impl RealtimeRegistry {
move |repositories, (_server_id, user_id): (Uuid, Uuid)| {
let registry = Arc::clone(&registry);
async move {
if let Ok(all) = repositories.computed_permission.get_all().await {
registry.set_user_channels(
user_id,
all.into_iter()
.filter(|p| {
p.user_id == user_id
&& p.scope_type == PermissionScopeType::Channel
&& ChannelPermission::from_bits_retain(p.permissions as u64)
.contains(ChannelPermission::READ_CHANNEL)
})
.map(|p| p.resource_id),
);
if let Err(error) = registry.refresh_user(&repositories, user_id).await {
tracing::error!(%user_id, ?error, "Unable to refresh realtime registry")
}
}
},
@@ -210,8 +277,22 @@ mod tests {
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));
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));
+14 -2
View File
@@ -1,4 +1,4 @@
use crate::models::{role, server};
use crate::models::{role, server, server_user};
use crate::repositories::Repositories;
use crate::services::ServicesContext;
use sea_orm::{
@@ -85,6 +85,18 @@ impl ServerService {
let txn = db.begin().await?;
let existing = server::Entity::find_by_id(id)
.one(&txn)
.await?
.ok_or_else(|| anyhow::anyhow!("Server not found"))?;
let audience = server_user::Entity::find()
.filter(server_user::Column::ServerId.eq(id))
.all(&txn)
.await?
.into_iter()
.map(|member| member.user_id)
.collect::<Vec<_>>();
let res = server::Entity::delete_by_id(id).exec(&txn).await?;
let deleted = res.rows_affected > 0;
@@ -92,7 +104,7 @@ impl ServerService {
txn.commit().await?;
if deleted {
event_bus.emit("server_deleted", id);
event_bus.emit("server_deleted", (existing, audience));
}
Ok(deleted)