use crate::command::context::CommandContext; use crate::command::handler::{CommandHandler, CommandMetadata}; use crate::command::handlers::list_topics::TopicSummary; use crate::command::response::{CommandError, CommandResponse, MessageKind}; use crate::command::Command; use crate::gateway::session::SessionManager; use crate::storage::SessionStore; use async_trait::async_trait; use std::sync::Arc; /// 删除话题命令处理器 pub struct DeleteTopicCommandHandler { store: Arc, session_manager: Option, } impl DeleteTopicCommandHandler { pub fn new(store: Arc) -> Self { Self { store, session_manager: None, } } pub fn with_session_manager(mut self, session_manager: SessionManager) -> Self { self.session_manager = Some(session_manager); self } } #[async_trait] impl CommandHandler for DeleteTopicCommandHandler { fn can_handle(&self, cmd: &Command) -> bool { matches!(cmd, Command::DeleteTopic { .. }) } fn metadata(&self) -> Option { Some(CommandMetadata { name: "delete", description: "删除指定话题", usage: "/delete ", }) } async fn handle( &self, cmd: Command, ctx: CommandContext, ) -> Result { match cmd { Command::DeleteTopic { topic_id } => handle_delete_topic(self, topic_id, ctx).await, _ => unreachable!(), } } } async fn handle_delete_topic( handler: &DeleteTopicCommandHandler, topic_id: String, ctx: CommandContext, ) -> Result { let session_id = ctx .session_id .as_deref() .ok_or_else(|| CommandError::new("NO_SESSION", "No active session"))?; // 验证话题存在 let topic = handler .store .get_topic(&topic_id) .map_err(|e| CommandError::new("GET_TOPIC_ERROR", e.to_string()))? .ok_or_else(|| { CommandError::new("TOPIC_NOT_FOUND", format!("Topic not found: {}", topic_id)) })?; let topic_title = topic.title.clone(); // 删除话题(存储层方法已存在) handler .store .delete_topic(&topic_id) .map_err(|e| CommandError::new("DELETE_TOPIC_ERROR", e.to_string()))?; // 查询更新后的话题列表,返回给前端刷新侧边栏 let topics = handler .store .list_topics(session_id) .map_err(|e| CommandError::new("LIST_TOPICS_ERROR", e.to_string()))?; let topic_summaries: Vec = topics .into_iter() .map(|t| TopicSummary { topic_id: t.id, session_id: t.session_id, title: t.title, description: t.description.filter(|d| !d.is_empty()), message_count: t.message_count, created_at: t.created_at, last_active_at: t.last_active_at, }) .collect(); let topics_json = serde_json::to_string(&topic_summaries) .map_err(|e| CommandError::new("SERIALIZE_ERROR", e.to_string()))?; let message = format!("✓ 已删除话题: {}", topic_title); Ok(CommandResponse::success(ctx.request_id) .with_message(MessageKind::Notification, &message) .with_metadata("topics", &topics_json) .with_metadata("deleted_topic_id", &topic_id)) } #[cfg(test)] mod tests { use super::*; use crate::storage::SessionStore; use std::sync::Arc; fn create_test_handler() -> DeleteTopicCommandHandler { let store = Arc::new(SessionStore::in_memory().unwrap()); DeleteTopicCommandHandler::new(store) } #[tokio::test] async fn test_delete_topic_success() { let handler = create_test_handler(); let store = handler.store.clone(); // 先创建 session 和 topic let session = store.create_session("test_channel", Some("test")).unwrap(); let topic = store .create_topic(&session.id, "test topic", None) .unwrap(); let ctx = CommandContext::new("test", "test_channel") .with_session_id(&session.id) .with_chat_id(&session.id); let cmd = Command::DeleteTopic { topic_id: topic.id.clone(), }; let result = handler.handle(cmd, ctx).await; assert!(result.is_ok()); let resp = result.unwrap(); assert!(resp.success); assert!(resp.metadata.contains_key("deleted_topic_id")); // 验证话题已被删除 let deleted = store.get_topic(&topic.id).unwrap(); assert!(deleted.is_none()); } #[tokio::test] async fn test_delete_nonexistent_topic() { let handler = create_test_handler(); let store = handler.store.clone(); let session = store.create_session("test_channel", Some("test")).unwrap(); let ctx = CommandContext::new("test", "test_channel") .with_session_id(&session.id) .with_chat_id(&session.id); let cmd = Command::DeleteTopic { topic_id: "nonexistent".to_string(), }; let result = handler.handle(cmd, ctx).await; assert!(result.is_err()); } #[test] fn test_can_handle() { let handler = create_test_handler(); assert!(handler.can_handle(&Command::DeleteTopic { topic_id: "test".to_string() })); assert!(!handler.can_handle(&Command::Help)); } }