refactor: update channel HTTP handlers (ban, message, room, thread)

This commit is contained in:
zhenyi 2026-05-30 15:07:12 +08:00
parent 25b73e1054
commit 2b7777adbc
5 changed files with 161 additions and 33 deletions

View File

@ -53,6 +53,7 @@ impl WsHandler {
workspace: Uuid, workspace: Uuid,
user: Uuid, user: Uuid,
) -> ChannelResult<Option<WsOutEvent>> { ) -> ChannelResult<Option<WsOutEvent>> {
Self::ensure_workspace_member(bus, _user_id, workspace).await?;
db::sqlx::query( db::sqlx::query(
"DELETE FROM user_blacklist WHERE \"user\" = $1 AND black = $2", "DELETE FROM user_blacklist WHERE \"user\" = $1 AND black = $2",
) )

View File

@ -184,15 +184,27 @@ impl WsHandler {
// ── End auto-thread logic ────────────────────────────────────── // ── End auto-thread logic ──────────────────────────────────────
if let Some(thread_id) = effective_thread { if let Some(thread_id) = effective_thread {
let exists: Option<(Uuid,)> = db::sqlx::query_as( let thread_row: Option<(bool, bool)> = db::sqlx::query_as(
"SELECT id FROM room_thread WHERE id = $1 AND room = $2", "SELECT locked, archived FROM room_thread WHERE id = $1 AND room = $2",
) )
.bind(thread_id) .bind(thread_id)
.bind(room) .bind(room)
.fetch_optional(bus.inner.db.reader()) .fetch_optional(bus.inner.db.reader())
.await?; .await?;
if exists.is_none() { match thread_row {
return Err(ChannelError::RoomNotFound); None => return Err(ChannelError::RoomNotFound),
Some((locked, archived)) => {
if locked {
return Err(ChannelError::Validation(
"thread is resolved".to_string(),
));
}
if archived {
return Err(ChannelError::Validation(
"thread is archived".to_string(),
));
}
}
} }
} }
@ -458,30 +470,67 @@ impl WsHandler {
room: Uuid, room: Uuid,
seq: i64, seq: i64,
limit: Option<u64>, limit: Option<u64>,
thread: Option<Uuid>,
) -> ChannelResult<Option<WsOutEvent>> { ) -> ChannelResult<Option<WsOutEvent>> {
Self::ensure_room_access(bus, user_id, room).await?; Self::ensure_room_access(bus, user_id, room).await?;
let size = limit let size = limit
.unwrap_or(MAX_MESSAGES_PER_REQUEST) .unwrap_or(MAX_MESSAGES_PER_REQUEST)
.min(MAX_MESSAGES_PER_REQUEST) as i64; .min(MAX_MESSAGES_PER_REQUEST) as i64;
let rows = db::sqlx::query_as::<_, model::room::RoomMessageModel>(
"(SELECT id, room, seq, thread, parent, author, content, content_type, pinned, \ let rows = if let Some(tid) = thread {
system_type, metadata, edited_at, created_at, updated_at, deleted_at \ // Pre-fetch starter_message to avoid per-row subquery
FROM room_message \ let starter: Option<(Uuid,)> = db::sqlx::query_as(
WHERE room = $1 AND seq < $2 AND deleted_at IS NULL \ "SELECT starter_message FROM room_thread WHERE id = $1",
ORDER BY seq DESC LIMIT $3) \ )
UNION ALL \ .bind(tid)
(SELECT id, room, seq, thread, parent, author, content, content_type, pinned, \ .fetch_optional(bus.inner.db.reader())
system_type, metadata, edited_at, created_at, updated_at, deleted_at \ .await?;
FROM room_message \ let starter_id = starter.map(|r| r.0);
WHERE room = $1 AND seq >= $2 AND deleted_at IS NULL \
ORDER BY seq ASC LIMIT $3) \ db::sqlx::query_as::<_, model::room::RoomMessageModel>(
ORDER BY seq ASC", "(SELECT id, room, seq, thread, parent, author, content, content_type, pinned, \
) system_type, metadata, edited_at, created_at, updated_at, deleted_at \
.bind(room) FROM room_message \
.bind(seq) WHERE room = $1 AND seq < $2 AND deleted_at IS NULL \
.bind(size) AND (thread = $4 OR ($5 IS NOT NULL AND id = $5)) \
.fetch_all(bus.inner.db.reader()) ORDER BY seq DESC LIMIT $3) \
.await?; UNION ALL \
(SELECT id, room, seq, thread, parent, author, content, content_type, pinned, \
system_type, metadata, edited_at, created_at, updated_at, deleted_at \
FROM room_message \
WHERE room = $1 AND seq >= $2 AND deleted_at IS NULL \
AND (thread = $4 OR ($5 IS NOT NULL AND id = $5)) \
ORDER BY seq ASC LIMIT $3) \
ORDER BY seq ASC",
)
.bind(room)
.bind(seq)
.bind(size)
.bind(tid)
.bind(starter_id)
.fetch_all(bus.inner.db.reader())
.await?
} else {
db::sqlx::query_as::<_, model::room::RoomMessageModel>(
"(SELECT id, room, seq, thread, parent, author, content, content_type, pinned, \
system_type, metadata, edited_at, created_at, updated_at, deleted_at \
FROM room_message \
WHERE room = $1 AND seq < $2 AND deleted_at IS NULL AND thread IS NULL \
ORDER BY seq DESC LIMIT $3) \
UNION ALL \
(SELECT id, room, seq, thread, parent, author, content, content_type, pinned, \
system_type, metadata, edited_at, created_at, updated_at, deleted_at \
FROM room_message \
WHERE room = $1 AND seq >= $2 AND deleted_at IS NULL AND thread IS NULL \
ORDER BY seq ASC LIMIT $3) \
ORDER BY seq ASC",
)
.bind(room)
.bind(seq)
.bind(size)
.fetch_all(bus.inner.db.reader())
.await?
};
let author_ids: Vec<Uuid> = rows.iter().map(|r| r.author).collect(); let author_ids: Vec<Uuid> = rows.iter().map(|r| r.author).collect();
let user_map = bus.lookup_users(&author_ids).await.unwrap_or_default(); let user_map = bus.lookup_users(&author_ids).await.unwrap_or_default();
let message_ids: Vec<Uuid> = rows.iter().map(|r| r.id).collect(); let message_ids: Vec<Uuid> = rows.iter().map(|r| r.id).collect();
@ -563,13 +612,13 @@ impl WsHandler {
room: missed_room.clone(), room: missed_room.clone(),
sender_type: "user".to_string(), sender_type: "user".to_string(),
sender, sender,
thread: None, thread: m.thread,
in_reply_to: None, in_reply_to: m.parent,
content: m.content, content: m.content,
content_type: "text".to_string(), content_type: m.content_type,
pinned: false, pinned: m.pinned,
system_type: None, system_type: m.system_type,
metadata: serde_json::Value::Null, metadata: m.metadata,
thinking_content: None, thinking_content: None,
thinking_is_chunked: None, thinking_is_chunked: None,
send_at: m.send_at, send_at: m.send_at,

View File

@ -73,8 +73,8 @@ impl WsHandler {
) )
.await .await
} }
WsInMessage::MessageAround { room, seq, limit } => { WsInMessage::MessageAround { room, seq, limit, thread } => {
Self::message_around(bus, user_id, room, seq, limit).await Self::message_around(bus, user_id, room, seq, limit, thread).await
} }
WsInMessage::MessageCreate { WsInMessage::MessageCreate {
room, room,
@ -180,6 +180,9 @@ impl WsHandler {
WsInMessage::ThreadCreate { room, parent } => { WsInMessage::ThreadCreate { room, parent } => {
Self::thread_create(bus, user_id, room, parent).await Self::thread_create(bus, user_id, room, parent).await
} }
WsInMessage::ThreadList { room } => {
Self::thread_list(bus, user_id, room).await
}
WsInMessage::ThreadResolve { thread_id } => { WsInMessage::ThreadResolve { thread_id } => {
Self::thread_resolve(bus, user_id, thread_id).await Self::thread_resolve(bus, user_id, thread_id).await
} }

View File

@ -202,10 +202,11 @@ impl WsHandler {
pub(super) async fn access_grant( pub(super) async fn access_grant(
bus: &ChannelBus, bus: &ChannelBus,
_user_id: Uuid, user_id: Uuid,
room: Uuid, room: Uuid,
target_user: Uuid, target_user: Uuid,
) -> ChannelResult<Option<WsOutEvent>> { ) -> ChannelResult<Option<WsOutEvent>> {
Self::ensure_room_access(bus, user_id, room).await?;
db::sqlx::query( db::sqlx::query(
"INSERT INTO room_permission_overwrite \ "INSERT INTO room_permission_overwrite \
(room, target_type, target_id, allow_permissions, deny_permissions, created_at, updated_at) \ (room, target_type, target_id, allow_permissions, deny_permissions, created_at, updated_at) \
@ -235,6 +236,7 @@ impl WsHandler {
room: Uuid, room: Uuid,
target_user: Uuid, target_user: Uuid,
) -> ChannelResult<Option<WsOutEvent>> { ) -> ChannelResult<Option<WsOutEvent>> {
Self::ensure_room_access(bus, user_id, room).await?;
db::sqlx::query( db::sqlx::query(
"DELETE FROM room_permission_overwrite \ "DELETE FROM room_permission_overwrite \
WHERE room = $1 AND target_type = 'user' AND target_id = $2", WHERE room = $1 AND target_type = 'user' AND target_id = $2",

View File

@ -1,4 +1,4 @@
use chrono::Utc; use chrono::{DateTime, Utc};
use uuid::Uuid; use uuid::Uuid;
use crate::event::{RoomInfo, UserInfo, thread}; use crate::event::{RoomInfo, UserInfo, thread};
@ -7,7 +7,80 @@ use crate::{ChannelBus, ChannelError, ChannelResult};
use super::WsOutEvent; use super::WsOutEvent;
use super::WsHandler; use super::WsHandler;
/// Helper struct for thread_list JOIN query result
#[derive(db::sqlx::FromRow)]
struct ThreadListRow {
id: Uuid,
room: Uuid,
seq: i64,
starter_message: Option<Uuid>,
title: String,
created_by: Uuid,
archived: bool,
locked: bool,
last_message_at: Option<DateTime<Utc>>,
created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
archived_at: Option<DateTime<Utc>>,
parent_seq: i64,
}
impl WsHandler { impl WsHandler {
pub(super) async fn thread_list(
bus: &ChannelBus,
user_id: Uuid,
room: Uuid,
) -> ChannelResult<Option<WsOutEvent>> {
Self::ensure_room_access(bus, user_id, room).await?;
// Join room_thread with room_message to get the parent message's seq
let rows = db::sqlx::query_as::<_, ThreadListRow>(
"SELECT rt.id, rt.room, rt.seq, rt.starter_message, rt.title, rt.created_by, \
rt.archived, rt.locked, rt.last_message_at, rt.created_at, rt.updated_at, rt.archived_at, \
COALESCE(rm.seq, 0) as parent_seq \
FROM room_thread rt \
LEFT JOIN room_message rm ON rm.id = rt.starter_message \
WHERE rt.room = $1 ORDER BY rt.last_message_at DESC NULLS LAST",
)
.bind(room)
.fetch_all(bus.inner.db.reader())
.await?;
let mut items = Vec::new();
for row in rows {
let tc_room = bus.lookup_room(row.room).await
.unwrap_or_else(|_| RoomInfo::unknown(row.room));
let created_by = bus.lookup_user(row.created_by).await
.unwrap_or_else(|_| UserInfo::unknown(row.created_by));
// Get last message preview
let preview: Option<(String,)> = db::sqlx::query_as(
"SELECT content FROM room_message \
WHERE thread = $1 AND deleted_at IS NULL \
ORDER BY seq DESC LIMIT 1",
)
.bind(row.id)
.fetch_optional(bus.inner.db.reader())
.await?;
items.push(thread::ThreadListItem {
id: row.id,
room: tc_room,
seq: row.seq,
parent_seq: row.parent_seq,
title: row.title,
created_by,
archived: row.archived,
locked: row.locked,
last_message_at: row.last_message_at,
last_message_preview: preview.map(|p| p.0),
created_at: row.created_at,
});
}
Ok(Some(WsOutEvent::ThreadList {
data: thread::ThreadListService { threads: items },
}))
}
pub(super) async fn thread_create( pub(super) async fn thread_create(
bus: &ChannelBus, bus: &ChannelBus,
user_id: Uuid, user_id: Uuid,