use crate::error::RoomError; use crate::service::RoomService; use crate::ws_context::WsUserContext; use chrono::Utc; use models::rooms::{room, room_ai, room_attachment, room_category, room_message, room_message_edit_history, room_message_reaction, room_notifications, room_pin, room_thread, room_access, 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 { 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 { 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 = 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(()) } }