301 lines
9.8 KiB
Rust
301 lines
9.8 KiB
Rust
use crate::error::RoomError;
|
|
use crate::service::RoomService;
|
|
use crate::ws_context::WsUserContext;
|
|
use chrono::Utc;
|
|
use models::rooms::{
|
|
RoomMemberRole, room, room_ai, room_category, room_member, room_message, room_pin, room_thread,
|
|
};
|
|
use queue::ProjectRoomEvent;
|
|
use sea_orm::*;
|
|
use uuid::Uuid;
|
|
|
|
impl RoomService {
|
|
pub async fn room_list(
|
|
&self,
|
|
project_name: String,
|
|
only_public: Option<bool>,
|
|
ctx: &WsUserContext,
|
|
) -> Result<Vec<super::RoomResponse>, RoomError> {
|
|
let user_id = ctx.user_id;
|
|
let project = self.utils_find_project_by_name(project_name).await?;
|
|
self.check_project_access(project.id, user_id).await?;
|
|
|
|
let mut query = room::Entity::find().filter(room::Column::Project.eq(project.id));
|
|
if only_public.unwrap_or(false) {
|
|
query = query.filter(room::Column::Public.eq(true));
|
|
}
|
|
|
|
let models = query
|
|
.order_by_desc(room::Column::LastMsgAt)
|
|
.all(&self.db)
|
|
.await?;
|
|
|
|
let room_ids: Vec<Uuid> = models.iter().map(|r| r.id).collect();
|
|
|
|
let latest_seqs: std::collections::HashMap<Uuid, i64> = room_message::Entity::find()
|
|
.select_only()
|
|
.column(room_message::Column::Room)
|
|
.column_as(room_message::Column::Seq.max(), "max_seq")
|
|
.filter(room_message::Column::Room.is_in(room_ids.clone()))
|
|
.group_by(room_message::Column::Room)
|
|
.into_tuple::<(Uuid, Option<i64>)>()
|
|
.all(&self.db)
|
|
.await?
|
|
.into_iter()
|
|
.map(|(room, seq)| (room, seq.unwrap_or(0)))
|
|
.collect();
|
|
|
|
let member_read_seqs: std::collections::HashMap<Uuid, i64> = room_member::Entity::find()
|
|
.filter(room_member::Column::User.eq(user_id))
|
|
.filter(room_member::Column::Room.is_in(room_ids))
|
|
.all(&self.db)
|
|
.await?
|
|
.into_iter()
|
|
.map(|m| (m.room, m.last_read_seq.unwrap_or(0)))
|
|
.collect();
|
|
|
|
let mut responses = Vec::new();
|
|
for model in models {
|
|
let last_read_seq = member_read_seqs.get(&model.id).copied().unwrap_or(0);
|
|
let latest_seq = latest_seqs.get(&model.id).copied().unwrap_or(0);
|
|
let unread_count = std::cmp::max(latest_seq - last_read_seq, 0);
|
|
|
|
let mut response = super::RoomResponse::from(model);
|
|
response.unread_count = unread_count;
|
|
responses.push(response);
|
|
}
|
|
|
|
Ok(responses)
|
|
}
|
|
|
|
pub async fn room_create(
|
|
&self,
|
|
project_name: String,
|
|
request: super::RoomCreateRequest,
|
|
ctx: &WsUserContext,
|
|
) -> Result<super::RoomResponse, RoomError> {
|
|
let user_id = ctx.user_id;
|
|
let project = self.utils_find_project_by_name(project_name).await?;
|
|
self.require_project_admin(project.id, user_id).await?;
|
|
|
|
Self::validate_name(&request.room_name, super::MAX_ROOM_NAME_LEN)?;
|
|
|
|
if let Some(category_id) = request.category {
|
|
let category = room_category::Entity::find_by_id(category_id)
|
|
.one(&self.db)
|
|
.await?
|
|
.ok_or_else(|| RoomError::NotFound("Room category not found".to_string()))?;
|
|
if category.project != project.id {
|
|
return Err(RoomError::BadRequest(
|
|
"category does not belong to this project".to_string(),
|
|
));
|
|
}
|
|
}
|
|
|
|
let txn = self.db.begin().await?;
|
|
|
|
let room_name = request.room_name.clone();
|
|
let room_model = room::ActiveModel {
|
|
id: Set(Uuid::now_v7()),
|
|
project: Set(project.id),
|
|
room_name: Set(request.room_name),
|
|
public: Set(request.public),
|
|
category: Set(request.category),
|
|
created_by: Set(user_id),
|
|
created_at: Set(Utc::now()),
|
|
last_msg_at: Set(Utc::now()),
|
|
}
|
|
.insert(&txn)
|
|
.await?;
|
|
|
|
room_member::ActiveModel {
|
|
room: Set(room_model.id),
|
|
user: Set(user_id),
|
|
role: Set(RoomMemberRole::Owner),
|
|
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(&txn)
|
|
.await?;
|
|
|
|
txn.commit().await?;
|
|
|
|
self.spawn_room_workers(room_model.id);
|
|
|
|
let event = ProjectRoomEvent {
|
|
event_type: super::RoomEventType::RoomCreated.as_str().into(),
|
|
project_id: project.id,
|
|
room_id: Some(room_model.id),
|
|
category_id: None,
|
|
message_id: None,
|
|
seq: None,
|
|
timestamp: Utc::now(),
|
|
};
|
|
let _ = self
|
|
.queue
|
|
.publish_project_room_event(project.id, event)
|
|
.await;
|
|
|
|
self.notify_project_members(
|
|
project.id,
|
|
super::NotificationType::RoomCreated,
|
|
format!("新房间已创建: {}", room_name),
|
|
None,
|
|
Some(room_model.id),
|
|
);
|
|
|
|
Ok(super::RoomResponse::from(room_model))
|
|
}
|
|
|
|
pub async fn room_get(
|
|
&self,
|
|
room_id: Uuid,
|
|
ctx: &WsUserContext,
|
|
) -> Result<super::RoomResponse, RoomError> {
|
|
let user_id = ctx.user_id;
|
|
let model = self.find_room_or_404(room_id).await?;
|
|
self.ensure_room_visible_for_user(&model, user_id).await?;
|
|
Ok(super::RoomResponse::from(model))
|
|
}
|
|
|
|
pub async fn room_update(
|
|
&self,
|
|
room_id: Uuid,
|
|
request: super::RoomUpdateRequest,
|
|
ctx: &WsUserContext,
|
|
) -> Result<super::RoomResponse, RoomError> {
|
|
let user_id = ctx.user_id;
|
|
let room_model = self.find_room_or_404(room_id).await?;
|
|
self.require_room_admin(room_id, user_id).await?;
|
|
|
|
if let Some(category_id) = request.category {
|
|
let category = room_category::Entity::find_by_id(category_id)
|
|
.one(&self.db)
|
|
.await?
|
|
.ok_or_else(|| RoomError::NotFound("Room category not found".to_string()))?;
|
|
if category.project != room_model.project {
|
|
return Err(RoomError::BadRequest(
|
|
"category does not belong to this project".to_string(),
|
|
));
|
|
}
|
|
}
|
|
|
|
let mut active: room::ActiveModel = room_model.into();
|
|
|
|
let renamed = request.room_name.is_some();
|
|
let moved = request.category.is_some();
|
|
|
|
if let Some(room_name) = request.room_name {
|
|
active.room_name = Set(room_name);
|
|
}
|
|
if let Some(public) = request.public {
|
|
active.public = Set(public);
|
|
}
|
|
if request.category.is_some() {
|
|
active.category = Set(request.category);
|
|
}
|
|
let updated = active.update(&self.db).await?;
|
|
|
|
if renamed {
|
|
let event = ProjectRoomEvent {
|
|
event_type: super::RoomEventType::RoomRenamed.as_str().into(),
|
|
project_id: updated.project,
|
|
room_id: Some(updated.id),
|
|
category_id: None,
|
|
message_id: None,
|
|
seq: None,
|
|
timestamp: Utc::now(),
|
|
};
|
|
let _ = self
|
|
.queue
|
|
.publish_project_room_event(updated.project, event)
|
|
.await;
|
|
}
|
|
if moved {
|
|
let event = ProjectRoomEvent {
|
|
event_type: super::RoomEventType::RoomMoved.as_str().into(),
|
|
project_id: updated.project,
|
|
room_id: Some(updated.id),
|
|
category_id: None,
|
|
message_id: None,
|
|
seq: None,
|
|
timestamp: Utc::now(),
|
|
};
|
|
let _ = self
|
|
.queue
|
|
.publish_project_room_event(updated.project, event)
|
|
.await;
|
|
}
|
|
|
|
Ok(super::RoomResponse::from(updated))
|
|
}
|
|
|
|
pub async fn room_delete(&self, room_id: Uuid, ctx: &WsUserContext) -> Result<(), RoomError> {
|
|
let user_id = ctx.user_id;
|
|
let room_model = self.find_room_or_404(room_id).await?;
|
|
self.require_room_admin(room_id, user_id).await?;
|
|
let project_id = room_model.project;
|
|
|
|
let txn = self.db.begin().await?;
|
|
|
|
room_message::Entity::delete_many()
|
|
.filter(room_message::Column::Room.eq(room_id))
|
|
.exec(&txn)
|
|
.await?;
|
|
|
|
room_pin::Entity::delete_many()
|
|
.filter(room_pin::Column::Room.eq(room_id))
|
|
.exec(&txn)
|
|
.await?;
|
|
|
|
room_thread::Entity::delete_many()
|
|
.filter(room_thread::Column::Room.eq(room_id))
|
|
.exec(&txn)
|
|
.await?;
|
|
|
|
room_member::Entity::delete_many()
|
|
.filter(room_member::Column::Room.eq(room_id))
|
|
.exec(&txn)
|
|
.await?;
|
|
|
|
room_ai::Entity::delete_many()
|
|
.filter(room_ai::Column::Room.eq(room_id))
|
|
.exec(&txn)
|
|
.await?;
|
|
|
|
room::Entity::delete_by_id(room_id).exec(&txn).await?;
|
|
|
|
txn.commit().await?;
|
|
|
|
self.room_manager.shutdown_room(room_id).await;
|
|
|
|
let event = ProjectRoomEvent {
|
|
event_type: super::RoomEventType::RoomDeleted.as_str().into(),
|
|
project_id,
|
|
room_id: Some(room_id),
|
|
category_id: None,
|
|
message_id: None,
|
|
seq: None,
|
|
timestamp: Utc::now(),
|
|
};
|
|
let _ = self
|
|
.queue
|
|
.publish_project_room_event(project_id, event)
|
|
.await;
|
|
|
|
self.notify_project_members(
|
|
project_id,
|
|
super::NotificationType::RoomDeleted,
|
|
format!("房间 {} 已被删除", room_model.room_name),
|
|
None,
|
|
Some(room_id),
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
}
|