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, ctx: &WsUserContext, ) -> Result, 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 = models.iter().map(|r| r.id).collect(); let latest_seqs: std::collections::HashMap = 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)>() .all(&self.db) .await? .into_iter() .map(|(room, seq)| (room, seq.unwrap_or(0))) .collect(); let member_read_seqs: std::collections::HashMap = 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 { 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 { 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 { 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(()) } }