gitdataai/libs/room/src/member.rs
2026-05-14 10:02:21 +08:00

143 lines
5.3 KiB
Rust

use crate::error::RoomError;
use crate::service::RoomService;
use crate::ws_context::WsUserContext;
use models::projects::project_members;
use models::rooms::room_access;
use models::rooms::room_user_state;
use models::users::user as user_model;
use sea_orm::*;
use uuid::Uuid;
impl RoomService {
/// List room participants: for public rooms, all project members;
/// for private rooms, project admins + room_access grantees + creator.
pub async fn room_participant_list(
&self,
room_id: Uuid,
ctx: &WsUserContext,
) -> Result<super::RoomParticipantListResponse, RoomError> {
self.require_room_access(room_id, ctx.user_id).await?;
let room_model = self.find_room_or_404(room_id).await?;
let members: Vec<Uuid> = if room_model.public {
// Public room: all project members
project_members::Entity::find()
.filter(project_members::Column::Project.eq(room_model.project))
.all(&self.db)
.await?
.into_iter()
.map(|m| m.user)
.collect()
} else {
// Private room: creator + project admins + room_access grantees
let mut user_ids = vec![room_model.created_by];
// Project admins
let project_admins = project_members::Entity::find()
.filter(project_members::Column::Project.eq(room_model.project))
.all(&self.db)
.await?;
for m in project_admins {
if matches!(
m.scope_role(),
Ok(models::projects::MemberRole::Owner | models::projects::MemberRole::Admin)
) {
if !user_ids.contains(&m.user) {
user_ids.push(m.user);
}
}
}
// Explicit access grants
let access_list = room_access::Entity::find()
.filter(room_access::Column::Room.eq(room_id))
.all(&self.db)
.await?;
for a in access_list {
if !user_ids.contains(&a.user) {
user_ids.push(a.user);
}
}
user_ids
};
// Fetch user info
let users: std::collections::HashMap<Uuid, super::UserInfo> = if !members.is_empty() {
user_model::Entity::find()
.filter(user_model::Column::Uid.is_in(members.clone()))
.all(&self.db)
.await?
.into_iter()
.map(|u| {
(
u.uid,
super::UserInfo {
uid: u.uid,
username: u.username,
avatar_url: u.avatar_url,
},
)
})
.collect()
} else {
std::collections::HashMap::new()
};
// Fetch room_user_state for each member (for last_read_seq, DND etc.)
let states = room_user_state::Entity::find()
.filter(room_user_state::Column::Room.eq(room_id))
.filter(room_user_state::Column::User.is_in(members.clone()))
.all(&self.db)
.await?;
let state_map: std::collections::HashMap<(Uuid, Uuid), room_user_state::Model> =
states.into_iter().map(|s| ((s.room, s.user), s)).collect();
// Determine project-level role for each member
let project_member_list = project_members::Entity::find()
.filter(project_members::Column::Project.eq(room_model.project))
.filter(project_members::Column::User.is_in(members.clone()))
.all(&self.db)
.await?;
let role_map: std::collections::HashMap<Uuid, String> = project_member_list
.into_iter()
.map(|m| {
(
m.user,
m.scope_role()
.map(|r| r.to_string())
.unwrap_or_else(|_| "member".to_string()),
)
})
.collect();
let participants = members
.into_iter()
.map(|user_id| {
let user_info = users.get(&user_id).cloned();
let state = state_map.get(&(room_id, user_id));
let project_role = role_map
.get(&user_id)
.cloned()
.unwrap_or_else(|| "member".to_string());
let is_room_owner = room_model.created_by == user_id;
super::RoomParticipantResponse {
room: room_id,
user: user_id,
user_info,
project_role,
is_room_owner,
last_read_seq: state.and_then(|s| s.last_read_seq),
do_not_disturb: state.map(|s| s.do_not_disturb).unwrap_or(false),
dnd_start_hour: state.and_then(|s| s.dnd_start_hour),
dnd_end_hour: state.and_then(|s| s.dnd_end_hour),
joined_at: state.and_then(|s| s.joined_at),
}
})
.collect();
Ok(super::RoomParticipantListResponse { participants })
}
}