gitdataai/libs/transport/handler/inbound.rs
ZhenYi 14f6e1e500 feat(core): initialize project with access control and AI integration
- Add gitignore and prettier configuration files for project scaffolding
- Implement room access control service with project member verification
- Create user access key management with CRUD operations and activity logging
- Add accordion UI component for frontend expandable sections
- Implement room AI configuration with list, upsert, and delete operations
- Add AI event types for agent join/leave/status change tracking
- Create streaming AI processing services for mode and react patterns
- Build room AI service with model detection and idempotency handling
- Integrate chat service orchestration for AI message processing
- Add typing indicators and stream cancellation for AI interactions
- Implement mention parsing and context extraction for AI agents
2026-05-03 06:04:31 +08:00

290 lines
13 KiB
Rust

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<Option<WsOutEvent>, 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::<uuid::Uuid>().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::<uuid::Uuid>().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)
}
}
}
}