gitdataai/libs/room/src/member.rs
2026-04-14 19:02:01 +08:00

371 lines
12 KiB
Rust

use crate::error::RoomError;
use crate::service::RoomService;
use crate::ws_context::WsUserContext;
use chrono::Utc;
use models::projects::project_members;
use models::rooms::{RoomMemberRole, room_member};
use models::users::user as user_model;
use sea_orm::*;
use uuid::Uuid;
impl RoomService {
pub async fn room_member_list(
&self,
room_id: Uuid,
ctx: &WsUserContext,
) -> Result<Vec<super::RoomMemberResponse>, RoomError> {
let user_id = ctx.user_id;
self.require_room_member(room_id, user_id).await?;
let members = room_member::Entity::find()
.filter(room_member::Column::Room.eq(room_id))
.all(&self.db)
.await?;
let user_ids: Vec<Uuid> = members.iter().map(|m| m.user).collect();
let users: std::collections::HashMap<Uuid, super::UserInfo> = if !user_ids.is_empty() {
use sea_orm::ColumnTrait;
user_model::Entity::find()
.filter(user_model::Column::Uid.is_in(user_ids))
.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()
};
let responses = members
.into_iter()
.map(|m| super::RoomMemberResponse {
room: m.room,
user: m.user,
user_info: users.get(&m.user).cloned(),
role: m.role.to_string(),
first_msg_in: m.first_msg_in,
joined_at: m.joined_at,
last_read_seq: m.last_read_seq,
do_not_disturb: m.do_not_disturb,
dnd_start_hour: m.dnd_start_hour,
dnd_end_hour: m.dnd_end_hour,
})
.collect();
Ok(responses)
}
pub async fn room_member_add(
&self,
room_id: Uuid,
request: super::RoomMemberAddRequest,
ctx: &WsUserContext,
) -> Result<super::RoomMemberResponse, RoomError> {
let actor_id = ctx.user_id;
let room_model = self.find_room_or_404(room_id).await?;
self.require_room_admin(room_id, actor_id).await?;
let target_project_member = project_members::Entity::find()
.filter(project_members::Column::Project.eq(room_model.project))
.filter(project_members::Column::User.eq(request.user_id))
.one(&self.db)
.await?;
if target_project_member.is_none() {
return Err(RoomError::NoPower);
}
if let Some(existing) = self.find_room_member(room_id, request.user_id).await? {
let user_info = user_model::Entity::find()
.filter(user_model::Column::Uid.eq(request.user_id))
.one(&self.db)
.await
.ok()
.flatten()
.map(|u| super::UserInfo {
uid: u.uid,
username: u.username,
avatar_url: u.avatar_url,
});
let mut response = super::RoomMemberResponse::from(existing);
response.user_info = user_info;
return Ok(response);
}
let role = if let Some(role) = request.role {
Self::parse_room_member_role(&role.to_lowercase())?
} else {
RoomMemberRole::Member
};
let created = room_member::ActiveModel {
room: Set(room_id),
user: Set(request.user_id),
role: Set(role),
first_msg_in: Set(None),
joined_at: Set(Some(Utc::now())),
last_read_seq: Set(None),
do_not_disturb: Set(false),
dnd_start_hour: Set(None),
dnd_end_hour: Set(None),
}
.insert(&self.db)
.await?;
drop(self.room_manager.subscribe(room_id, request.user_id).await);
self.publish_room_event(
room_model.project,
super::RoomEventType::MemberJoined,
Some(room_id),
None,
None,
None,
)
.await;
let _ = self
.notification_create(super::NotificationCreateRequest {
notification_type: super::NotificationType::Invitation,
user_id: request.user_id,
title: format!("你已被邀请加入房间 {}", room_model.room_name),
content: None,
room_id: Some(room_id),
project_id: room_model.project,
related_message_id: None,
related_user_id: Some(actor_id),
related_room_id: Some(room_id),
metadata: None,
expires_at: None,
})
.await;
let created_response = {
let user_info = user_model::Entity::find()
.filter(user_model::Column::Uid.eq(request.user_id))
.one(&self.db)
.await
.ok()
.flatten()
.map(|u| super::UserInfo {
uid: u.uid,
username: u.username,
avatar_url: u.avatar_url,
});
let mut r = super::RoomMemberResponse::from(created);
r.user_info = user_info;
r
};
Ok(created_response)
}
pub async fn room_member_update_role(
&self,
room_id: Uuid,
request: super::RoomMemberRoleUpdateRequest,
ctx: &WsUserContext,
) -> Result<super::RoomMemberResponse, RoomError> {
let actor_id = ctx.user_id;
let actor = self.require_room_admin(room_id, actor_id).await?;
let target = self
.find_room_member(room_id, request.user_id)
.await?
.ok_or_else(|| RoomError::NotFound("Room member not found".to_string()))?;
if target.role == RoomMemberRole::Owner {
return Err(RoomError::NoPower);
}
let new_role = Self::parse_room_member_role(&request.role.to_lowercase())?;
if matches!(new_role, RoomMemberRole::Owner) {
return Err(RoomError::NoPower);
}
if actor.role != RoomMemberRole::Owner && matches!(new_role, RoomMemberRole::Admin) {
return Err(RoomError::NoPower);
}
let old_role = target.role.clone();
let new_role_cloned = new_role.clone();
let mut active: room_member::ActiveModel = target.into();
active.role = Set(new_role);
let updated = active.update(&self.db).await?;
let room = self.find_room_or_404(room_id).await?;
let _ = self
.notification_create(super::NotificationCreateRequest {
notification_type: super::NotificationType::RoleChange,
user_id: request.user_id,
title: format!(
"你在房间 {} 的角色已变更为 {}",
room.room_name, new_role_cloned
),
content: None,
room_id: Some(room_id),
project_id: room.project,
related_message_id: None,
related_user_id: Some(actor_id),
related_room_id: Some(room_id),
metadata: Some(serde_json::json!({
"old_role": old_role.to_string(),
"new_role": new_role_cloned.to_string(),
})),
expires_at: None,
})
.await;
let updated_response = {
let user_info = user_model::Entity::find()
.filter(user_model::Column::Uid.eq(request.user_id))
.one(&self.db)
.await
.ok()
.flatten()
.map(|u| super::UserInfo {
uid: u.uid,
username: u.username,
avatar_url: u.avatar_url,
});
let mut r = super::RoomMemberResponse::from(updated);
r.user_info = user_info;
r
};
Ok(updated_response)
}
pub async fn room_member_remove(
&self,
room_id: Uuid,
user_id: Uuid,
ctx: &WsUserContext,
) -> Result<(), RoomError> {
let actor_id = ctx.user_id;
let actor = self.require_room_admin(room_id, actor_id).await?;
let target = self
.find_room_member(room_id, user_id)
.await?
.ok_or_else(|| RoomError::NotFound("Room member not found".to_string()))?;
if target.role == RoomMemberRole::Owner {
return Err(RoomError::NoPower);
}
if actor.role == RoomMemberRole::Admin && target.role == RoomMemberRole::Admin {
return Err(RoomError::NoPower);
}
room_member::Entity::delete_by_id((room_id, user_id))
.exec(&self.db)
.await?;
self.room_manager.unsubscribe(room_id, user_id).await;
let room = self.find_room_or_404(room_id).await?;
self.publish_room_event(
room.project,
super::RoomEventType::MemberRemoved,
Some(room_id),
None,
None,
None,
)
.await;
Ok(())
}
pub async fn room_member_set_read_seq(
&self,
room_id: Uuid,
request: super::RoomMemberReadSeqRequest,
ctx: &WsUserContext,
) -> Result<super::RoomMemberResponse, RoomError> {
let user_id = ctx.user_id;
let member = self.require_room_member_model(room_id, user_id).await?;
let mut active: room_member::ActiveModel = member.into();
active.last_read_seq = Set(Some(request.last_read_seq));
let updated = active.update(&self.db).await?;
let room = self.find_room_or_404(room_id).await?;
self.publish_room_event(
room.project,
super::RoomEventType::ReadReceipt,
Some(room_id),
None,
Some(user_id),
Some(request.last_read_seq),
)
.await;
let updated_response = {
let user_info = user_model::Entity::find()
.filter(user_model::Column::Uid.eq(user_id))
.one(&self.db)
.await
.ok()
.flatten()
.map(|u| super::UserInfo {
uid: u.uid,
username: u.username,
avatar_url: u.avatar_url,
});
let mut r = super::RoomMemberResponse::from(updated);
r.user_info = user_info;
r
};
Ok(updated_response)
}
pub async fn room_member_update_dnd(
&self,
room_id: Uuid,
request: super::RoomMemberUpdateDndRequest,
ctx: &WsUserContext,
) -> Result<super::RoomMemberResponse, RoomError> {
let user_id = ctx.user_id;
let member = self.require_room_member_model(room_id, user_id).await?;
let mut active: room_member::ActiveModel = member.into();
if let Some(dnd) = request.do_not_disturb {
active.do_not_disturb = Set(dnd);
}
if let Some(start) = request.dnd_start_hour {
if !(0..=23).contains(&start) {
return Err(RoomError::BadRequest("dnd_start_hour must be 0-23".into()));
}
active.dnd_start_hour = Set(Some(start));
}
if let Some(end) = request.dnd_end_hour {
if !(0..=23).contains(&end) {
return Err(RoomError::BadRequest("dnd_end_hour must be 0-23".into()));
}
active.dnd_end_hour = Set(Some(end));
}
let updated = active.update(&self.db).await?;
let updated_response = {
let user_info = user_model::Entity::find()
.filter(user_model::Column::Uid.eq(user_id))
.one(&self.db)
.await
.ok()
.flatten()
.map(|u| super::UserInfo {
uid: u.uid,
username: u.username,
avatar_url: u.avatar_url,
});
let mut r = super::RoomMemberResponse::from(updated);
r.user_info = user_info;
r
};
Ok(updated_response)
}
}