post services integrations

This commit is contained in:
2026-07-29 18:09:38 +02:00
parent 6cb9acf98b
commit 759bc1dc15
23 changed files with 956 additions and 95 deletions
+88
View File
@@ -0,0 +1,88 @@
use crate::services::ServicesContext;
use crate::models::category;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, TransactionTrait, Set};
use std::sync::Arc;
use uuid::Uuid;
#[derive(Debug, Clone)]
pub struct CategoryService {
service_context: Arc<ServicesContext>,
}
impl CategoryService {
pub fn new(service_context: Arc<ServicesContext>) -> Self {
Self { service_context }
}
pub async fn create_category(
&self,
server_id: Uuid,
name: String,
) -> Result<category::Model, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
let active = category::ActiveModel {
server_id: Set(server_id),
name: Set(name),
..Default::default()
};
let cat = active.insert(&txn).await?;
txn.commit().await?;
event_bus.emit("category_created", cat.clone());
Ok(cat)
}
pub async fn update_category(
&self,
id: Uuid,
name: String,
) -> Result<category::Model, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
let existing = category::Entity::find_by_id(id)
.one(&txn)
.await?
.ok_or_else(|| anyhow::anyhow!("Category not found"))?;
let mut active: category::ActiveModel = existing.into();
active.name = Set(name);
let cat = active.update(&txn).await?;
txn.commit().await?;
event_bus.emit("category_updated", cat.clone());
Ok(cat)
}
pub async fn delete_category(&self, id: Uuid) -> Result<bool, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
let res = category::Entity::delete_by_id(id)
.exec(&txn)
.await?;
let deleted = res.rows_affected > 0;
txn.commit().await?;
if deleted {
event_bus.emit("category_deleted", id);
}
Ok(deleted)
}
}
+263
View File
@@ -0,0 +1,263 @@
use crate::repositories::Repositories;
use crate::services::ServicesContext;
use crate::domain::dto::channel::{CreateChannelRequest, UpdateChannelRequest};
use crate::models::channel;
use event_bus::Scope;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QuerySelect, QueryOrder, TransactionTrait, Set, PaginatorTrait};
use std::sync::Arc;
use uuid::Uuid;
#[derive(Debug, Clone)]
pub struct ChannelService {
service_context: Arc<ServicesContext>,
}
impl ChannelService {
pub fn new(service_context: Arc<ServicesContext>) -> Self {
Self { service_context }
}
pub async fn create_channel(
&self,
payload: CreateChannelRequest,
) -> Result<channel::Model, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
// Start transaction
let txn = db.begin().await?;
// 1. Insert channel within transaction
let active_model = channel::ActiveModel {
server_id: Set(payload.server_id),
category_id: Set(payload.category_id),
channel_type: Set(payload.channel_type),
name: Set(payload.name),
..Default::default()
};
let channel = active_model.insert(&txn).await?;
// 2. If server_id is present, insert into server_item_order within transaction
if let Some(server_id) = payload.server_id {
// Get max order key or determine order key
let max_order: Option<i64> = crate::models::server_item_order::Entity::find()
.filter(crate::models::server_item_order::Column::ServerId.eq(server_id))
.select_only()
.column_as(crate::models::server_item_order::Column::OrderKey.max(), "max_key")
.into_tuple::<Option<i64>>()
.one(&txn)
.await?
.flatten();
let next_order = max_order.unwrap_or(0) + 1;
let order_item = crate::models::server_item_order::ActiveModel {
server_id: Set(server_id),
resource_id: Set(channel.id),
resource_type: Set(crate::models::server_item_order::OrderedResourceType::Channel),
parent_category_id: Set(payload.category_id),
order_key: Set(next_order),
..Default::default()
};
order_item.insert(&txn).await?;
}
// Commit transaction
txn.commit().await?;
// Post-commit event emission
event_bus.emit("channel_created", channel.clone());
Ok(channel)
}
pub async fn update_channel(
&self,
id: Uuid,
payload: UpdateChannelRequest,
) -> Result<channel::Model, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
let existing = channel::Entity::find_by_id(id)
.one(&txn)
.await?
.ok_or_else(|| anyhow::anyhow!("Channel not found"))?;
let mut active: channel::ActiveModel = existing.into();
active.server_id = Set(payload.server_id);
active.category_id = Set(payload.category_id);
active.channel_type = Set(payload.channel_type);
active.name = Set(payload.name);
let channel = active.update(&txn).await?;
// Update server_item_order parent_category_id or server_id if needed
if let Some(server_id) = channel.server_id {
crate::models::server_item_order::Entity::update_many()
.set(crate::models::server_item_order::ActiveModel {
parent_category_id: Set(channel.category_id),
..Default::default()
})
.filter(crate::models::server_item_order::Column::ResourceId.eq(channel.id))
.exec(&txn)
.await?;
}
txn.commit().await?;
event_bus.emit("channel_updated", channel.clone());
Ok(channel)
}
pub async fn delete_channel(&self, id: Uuid) -> Result<bool, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
// Delete associated order records
crate::models::server_item_order::Entity::delete_many()
.filter(crate::models::server_item_order::Column::ResourceId.eq(id))
.exec(&txn)
.await?;
let res = channel::Entity::delete_by_id(id)
.exec(&txn)
.await?;
let deleted = res.rows_affected > 0;
txn.commit().await?;
if deleted {
event_bus.emit("channel_deleted", id);
}
Ok(deleted)
}
pub async fn set_user_permission(
&self,
channel_id: Uuid,
user_id: Uuid,
permissions: u64,
) -> Result<(), anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
let permission = crate::models::channel_user_permission::ActiveModel {
channel_id: Set(channel_id),
user_id: Set(user_id),
permissions: Set(permissions as i64),
..Default::default()
};
crate::models::channel_user_permission::Entity::insert(permission)
.on_conflict(
sea_orm::sea_query::OnConflict::columns([
crate::models::channel_user_permission::Column::ChannelId,
crate::models::channel_user_permission::Column::UserId,
])
.update_columns([crate::models::channel_user_permission::Column::Permissions])
.to_owned(),
)
.exec(&txn)
.await?;
txn.commit().await?;
event_bus.emit("channel_user_permission_created", (channel_id, user_id, permissions));
Ok(())
}
pub async fn remove_user_permission(
&self,
channel_id: Uuid,
user_id: Uuid,
) -> Result<(), anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
crate::models::channel_user_permission::Entity::delete_many()
.filter(crate::models::channel_user_permission::Column::ChannelId.eq(channel_id))
.filter(crate::models::channel_user_permission::Column::UserId.eq(user_id))
.exec(&txn)
.await?;
txn.commit().await?;
event_bus.emit("channel_user_permission_deleted", (channel_id, user_id));
Ok(())
}
pub async fn set_role_permission(
&self,
channel_id: Uuid,
role_id: Uuid,
permissions: u64,
) -> Result<(), anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
let permission = crate::models::channel_role_permission::ActiveModel {
channel_id: Set(channel_id),
role_id: Set(role_id),
permissions: Set(permissions as i64),
..Default::default()
};
crate::models::channel_role_permission::Entity::insert(permission)
.on_conflict(
sea_orm::sea_query::OnConflict::columns([
crate::models::channel_role_permission::Column::ChannelId,
crate::models::channel_role_permission::Column::RoleId,
])
.update_columns([crate::models::channel_role_permission::Column::Permissions])
.to_owned(),
)
.exec(&txn)
.await?;
txn.commit().await?;
event_bus.emit("channel_role_permission_created", (channel_id, role_id, permissions));
Ok(())
}
pub async fn remove_role_permission(
&self,
channel_id: Uuid,
role_id: Uuid,
) -> Result<(), anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
crate::models::channel_role_permission::Entity::delete_many()
.filter(crate::models::channel_role_permission::Column::ChannelId.eq(channel_id))
.filter(crate::models::channel_role_permission::Column::RoleId.eq(role_id))
.exec(&txn)
.await?;
txn.commit().await?;
event_bus.emit("channel_role_permission_deleted", (channel_id, role_id));
Ok(())
}
}
+106
View File
@@ -0,0 +1,106 @@
use crate::services::ServicesContext;
use crate::models::{channel, message};
use event_bus::Scope;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QuerySelect, TransactionTrait, Set};
use std::sync::Arc;
use uuid::Uuid;
#[derive(Debug, Clone)]
pub struct MessageService {
service_context: Arc<ServicesContext>,
}
impl MessageService {
pub fn new(service_context: Arc<ServicesContext>) -> Self {
Self { service_context }
}
pub async fn create_message(
&self,
channel_id: Uuid,
author_id: Uuid,
content: String,
) -> Result<message::Model, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
let active = message::ActiveModel {
channel_id: Set(channel_id),
user_id: Set(author_id),
content: Set(content),
..Default::default()
};
let msg = active.insert(&txn).await?;
txn.commit().await?;
let mut scopes: Vec<Scope> = Vec::new();
scopes.push(Scope::uuid("channel", msg.channel_id));
let server_id: Option<Uuid> = channel::Entity::find_by_id(msg.channel_id)
.select_only()
.column(channel::Column::ServerId)
.into_tuple::<Option<Uuid>>()
.one(db)
.await?
.flatten();
if let Some(server_id) = server_id {
scopes.push(Scope::uuid("server", server_id));
}
event_bus.emit_scoped("message_created", scopes, msg.clone());
Ok(msg)
}
pub async fn update_message(
&self,
id: Uuid,
content: String,
) -> Result<message::Model, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
let existing = message::Entity::find_by_id(id)
.one(&txn)
.await?
.ok_or_else(|| anyhow::anyhow!("Message not found"))?;
let mut active: message::ActiveModel = existing.into();
active.content = Set(content);
let msg = active.update(&txn).await?;
txn.commit().await?;
event_bus.emit("message_updated", msg.clone());
Ok(msg)
}
pub async fn delete_message(&self, id: Uuid) -> Result<bool, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
let res = message::Entity::delete_by_id(id)
.exec(&txn)
.await?;
let deleted = res.rows_affected > 0;
txn.commit().await?;
if deleted {
event_bus.emit("message_deleted", id);
}
Ok(deleted)
}
}
+30
View File
@@ -1,11 +1,23 @@
use crate::repositories::Repositories;
use crate::services::permission_sync::PermissionSyncService;
use crate::services::server_order::ServerOrderService;
use crate::services::channel::ChannelService;
use crate::services::server::ServerService;
use crate::services::category::CategoryService;
use crate::services::message::MessageService;
use crate::services::user::UserService;
use crate::services::role::RoleService;
use event_bus::EventBus;
use std::sync::{Arc, OnceLock};
pub mod permission_sync;
mod server_order;
pub mod channel;
pub mod server;
pub mod category;
pub mod message;
pub mod user;
pub mod role;
#[derive(Debug, Clone)]
pub struct ServicesContext {
@@ -18,6 +30,12 @@ pub struct ServicesContext {
pub struct Services {
pub permission_sync: Arc<PermissionSyncService>,
pub server_order: Arc<ServerOrderService>,
pub channel: Arc<ChannelService>,
pub server: Arc<ServerService>,
pub category: Arc<CategoryService>,
pub message: Arc<MessageService>,
pub user: Arc<UserService>,
pub role: Arc<RoleService>,
}
impl Services {
@@ -29,10 +47,22 @@ impl Services {
});
let permission_sync = Arc::new(PermissionSyncService::new(service_context.clone()));
let server_order = Arc::new(ServerOrderService::new(service_context.clone()));
let channel = Arc::new(ChannelService::new(service_context.clone()));
let server = Arc::new(ServerService::new(service_context.clone()));
let category = Arc::new(CategoryService::new(service_context.clone()));
let message = Arc::new(MessageService::new(service_context.clone()));
let user = Arc::new(UserService::new(service_context.clone()));
let role = Arc::new(RoleService::new(service_context.clone()));
let services = Self {
permission_sync,
server_order,
channel,
server,
category,
message,
user,
role,
};
let _ = service_context.services.set(services.clone());
services
+73
View File
@@ -0,0 +1,73 @@
use crate::services::ServicesContext;
use crate::models::role;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, TransactionTrait, Set};
use std::sync::Arc;
use uuid::Uuid;
#[derive(Debug, Clone)]
pub struct RoleService {
service_context: Arc<ServicesContext>,
}
impl RoleService {
pub fn new(service_context: Arc<ServicesContext>) -> Self {
Self { service_context }
}
pub async fn create_role(
&self,
active: role::ActiveModel,
) -> Result<role::Model, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
let group = active.insert(&txn).await?;
txn.commit().await?;
event_bus.emit("group_created", group.clone());
Ok(group)
}
pub async fn update_role(
&self,
active: role::ActiveModel,
) -> Result<role::Model, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
let group = active.update(&txn).await?;
txn.commit().await?;
event_bus.emit("group_updated", group.clone());
Ok(group)
}
pub async fn delete_role(&self, id: Uuid) -> Result<bool, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
let res = role::Entity::delete_by_id(id)
.exec(&txn)
.await?;
let deleted = res.rows_affected > 0;
txn.commit().await?;
if deleted {
event_bus.emit("group_deleted", id);
}
Ok(deleted)
}
}
+153
View File
@@ -0,0 +1,153 @@
use crate::repositories::Repositories;
use crate::services::ServicesContext;
use crate::models::{role, server};
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QuerySelect, QueryOrder, TransactionTrait, Set};
use std::sync::Arc;
use uuid::Uuid;
#[derive(Debug, Clone)]
pub struct ServerService {
service_context: Arc<ServicesContext>,
}
impl ServerService {
pub fn new(service_context: Arc<ServicesContext>) -> Self {
Self { service_context }
}
pub async fn create_server(
&self,
name: String,
is_default: bool,
) -> Result<server::Model, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
let active = server::ActiveModel {
name: Set(name),
is_default: Set(is_default),
..Default::default()
};
let srv = active.insert(&txn).await?;
let default_group = role::ActiveModel {
server_id: Set(srv.id),
name: Set("Member".to_string()),
is_default: Set(true),
..Default::default()
};
default_group.insert(&txn).await?;
txn.commit().await?;
event_bus.emit("server_created", srv.clone());
Ok(srv)
}
pub async fn update_server(
&self,
id: Uuid,
name: String,
is_default: bool,
) -> Result<server::Model, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
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 mut active: server::ActiveModel = existing.into();
active.name = Set(name);
active.is_default = Set(is_default);
let srv = active.update(&txn).await?;
txn.commit().await?;
event_bus.emit("server_updated", srv.clone());
Ok(srv)
}
pub async fn delete_server(&self, id: Uuid) -> Result<bool, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
let res = server::Entity::delete_by_id(id)
.exec(&txn)
.await?;
let deleted = res.rows_affected > 0;
txn.commit().await?;
if deleted {
event_bus.emit("server_deleted", id);
}
Ok(deleted)
}
pub async fn add_user(&self, server_id: Uuid, user_id: Uuid) -> Result<bool, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
crate::models::server_user::ActiveModel {
server_id: Set(server_id),
user_id: Set(user_id),
..Default::default()
}
.insert(&txn)
.await?;
let role_id: Uuid = role::Entity::find()
.filter(role::Column::ServerId.eq(server_id))
.filter(role::Column::IsDefault.eq(true))
.select_only()
.column(role::Column::Id)
.into_tuple::<Uuid>()
.one(&txn)
.await?
.ok_or_else(|| anyhow::anyhow!("Rôle par défaut introuvable"))?;
txn.commit().await?;
event_bus.emit("server_user_created", (server_id, user_id));
Ok(true)
}
pub async fn remove_user(&self, server_id: Uuid, user_id: Uuid) -> Result<bool, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
let res = crate::models::server_user::Entity::delete_many()
.filter(crate::models::server_user::Column::ServerId.eq(server_id))
.filter(crate::models::server_user::Column::UserId.eq(user_id))
.exec(&txn)
.await?;
let deleted = res.rows_affected > 0;
txn.commit().await?;
if deleted {
event_bus.emit("server_user_deleted", (server_id, user_id));
}
Ok(deleted)
}
}
+104
View File
@@ -0,0 +1,104 @@
use crate::services::ServicesContext;
use crate::models::{role, user};
use crate::auth::password;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, TransactionTrait, Set};
use std::sync::Arc;
use uuid::Uuid;
#[derive(Debug, Clone)]
pub struct UserService {
service_context: Arc<ServicesContext>,
}
impl UserService {
pub fn new(service_context: Arc<ServicesContext>) -> Self {
Self { service_context }
}
pub async fn create_user(
&self,
active: user::ActiveModel,
) -> Result<user::Model, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
let usr = active.insert(&txn).await?;
txn.commit().await?;
event_bus.emit("user_created", usr.clone());
Ok(usr)
}
pub async fn update_user(
&self,
active: user::ActiveModel,
) -> Result<user::Model, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
let usr = active.update(&txn).await?;
txn.commit().await?;
event_bus.emit("user_updated", usr.clone());
Ok(usr)
}
pub async fn set_password(&self, id: Uuid, password_str: String) -> Result<(), anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
let user_model = user::Entity::find_by_id(id)
.one(&txn)
.await?
.ok_or_else(|| anyhow::anyhow!("User not found"))?;
let mut active = user_model.into_active_model();
let password_to_hash = password_str.clone();
let hashed = tokio::task::spawn_blocking(move || password::hash_password(&password_to_hash))
.await
.map_err(|e| anyhow::anyhow!("Join error: {}", e))?
.map_err(|e| anyhow::anyhow!("Password hashing failed: {}", e))?;
active.password = Set(hashed);
let usr = active.update(&txn).await?;
txn.commit().await?;
event_bus.emit("user_changed", usr);
Ok(())
}
pub async fn delete_user(&self, id: Uuid) -> Result<bool, anyhow::Error> {
let db = &self.service_context.repositories.server.context.db;
let event_bus = &self.service_context.event_bus;
let txn = db.begin().await?;
let res = user::Entity::delete_by_id(id)
.exec(&txn)
.await?;
let deleted = res.rows_affected > 0;
txn.commit().await?;
if deleted {
event_bus.emit("user_deleted", id);
}
Ok(deleted)
}
}