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 { self.require_room_access(room_id, ctx.user_id).await?; let room_model = self.find_room_or_404(room_id).await?; let members: Vec = 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 = 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 = 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 }) } }