282 lines
9.6 KiB
Rust
282 lines
9.6 KiB
Rust
use crate::error::RoomError;
|
|
use crate::service::RoomService;
|
|
use crate::ws_context::WsUserContext;
|
|
use chrono::Utc;
|
|
use models::rooms::{
|
|
room, room_access, room_ai, room_attachment, room_category, room_message,
|
|
room_message_edit_history, room_message_reaction, room_notifications, room_pin, room_thread,
|
|
room_user_state,
|
|
};
|
|
use queue::ProjectRoomEvent;
|
|
use sea_orm::*;
|
|
use uuid::Uuid;
|
|
|
|
impl RoomService {
|
|
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?;
|
|
|
|
// Create room_user_state for creator
|
|
room_user_state::ActiveModel {
|
|
room: Set(room_model.id),
|
|
user: Set(user_id),
|
|
last_read_seq: Set(None),
|
|
do_not_disturb: Set(false),
|
|
dnd_start_hour: Set(None),
|
|
dnd_end_hour: Set(None),
|
|
joined_at: Set(Some(Utc::now())),
|
|
}
|
|
.insert(&txn)
|
|
.await?;
|
|
|
|
// For private rooms, creator is auto-granted access
|
|
if !request.public {
|
|
room_access::ActiveModel {
|
|
room: Set(room_model.id),
|
|
user: Set(user_id),
|
|
granted_by: Set(user_id),
|
|
granted_at: Set(Utc::now()),
|
|
}
|
|
.insert(&txn)
|
|
.await?;
|
|
}
|
|
|
|
txn.commit().await?;
|
|
self.invalidate_room_list_cache(project.id).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),
|
|
);
|
|
|
|
let version = Self::raw_increment_room_version(&self.cache, room_model.id).await?;
|
|
let mut resp = super::RoomResponse::from(room_model);
|
|
resp.version = version;
|
|
observability::incr!(observability::ROOMS_CREATED_TOTAL);
|
|
Ok(resp)
|
|
}
|
|
|
|
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(name) = request.room_name {
|
|
active.room_name = Set(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?;
|
|
|
|
self.invalidate_room_list_cache(updated.project).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;
|
|
}
|
|
|
|
let version = self.increment_room_version(room_id).await?;
|
|
let mut resp = super::RoomResponse::from(updated);
|
|
resp.version = version;
|
|
Ok(resp)
|
|
}
|
|
|
|
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_attachment::Entity::delete_many()
|
|
.filter(room_attachment::Column::Room.eq(room_id))
|
|
.exec(&txn)
|
|
.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_access::Entity::delete_many()
|
|
.filter(room_access::Column::Room.eq(room_id))
|
|
.exec(&txn)
|
|
.await?;
|
|
room_user_state::Entity::delete_many()
|
|
.filter(room_user_state::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_message_reaction::Entity::delete_many()
|
|
.filter(room_message_reaction::Column::Room.eq(room_id))
|
|
.exec(&txn)
|
|
.await?;
|
|
|
|
let subquery = room_message::Entity::find()
|
|
.filter(room_message::Column::Room.eq(room_id))
|
|
.select_only()
|
|
.column(room_message::Column::Id)
|
|
.into_query();
|
|
room_message_edit_history::Entity::delete_many()
|
|
.filter(room_message_edit_history::Column::Message.in_subquery(subquery))
|
|
.exec(&txn)
|
|
.await?;
|
|
|
|
room_notifications::Entity::delete_many()
|
|
.filter(room_notifications::Column::Room.eq(room_id))
|
|
.exec(&txn)
|
|
.await?;
|
|
room::Entity::delete_by_id(room_id).exec(&txn).await?;
|
|
|
|
txn.commit().await?;
|
|
self.invalidate_room_list_cache(project_id).await;
|
|
self.room_manager.shutdown_room(room_id).await;
|
|
|
|
let seq_key = format!("room:seq:{}", room_id);
|
|
if let Ok(mut conn) = self.cache.conn().await {
|
|
let _: Option<String> = redis::cmd("DEL").arg(&seq_key).query_async(&mut conn).await
|
|
.inspect_err(|e| {
|
|
tracing::warn!(seq_key = %seq_key, error = %e, "room_delete: failed to DEL seq key");
|
|
}).ok();
|
|
}
|
|
|
|
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),
|
|
);
|
|
|
|
observability::incr!(observability::ROOMS_DELETED_TOTAL);
|
|
Ok(())
|
|
}
|
|
}
|