use crate::domain::events::server::{ ServerCreatedEvent, ServerDeletedEvent, ServerUpdatedEvent, ServerUserCreatedEvent, ServerUserDeletedEvent, }; use crate::models::{role, role_user, server, server_user}; use crate::services::ServicesContext; use sea_orm::{ ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, Set, TransactionTrait, }; use std::sync::Arc; use uuid::Uuid; #[derive(Debug, Clone)] pub struct ServerService { service_context: Arc, } impl ServerService { pub fn new(service_context: Arc) -> Self { Self { service_context } } pub async fn create_server( &self, name: String, password: Option, is_default: bool, owner_id: Uuid, ) -> Result { 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), password: Set(password), is_default: Set(is_default), owner_id: Set(Some(owner_id)), ..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() }; let default_group = default_group.insert(&txn).await?; server_user::ActiveModel { server_id: Set(srv.id), user_id: Set(owner_id), ..Default::default() } .insert(&txn) .await?; role_user::ActiveModel { role_id: Set(default_group.id), user_id: Set(owner_id), } .insert(&txn) .await?; txn.commit().await?; event_bus.emit(ServerCreatedEvent { server: srv.clone(), }); Ok(srv) } pub async fn join_server( &self, server_id: Uuid, user_id: Uuid, password: Option, ) -> Result { let db = &self.service_context.repositories.server.context.db; let event_bus = &self.service_context.event_bus; let txn = db.begin().await?; let srv = server::Entity::find_by_id(server_id) .one(&txn) .await? .ok_or_else(|| anyhow::anyhow!("Server not found"))?; if srv.password != password { return Err(anyhow::anyhow!("Invalid server password")); } if server_user::Entity::find() .filter(server_user::Column::ServerId.eq(server_id)) .filter(server_user::Column::UserId.eq(user_id)) .one(&txn) .await? .is_some() { txn.commit().await?; return Ok(srv); } let membership = server_user::ActiveModel { server_id: Set(server_id), user_id: Set(user_id), ..Default::default() } .insert(&txn) .await?; let default_role = role::Entity::find() .filter(role::Column::ServerId.eq(server_id)) .filter(role::Column::IsDefault.eq(true)) .one(&txn) .await? .ok_or_else(|| anyhow::anyhow!("Default role not found"))?; role_user::ActiveModel { role_id: Set(default_role.id), user_id: Set(user_id), } .insert(&txn) .await?; txn.commit().await?; let services = self .service_context .services .get() .expect("services initialized"); if let Err(error) = self .service_context .repositories .computed_permission .full_sync_user(user_id, server_id) .await { tracing::error!(%user_id, %server_id, ?error, "Unable to sync permissions on join"); } if let Err(error) = services .realtime_registry .refresh_user(&self.service_context.repositories, user_id) .await { tracing::error!(%user_id, ?error, "Unable to refresh realtime registry on join"); } event_bus.emit(ServerUserCreatedEvent { server_user: membership, }); self.service_context .repositories .read_state .mark_server_read(server_id, user_id) .await?; Ok(srv) } pub async fn update_server( &self, id: Uuid, name: String, is_default: bool, ) -> Result { 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(ServerUpdatedEvent { server: srv.clone(), }); Ok(srv) } pub async fn delete_server(&self, id: Uuid) -> Result { 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 audience = server_user::Entity::find() .filter(server_user::Column::ServerId.eq(id)) .all(&txn) .await? .into_iter() .map(|member| member.user_id) .collect::>(); 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(ServerDeletedEvent { server: existing, audience, }); } Ok(deleted) } pub async fn add_user(&self, server_id: Uuid, user_id: Uuid) -> Result { let db = &self.service_context.repositories.server.context.db; let event_bus = &self.service_context.event_bus; let txn = db.begin().await?; let membership = 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::() .one(&txn) .await? .ok_or_else(|| anyhow::anyhow!("Rôle par défaut introuvable"))?; txn.commit().await?; event_bus.emit(ServerUserCreatedEvent { server_user: membership, }); Ok(true) } pub async fn remove_user(&self, server_id: Uuid, user_id: Uuid) -> Result { let db = &self.service_context.repositories.server.context.db; let event_bus = &self.service_context.event_bus; let txn = db.begin().await?; let membership = server_user::Entity::find() .filter(server_user::Column::ServerId.eq(server_id)) .filter(server_user::Column::UserId.eq(user_id)) .one(&txn) .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 let Some(server_user) = membership.filter(|_| deleted) { event_bus.emit(ServerUserDeletedEvent { server_user }); } Ok(deleted) } }