use db::channel::channel_overwrite_role as role_ow_entity; use db::channel::channel_overwrite_users as user_ow_entity; use db::channel::channels as channel_entity; use db::conn::sea_orm::*; use db::space::space_members as space_member_entity; use db::space::space_roles as space_role_entity; use db::space::spaces as space_entity; use uuid::Uuid; use crate::error::{SpaceError, SpaceResult}; use crate::features::authz; use crate::features::SpaceService; use super::types::{RoleOverwriteView, SetRoleOverwriteCmd, SetUserOverwriteCmd, UserOverwriteView}; async fn member_space(svc: &SpaceService, caller: Uuid, space_id: Uuid) -> SpaceResult { let db = svc.db.reader(); let space = space_entity::Entity::find_by_id(space_id) .one(&db) .await? .ok_or_else(|| SpaceError::NotFound(format!("space {space_id}")))?; let member = space_member_entity::Entity::find_by_id((space_id, caller)).one(&db).await?; let active = member .map(|m| m.status == space_member_entity::MemberStatus::Active) .unwrap_or(false); if !active { return Err(SpaceError::Forbidden("caller is not an active member of this space".to_string())); } Ok(space) } async fn channel_in_space( db: &C, space_id: Uuid, channel_id: Uuid, ) -> SpaceResult where C: ConnectionTrait, { let channel = channel_entity::Entity::find_by_id(channel_id) .one(db) .await? .ok_or_else(|| SpaceError::NotFound(format!("channel {channel_id}")))?; if channel.space_id != Some(space_id) { return Err(SpaceError::NotFound(format!("channel {channel_id}"))); } Ok(channel) } pub async fn set_role_overwrite( svc: &SpaceService, caller: Uuid, space_id: Uuid, channel_id: Uuid, cmd: SetRoleOverwriteCmd, ) -> SpaceResult { authz::require_perm(svc, caller, space_id, authz::MANAGE_OVERWRITES).await?; let db = svc.db.writer(); channel_in_space(&db, space_id, channel_id).await?; let role = space_role_entity::Entity::find_by_id(cmd.role_id) .one(&db) .await? .ok_or_else(|| SpaceError::NotFound(format!("role {}", cmd.role_id)))?; if role.space_id != space_id { return Err(SpaceError::NotFound(format!("role {}", cmd.role_id))); } let existing = role_ow_entity::Entity::find() .filter( role_ow_entity::Column::ChannelId .eq(channel_id) .and(role_ow_entity::Column::RoleId.eq(cmd.role_id)), ) .one(&db) .await?; let saved: role_ow_entity::Model = if let Some(ow) = existing { let mut active: role_ow_entity::ActiveModel = ow.into(); active.allow_mask = Set(cmd.allow_mask); active.deny_mask = Set(cmd.deny_mask); active.update(&db).await? } else { let active: role_ow_entity::ActiveModel = role_ow_entity::ActiveModel::builder() .set_id(Uuid::new_v4()) .set_space_id(space_id) .set_channel_id(channel_id) .set_role_id(cmd.role_id) .set_allow_mask(cmd.allow_mask) .set_deny_mask(cmd.deny_mask) .into(); active.insert(&db).await? }; Ok(saved.into()) } pub async fn remove_role_overwrite( svc: &SpaceService, caller: Uuid, space_id: Uuid, channel_id: Uuid, role_id: Uuid, ) -> SpaceResult<()> { authz::require_perm(svc, caller, space_id, authz::MANAGE_OVERWRITES).await?; let db = svc.db.writer(); channel_in_space(&db, space_id, channel_id).await?; let res = role_ow_entity::Entity::delete_many() .filter( role_ow_entity::Column::ChannelId .eq(channel_id) .and(role_ow_entity::Column::RoleId.eq(role_id)), ) .exec(&db) .await?; if res.rows_affected == 0 { return Err(SpaceError::NotFound(format!( "role overwrite for role {role_id} in channel {channel_id}" ))); } Ok(()) } pub async fn list_role_overwrites( svc: &SpaceService, caller: Uuid, space_id: Uuid, channel_id: Uuid, ) -> SpaceResult> { member_space(svc, caller, space_id).await?; let db = svc.db.reader(); channel_in_space(&db, space_id, channel_id).await?; let items = role_ow_entity::Entity::find() .filter(role_ow_entity::Column::ChannelId.eq(channel_id)) .all(&db) .await?; Ok(items.into_iter().map(RoleOverwriteView::from).collect()) } pub async fn set_user_overwrite( svc: &SpaceService, caller: Uuid, space_id: Uuid, channel_id: Uuid, cmd: SetUserOverwriteCmd, ) -> SpaceResult { authz::require_perm(svc, caller, space_id, authz::MANAGE_OVERWRITES).await?; let db = svc.db.writer(); channel_in_space(&db, space_id, channel_id).await?; let existing = user_ow_entity::Entity::find_by_id((channel_id, cmd.user_id)) .one(&db) .await?; let saved: user_ow_entity::Model = if let Some(ow) = existing { let mut active: user_ow_entity::ActiveModel = ow.into(); active.allow_mask = Set(cmd.allow_mask); active.deny_mask = Set(cmd.deny_mask); active.update(&db).await? } else { let active: user_ow_entity::ActiveModel = user_ow_entity::ActiveModel::builder() .set_channel_id(channel_id) .set_user_id(cmd.user_id) .set_allow_mask(cmd.allow_mask) .set_deny_mask(cmd.deny_mask) .into(); active.insert(&db).await? }; Ok(saved.into()) } pub async fn remove_user_overwrite( svc: &SpaceService, caller: Uuid, space_id: Uuid, channel_id: Uuid, user_id: Uuid, ) -> SpaceResult<()> { authz::require_perm(svc, caller, space_id, authz::MANAGE_OVERWRITES).await?; let db = svc.db.writer(); channel_in_space(&db, space_id, channel_id).await?; let res = user_ow_entity::Entity::delete_by_id((channel_id, user_id)) .exec(&db) .await?; if res.rows_affected == 0 { return Err(SpaceError::NotFound(format!( "user overwrite for user {user_id} in channel {channel_id}" ))); } Ok(()) } pub async fn list_user_overwrites( svc: &SpaceService, caller: Uuid, space_id: Uuid, channel_id: Uuid, ) -> SpaceResult> { member_space(svc, caller, space_id).await?; let db = svc.db.reader(); channel_in_space(&db, space_id, channel_id).await?; let items = user_ow_entity::Entity::find() .filter(user_ow_entity::Column::ChannelId.eq(channel_id)) .all(&db) .await?; Ok(items.into_iter().map(UserOverwriteView::from).collect()) }