249 lines
8.4 KiB
Rust
249 lines
8.4 KiB
Rust
use chrono::Utc;
|
|
use uuid::Uuid;
|
|
|
|
use crate::event::{RoomInfo, UserInfo, dm};
|
|
use crate::{ChannelBus, ChannelResult};
|
|
|
|
use super::WsOutEvent;
|
|
use super::WsHandler;
|
|
|
|
impl WsHandler {
|
|
pub(super) async fn dm_create(
|
|
bus: &ChannelBus,
|
|
user_id: Uuid,
|
|
recipient: Uuid,
|
|
) -> ChannelResult<Option<WsOutEvent>> {
|
|
if user_id == recipient {
|
|
return Err(crate::ChannelError::Internal(
|
|
"cannot create DM with yourself".to_string(),
|
|
));
|
|
}
|
|
let recipient_exists: Option<(Uuid,)> = db::sqlx::query_as(
|
|
"SELECT id FROM \"user\" WHERE id = $1",
|
|
)
|
|
.bind(recipient)
|
|
.fetch_optional(bus.inner.db.reader())
|
|
.await?;
|
|
if recipient_exists.is_none() {
|
|
return Err(crate::ChannelError::UserNotFound);
|
|
}
|
|
let (initiator, other) = if user_id < recipient {
|
|
(user_id, recipient)
|
|
} else {
|
|
(recipient, user_id)
|
|
};
|
|
let existing: Option<(Uuid, Uuid, bool)> = db::sqlx::query_as(
|
|
"SELECT room, initiator, is_closed FROM dm_conversation \
|
|
WHERE initiator = $1 AND recipient = $2",
|
|
)
|
|
.bind(initiator)
|
|
.bind(other)
|
|
.fetch_optional(bus.inner.db.reader())
|
|
.await?;
|
|
|
|
let now = Utc::now();
|
|
|
|
let (room_id, is_reopen) = if let Some((room, _, is_closed)) = existing {
|
|
if is_closed {
|
|
db::sqlx::query(
|
|
"UPDATE dm_conversation SET is_closed = false, closed_at = NULL, \
|
|
updated_at = now() WHERE initiator = $1 AND recipient = $2",
|
|
)
|
|
.bind(initiator)
|
|
.bind(other)
|
|
.execute(bus.inner.db.writer())
|
|
.await?;
|
|
db::sqlx::query(
|
|
"UPDATE room SET is_archived = false, updated_at = now() WHERE id = $1",
|
|
)
|
|
.bind(room)
|
|
.execute(bus.inner.db.writer())
|
|
.await?;
|
|
|
|
(room, true)
|
|
} else {
|
|
(room, false)
|
|
}
|
|
} else {
|
|
let shared_wk: Option<(Uuid,)> = db::sqlx::query_as(
|
|
"SELECT wm1.wk FROM wk_member wm1 \
|
|
INNER JOIN wk_member wm2 ON wm2.wk = wm1.wk \
|
|
WHERE wm1.\"user\" = $1 AND wm1.leave_at IS NULL \
|
|
AND wm2.\"user\" = $2 AND wm2.leave_at IS NULL \
|
|
LIMIT 1",
|
|
)
|
|
.bind(user_id)
|
|
.bind(recipient)
|
|
.fetch_optional(bus.inner.db.reader())
|
|
.await?;
|
|
|
|
let wk = shared_wk.map(|r| r.0).unwrap_or_else(|| {
|
|
Uuid::nil()
|
|
});
|
|
let room_id = Uuid::new_v4();
|
|
db::sqlx::query(
|
|
"INSERT INTO room (id, wk, name, topic, room_type, position, is_private, \
|
|
created_by, created_at, updated_at) \
|
|
VALUES ($1, $2, $3, NULL, 'DM', 0, true, $4, now(), now())",
|
|
)
|
|
.bind(room_id)
|
|
.bind(wk)
|
|
.bind(format!("dm-{}", &room_id.to_string()[..8]))
|
|
.bind(user_id)
|
|
.execute(bus.inner.db.writer())
|
|
.await?;
|
|
db::sqlx::query(
|
|
"INSERT INTO dm_conversation (room, initiator, recipient, created_at, updated_at) \
|
|
VALUES ($1, $2, $3, now(), now()) \
|
|
ON CONFLICT (initiator, recipient) DO NOTHING",
|
|
)
|
|
.bind(room_id)
|
|
.bind(initiator)
|
|
.bind(other)
|
|
.execute(bus.inner.db.writer())
|
|
.await?;
|
|
for uid in &[user_id, recipient] {
|
|
db::sqlx::query(
|
|
"INSERT INTO room_permission_overwrite \
|
|
(room, target_type, target_id, allow_mask, deny_mask, created_at) \
|
|
VALUES ($1, 'user', $2, 0, 0, now()) \
|
|
ON CONFLICT DO NOTHING",
|
|
)
|
|
.bind(room_id)
|
|
.bind(uid)
|
|
.execute(bus.inner.db.writer())
|
|
.await?;
|
|
}
|
|
|
|
(room_id, false)
|
|
};
|
|
let _ = crate::rooms::refresh_user_rooms_cache(
|
|
&bus.inner.db,
|
|
&bus.inner.cache,
|
|
&bus.inner.config,
|
|
user_id,
|
|
)
|
|
.await;
|
|
let _ = crate::rooms::refresh_user_rooms_cache(
|
|
&bus.inner.db,
|
|
&bus.inner.cache,
|
|
&bus.inner.config,
|
|
recipient,
|
|
)
|
|
.await;
|
|
|
|
let room_info =
|
|
bus.lookup_room(room_id).await.unwrap_or_else(|_| RoomInfo::unknown(room_id));
|
|
let initiator_info =
|
|
bus.lookup_user(user_id).await.unwrap_or_else(|_| UserInfo::unknown(user_id));
|
|
let recipient_info =
|
|
bus.lookup_user(recipient).await.unwrap_or_else(|_| UserInfo::unknown(recipient));
|
|
|
|
if is_reopen {
|
|
let data = dm::DmReopenedService {
|
|
room: room_info.clone(),
|
|
reopened_by: initiator_info,
|
|
reopened_at: now,
|
|
};
|
|
bus.emit_to_user(user_id, "dm.reopened", &data).await?;
|
|
bus.emit_to_user(recipient, "dm.reopened", &data).await?;
|
|
Ok(Some(WsOutEvent::DmReopened {
|
|
room: room_info,
|
|
data,
|
|
}))
|
|
} else {
|
|
let data = dm::DmCreatedService {
|
|
room: room_info.clone(),
|
|
initiator: initiator_info,
|
|
recipient: recipient_info,
|
|
created_at: now,
|
|
};
|
|
bus.emit_to_user(user_id, "dm.created", &data).await?;
|
|
bus.emit_to_user(recipient, "dm.created", &data).await?;
|
|
Ok(Some(WsOutEvent::DmCreated {
|
|
room: room_info,
|
|
data,
|
|
}))
|
|
}
|
|
}
|
|
pub(super) async fn dm_close(
|
|
bus: &ChannelBus,
|
|
user_id: Uuid,
|
|
room: Uuid,
|
|
) -> ChannelResult<Option<WsOutEvent>> {
|
|
let now = Utc::now();
|
|
|
|
let result = db::sqlx::query(
|
|
"UPDATE dm_conversation SET is_closed = true, closed_at = $1, updated_at = $1 \
|
|
WHERE room = $2 AND (initiator = $3 OR recipient = $3) AND is_closed = false",
|
|
)
|
|
.bind(now)
|
|
.bind(room)
|
|
.bind(user_id)
|
|
.execute(bus.inner.db.writer())
|
|
.await?;
|
|
|
|
if result.rows_affected() == 0 {
|
|
return Ok(None);
|
|
}
|
|
|
|
let room_info =
|
|
bus.lookup_room(room).await.unwrap_or_else(|_| RoomInfo::unknown(room));
|
|
let closed_by =
|
|
bus.lookup_user(user_id).await.unwrap_or_else(|_| UserInfo::unknown(user_id));
|
|
|
|
let data = dm::DmClosedService {
|
|
room: room_info.clone(),
|
|
closed_by,
|
|
closed_at: now,
|
|
};
|
|
bus.publish_room_event(room, "dm.closed", &data).await?;
|
|
Ok(Some(WsOutEvent::DmClosed {
|
|
room: room_info,
|
|
data,
|
|
}))
|
|
}
|
|
pub(super) async fn dm_list(
|
|
bus: &ChannelBus,
|
|
user_id: Uuid,
|
|
) -> ChannelResult<Option<WsOutEvent>> {
|
|
let rows = db::sqlx::query_as::<_, (Uuid, Uuid, Uuid, chrono::DateTime<Utc>)>(
|
|
"SELECT dc.room, dc.initiator, dc.recipient, dc.created_at \
|
|
FROM dm_conversation dc \
|
|
INNER JOIN room r ON r.id = dc.room \
|
|
WHERE (dc.initiator = $1 OR dc.recipient = $1) \
|
|
AND dc.is_closed = false \
|
|
AND r.deleted_at IS NULL \
|
|
ORDER BY dc.updated_at DESC",
|
|
)
|
|
.bind(user_id)
|
|
.fetch_all(bus.inner.db.reader())
|
|
.await?;
|
|
|
|
let mut results = Vec::with_capacity(rows.len());
|
|
for (room_id, initiator_id, recipient_id, created_at) in rows {
|
|
let room_info = bus
|
|
.lookup_room(room_id)
|
|
.await
|
|
.unwrap_or_else(|_| RoomInfo::unknown(room_id));
|
|
let initiator_info = bus
|
|
.lookup_user(initiator_id)
|
|
.await
|
|
.unwrap_or_else(|_| UserInfo::unknown(initiator_id));
|
|
let recipient_info = bus
|
|
.lookup_user(recipient_id)
|
|
.await
|
|
.unwrap_or_else(|_| UserInfo::unknown(recipient_id));
|
|
|
|
results.push(dm::DmCreatedService {
|
|
room: room_info,
|
|
initiator: initiator_info,
|
|
recipient: recipient_info,
|
|
created_at,
|
|
});
|
|
}
|
|
|
|
Ok(Some(WsOutEvent::DmList { data: results }))
|
|
}
|
|
}
|