use chrono::Utc; use uuid::Uuid; use crate::event::{RoomInfo, UserInfo, star}; use crate::{ChannelBus, ChannelResult}; use super::WsOutEvent; use super::WsHandler; impl WsHandler { pub(super) async fn message_star( bus: &ChannelBus, user_id: Uuid, room: Uuid, message: Uuid, do_star: bool, ) -> ChannelResult> { Self::ensure_room_access(bus, user_id, room).await?; Self::ensure_message_in_room(bus, room, message).await?; let room_info = bus.lookup_room(room).await.unwrap_or_else(|_| RoomInfo::unknown(room)); let user_info = bus.lookup_user(user_id).await.unwrap_or_else(|_| UserInfo::unknown(user_id)); if do_star { let result = db::sqlx::query( "INSERT INTO message_star (message, room, \"user\", created_at) \ VALUES ($1, $2, $3, now()) ON CONFLICT (message, \"user\") DO NOTHING", ) .bind(message) .bind(room) .bind(user_id) .execute(bus.inner.db.writer()) .await?; if result.rows_affected() == 0 { return Ok(None); } let seq_row: Option<(i64,)> = db::sqlx::query_as( "SELECT seq FROM room_message WHERE id = $1", ) .bind(message) .fetch_optional(bus.inner.db.reader()) .await?; let data = star::MessageStarredService { room: room_info.clone(), message_id: message, message_seq: seq_row.map(|r| r.0).unwrap_or(0), starred_by: user_info, starred_at: Utc::now(), }; bus.emit_to_user(user_id, "message.starred", &data).await?; Ok(Some(WsOutEvent::MessageStarred { room: room_info, data, })) } else { let result = db::sqlx::query( "DELETE FROM message_star WHERE message = $1 AND \"user\" = $2", ) .bind(message) .bind(user_id) .execute(bus.inner.db.writer()) .await?; if result.rows_affected() == 0 { return Ok(None); } let data = star::MessageUnstarredService { room: room_info.clone(), message_id: message, unstarred_by: user_info, unstarred_at: Utc::now(), }; bus.emit_to_user(user_id, "message.unstarred", &data).await?; Ok(Some(WsOutEvent::MessageUnstarred { room: room_info, data, })) } } pub(super) async fn starred_list( bus: &ChannelBus, user_id: Uuid, room: Option, limit: Option, ) -> ChannelResult> { let limit = limit.unwrap_or(50).min(100) as i64; let rows = if let Some(room_id) = room { Self::ensure_room_access(bus, user_id, room_id).await?; db::sqlx::query_as::<_, (Uuid, Uuid, i64, String, String, Uuid, chrono::DateTime, chrono::DateTime)>( "SELECT ms.id, rm.id, rm.seq, rm.content, rm.content_type, rm.author, ms.created_at, rm.created_at \ FROM message_star ms \ JOIN room_message rm ON rm.id = ms.message \ WHERE ms.\"user\" = $1 AND ms.room = $2 AND rm.deleted_at IS NULL \ ORDER BY ms.created_at DESC LIMIT $3", ) .bind(user_id) .bind(room_id) .bind(limit) .fetch_all(bus.inner.db.reader()) .await? } else { db::sqlx::query_as::<_, (Uuid, Uuid, i64, String, String, Uuid, chrono::DateTime, chrono::DateTime)>( "SELECT ms.id, rm.id, rm.seq, rm.content, rm.content_type, rm.author, ms.created_at, rm.created_at \ FROM message_star ms \ JOIN room_message rm ON rm.id = ms.message \ WHERE ms.\"user\" = $1 AND rm.deleted_at IS NULL \ ORDER BY ms.created_at DESC LIMIT $2", ) .bind(user_id) .bind(limit) .fetch_all(bus.inner.db.reader()) .await? }; let author_ids: Vec = rows.iter().map(|r| r.5).collect(); let user_map = bus.lookup_users(&author_ids).await.unwrap_or_default(); let mut entries = Vec::with_capacity(rows.len()); for (_star_id, msg_id, seq, content, content_type, author_id, starred_at, sent_at) in rows { let msg_room_row: Option<(Uuid,)> = db::sqlx::query_as( "SELECT room FROM room_message WHERE id = $1", ) .bind(msg_id) .fetch_optional(bus.inner.db.reader()) .await?; let msg_room_id = msg_room_row.map(|r| r.0).unwrap_or(Uuid::nil()); let room_info = bus .lookup_room(msg_room_id) .await .unwrap_or_else(|_| RoomInfo::unknown(msg_room_id)); let sender = user_map .get(&author_id) .cloned() .unwrap_or_else(|| UserInfo::unknown(author_id)); entries.push(star::StarredMessageEntry { message_id: msg_id, room: room_info, seq, content, content_type, sender, starred_at, sent_at, }); } Ok(Some(WsOutEvent::StarredList { data: entries })) } }