gitdataai/lib/channel/http/handler/subscription.rs

179 lines
5.7 KiB
Rust

use chrono::Utc;
use uuid::Uuid;
use crate::event::{RoomInfo, UserInfo, member};
use crate::{ChannelBus, ChannelResult};
use super::WsHandler;
use super::WsOutEvent;
impl WsHandler {
pub(super) async fn subscribe(
bus: &ChannelBus,
user_id: Uuid,
_room: Uuid,
) -> ChannelResult<Option<WsOutEvent>> {
bus.refresh_user(user_id).await?;
Ok(None)
}
pub(super) async fn unsubscribe(
_bus: &ChannelBus,
_user_id: Uuid,
_room: Uuid,
) -> ChannelResult<Option<WsOutEvent>> {
Ok(None)
}
pub(super) async fn typing(
bus: &ChannelBus,
room: Uuid,
user_id: Uuid,
action: &str,
) -> ChannelResult<Option<WsOutEvent>> {
Self::ensure_room_access(bus, user_id, room).await?;
let key = (room, user_id);
if action == "start" {
let ty_room = bus
.lookup_room(room)
.await
.unwrap_or_else(|_| RoomInfo::unknown(room));
let ty_user = bus
.lookup_user(user_id)
.await
.unwrap_or_else(|_| UserInfo::unknown(user_id));
let already_typing = bus.inner.typing_states.contains_key(&key);
if let Some((_, (_, _, old_cancel))) =
bus.inner.typing_states.remove(&key)
{
old_cancel.cancel();
}
let cancel = tokio_util::sync::CancellationToken::new();
let cancel_clone = cancel.clone();
let bus_clone = bus.clone();
let user_clone = ty_user.clone();
let room_clone = ty_room.clone();
bus.inner
.typing_states
.insert(key, (ty_user.clone(), ty_room.clone(), cancel));
tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
if cancel_clone.is_cancelled() {
return;
}
bus_clone
.inner
.typing_states
.remove(&(room_clone.id, user_clone.id));
let room_id = room_clone.id;
let stop_data = member::TypingStopService {
room: room_clone,
user: user_clone,
sender_type: "user".to_string(),
stopped_at: Utc::now(),
};
let _ = bus_clone
.publish_room_event(room_id, "typing.stop", &stop_data)
.await;
});
if !already_typing {
let data = member::TypingStartService {
room: ty_room,
user: ty_user,
sender_type: "user".to_string(),
started_at: Utc::now(),
};
bus.publish_room_event(room, "typing.start", &data).await?;
return Ok(Some(WsOutEvent::TypingStart {
room: data.room.clone(),
data,
}));
}
Ok(None)
} else {
if let Some((_, (_, _, cancel))) =
bus.inner.typing_states.remove(&key)
{
cancel.cancel();
}
let ty_room = bus
.lookup_room(room)
.await
.unwrap_or_else(|_| RoomInfo::unknown(room));
let ty_user = bus
.lookup_user(user_id)
.await
.unwrap_or_else(|_| UserInfo::unknown(user_id));
let data = member::TypingStopService {
room: ty_room,
user: ty_user,
sender_type: "user".to_string(),
stopped_at: Utc::now(),
};
bus.publish_room_event(room, "typing.stop", &data).await?;
Ok(Some(WsOutEvent::TypingStop {
room: data.room.clone(),
data,
}))
}
}
pub(super) async fn read_receipt(
bus: &ChannelBus,
user_id: Uuid,
room: Uuid,
last_read_seq: i64,
) -> ChannelResult<Option<WsOutEvent>> {
Self::ensure_room_access(bus, user_id, room).await?;
bus.inner
.reconnect
.save_client_state(user_id, room, last_read_seq)
.await?;
db::sqlx::query(
"INSERT INTO user_room_state (\"user\", room, last_read_seq, last_read_at, updated_at) \
VALUES ($1, $2, $3, now(), now()) \
ON CONFLICT (\"user\", room) DO UPDATE \
SET last_read_seq = GREATEST(user_room_state.last_read_seq, $3), \
last_read_at = now(), updated_at = now()",
)
.bind(user_id)
.bind(room)
.bind(last_read_seq)
.execute(bus.inner.db.writer())
.await?;
let rr_room = bus
.lookup_room(room)
.await
.unwrap_or_else(|_| RoomInfo::unknown(room));
let rr_user = bus
.lookup_user(user_id)
.await
.unwrap_or_else(|_| UserInfo::unknown(user_id));
let data = member::ReadReceiptService {
room: rr_room.clone(),
user: rr_user,
last_read_seq,
updated_at: Utc::now(),
};
bus.publish_room_event(room, "member.read_receipt", &data)
.await?;
Ok(Some(WsOutEvent::ReadReceipt {
room: rr_room,
data,
}))
}
pub(super) async fn csrf_token(
bus: &ChannelBus,
user_id: Uuid,
) -> ChannelResult<Option<WsOutEvent>> {
let token = bus.inner.csrf.generate_token(user_id).await?;
Ok(Some(WsOutEvent::CsrfToken { token }))
}
}