use room::ws_context::WsUserContext; use crate::error::AppTransportError; use crate::event::{category, message, reaction}; use super::session::TransportSession; use super::types::{WsInMessage, WsOutEvent}; pub struct MessageHandler; impl MessageHandler { pub async fn handle( session: &TransportSession, msg: WsInMessage, ) -> Result, AppTransportError> { match msg { WsInMessage::Ping => Ok(Some(WsOutEvent::Pong { protocol_version: super::types::WS_PROTOCOL_VERSION })), WsInMessage::Subscribe { room } => { let sub = session.subscribe_room(room).await?; session.subscriptions.insert(room, sub); Ok(None) } WsInMessage::Unsubscribe { room } => { session.unsubscribe_room(room).await; Ok(None) } WsInMessage::TypingStart { room } => { session.broadcast_typing(room, "start").await; Ok(None) } WsInMessage::TypingStop { room } => { session.broadcast_typing(room, "stop").await; Ok(None) } WsInMessage::ReadReceipt { room, last_read_seq } => { let ctx = WsUserContext::new(session.user.user_id); session.service.room.room_user_state_update_read_seq( room, last_read_seq, &ctx, ).await.map_err(|_| AppTransportError::Internal)?; Ok(None) } WsInMessage::MessageCreate { room, content, content_type, thread, in_reply_to } => { let ctx = WsUserContext::new(session.user.user_id); let msg = session.service.room.room_message_create( room, room::RoomMessageCreateRequest { content, content_type, thread, in_reply_to, attachment_ids: vec![], }, &ctx, ).await.map_err(|_| AppTransportError::Internal)?; Ok(Some(WsOutEvent::MessageNew { room_id: room, data: message::MessageNewService { id: msg.id, seq: msg.seq, room: msg.room, sender_type: msg.sender_type, sender_id: msg.sender_id, display_name: msg.display_name, thread: msg.thread, in_reply_to: msg.in_reply_to, content: msg.content, content_type: msg.content_type, thinking_content: msg.thinking_content, thinking_is_chunked: false, send_at: msg.send_at, }, })) } WsInMessage::MessageUpdate { message, content } => { let ctx = WsUserContext::new(session.user.user_id); session.service.room.room_message_update( message, room::RoomMessageUpdateRequest { content }, &ctx, ).await.map_err(|_| AppTransportError::Internal)?; Ok(None) } WsInMessage::MessageRevoke { message } => { let ctx = WsUserContext::new(session.user.user_id); session.service.room.room_message_revoke(message, &ctx) .await.map_err(|_| AppTransportError::Internal)?; Ok(None) } WsInMessage::RoomCreate { project, room_name, public, category } => { let ctx = WsUserContext::new(session.user.user_id); let rm = session.service.room.room_create( project.to_string(), room::RoomCreateRequest { room_name, public, category }, &ctx, ).await.map_err(|_| AppTransportError::Internal)?; Ok(Some(WsOutEvent::RoomCreated { room_id: rm.id, data: crate::event::rooms::RoomCreatedService { id: rm.id, project, room_name: rm.room_name, public: rm.public, category, created_by: session.user.user_id, created_at: rm.created_at, }, })) } WsInMessage::RoomUpdate { room, room_name, public, category } => { let ctx = WsUserContext::new(session.user.user_id); session.service.room.room_update( room, room::RoomUpdateRequest { room_name, public, category }, &ctx, ).await.map_err(|_| AppTransportError::Internal)?; Ok(None) } WsInMessage::RoomDelete { room } => { let ctx = WsUserContext::new(session.user.user_id); session.service.room.room_delete(room, &ctx) .await.map_err(|_| AppTransportError::Internal)?; Ok(None) } WsInMessage::CategoryCreate { project, name, position } => { let ctx = WsUserContext::new(session.user.user_id); let cat = session.service.room.room_category_create( project.to_string(), room::RoomCategoryCreateRequest { name, position }, &ctx, ).await.map_err(|_| AppTransportError::Internal)?; Ok(Some(WsOutEvent::CategoryCreated { project, data: category::CategoryCreatedService { id: cat.id, project, name: cat.name, position: cat.position, created_by: session.user.user_id, created_at: cat.created_at, }, })) } WsInMessage::CategoryUpdate { id, name, position } => { let ctx = WsUserContext::new(session.user.user_id); session.service.room.room_category_update( id, room::RoomCategoryUpdateRequest { name, position }, &ctx, ).await.map_err(|_| AppTransportError::Internal)?; Ok(None) } WsInMessage::CategoryDelete { id } => { let ctx = WsUserContext::new(session.user.user_id); session.service.room.room_category_delete(id, &ctx) .await.map_err(|_| AppTransportError::Internal)?; Ok(None) } WsInMessage::AccessGrant { room, user } => { let ctx = WsUserContext::new(session.user.user_id); session.service.room.room_access_grant(room, user, &ctx) .await.map_err(|_| AppTransportError::Internal)?; Ok(None) } WsInMessage::AccessRevoke { room, user } => { let ctx = WsUserContext::new(session.user.user_id); session.service.room.room_access_revoke(room, user, &ctx) .await.map_err(|_| AppTransportError::Internal)?; Ok(None) } WsInMessage::ReactionAdd { room, message, emoji } => { let ctx = WsUserContext::new(session.user.user_id); let rxs = session.service.room.message_reaction_add(message, emoji, &ctx) .await.map_err(|_| AppTransportError::Internal)?; Ok(Some(WsOutEvent::ReactionBatchUpdated { room_id: room, data: reaction::ReactionBatchUpdatedService { room: room, message: message, reactions: rxs.reactions.into_iter().map(|g| reaction::ReactionGroup { emoji: g.emoji, count: g.count as i64, reacted_by_me: g.reacted_by_me, users: g.users.iter().filter_map(|u| u.parse::().ok()).collect(), }).collect(), }, })) } WsInMessage::ReactionRemove { room, message, emoji } => { let ctx = WsUserContext::new(session.user.user_id); let rxs = session.service.room.message_reaction_remove(message, emoji, &ctx) .await.map_err(|_| AppTransportError::Internal)?; Ok(Some(WsOutEvent::ReactionBatchUpdated { room_id: room, data: reaction::ReactionBatchUpdatedService { room: room, message: message, reactions: rxs.reactions.into_iter().map(|g| reaction::ReactionGroup { emoji: g.emoji, count: g.count as i64, reacted_by_me: g.reacted_by_me, users: g.users.iter().filter_map(|u| u.parse::().ok()).collect(), }).collect(), }, })) } WsInMessage::ThreadCreate { room, parent } => { let ctx = WsUserContext::new(session.user.user_id); session.service.room.room_thread_create( room, room::RoomThreadCreateRequest { parent_seq: parent }, &ctx, ).await.map_err(|_| AppTransportError::Internal)?; Ok(None) } WsInMessage::ThreadResolve { .. } => Ok(None), WsInMessage::ThreadArchive { .. } => Ok(None), WsInMessage::PinAdd { room: _, message } => { let ctx = WsUserContext::new(session.user.user_id); session.service.room.room_pin_add(message, &ctx) .await.map_err(|_| AppTransportError::Internal)?; Ok(None) } WsInMessage::PinRemove { room: _, message } => { let ctx = WsUserContext::new(session.user.user_id); session.service.room.room_pin_remove(message, &ctx) .await.map_err(|_| AppTransportError::Internal)?; Ok(None) } WsInMessage::DraftSave { .. } | WsInMessage::DraftClear { .. } => { // TODO: draft service not yet implemented in room crate Ok(None) } WsInMessage::NotificationMarkRead { id } => { let ctx = WsUserContext::new(session.user.user_id); session.service.room.notification_mark_read(id, &ctx) .await.map_err(|_| AppTransportError::Internal)?; Ok(None) } WsInMessage::NotificationMarkAllRead { .. } => { let ctx = WsUserContext::new(session.user.user_id); session.service.room.notification_mark_all_read(&ctx) .await.map_err(|_| AppTransportError::Internal)?; Ok(None) } WsInMessage::NotificationArchive { id } => { let ctx = WsUserContext::new(session.user.user_id); session.service.room.notification_archive(id, &ctx) .await.map_err(|_| AppTransportError::Internal)?; Ok(None) } // ── Placeholder actions (TODO: implement service calls) ── WsInMessage::Search { .. } => Ok(None), WsInMessage::PresenceUpdate { .. } => Ok(None), WsInMessage::CustomStatusUpdate { .. } => Ok(None), WsInMessage::InviteCreate { .. } => Ok(None), WsInMessage::InviteAccept { .. } => Ok(None), WsInMessage::InviteRevoke { .. } => Ok(None), WsInMessage::BanCreate { .. } => Ok(None), WsInMessage::BanRemove { .. } => Ok(None), WsInMessage::VoiceJoin { .. } => Ok(None), WsInMessage::VoiceLeave { .. } => Ok(None), WsInMessage::VoiceMute { .. } => Ok(None), WsInMessage::VoiceDeaf { .. } => Ok(None), WsInMessage::ScreenShare { .. } => Ok(None), WsInMessage::AiList { .. } => Ok(None), WsInMessage::AiUpsert { .. } => Ok(None), WsInMessage::AiDelete { .. } => Ok(None), WsInMessage::StateSetReadSeq { room, last_read_seq } => { let ctx = WsUserContext::new(session.user.user_id); session.service.room.room_user_state_update_read_seq( room, last_read_seq, &ctx, ).await.map_err(|_| AppTransportError::Internal)?; Ok(None) } WsInMessage::StateUpdateDnd { room, do_not_disturb, dnd_start_hour, dnd_end_hour } => { let ctx = WsUserContext::new(session.user.user_id); session.service.room.room_user_state_update_dnd( room, room::RoomUserStateUpdateDndRequest { do_not_disturb, dnd_start_hour, dnd_end_hour, }, &ctx, ).await.map_err(|_| AppTransportError::Internal)?; Ok(None) } } } }