use crate::error::RoomError; use crate::service::RoomService; use crate::ws_context::WsUserContext; use chrono::Utc; use models::rooms::NotificationType; use models::rooms::room_message_edit_history; use models::users::user as user_model; use sea_orm::*; use uuid::Uuid; #[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)] pub struct MessageEditHistoryEntry { pub old_content: String, pub new_content: String, pub edited_at: chrono::DateTime, } #[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)] pub struct MessageEditHistoryResponse { pub message_id: Uuid, pub history: Vec, pub total_edits: i64, } #[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)] pub struct MentionNotificationResponse { pub message_id: Uuid, pub mentioned_by: Uuid, pub mentioned_by_name: String, pub content_preview: String, pub room_id: Uuid, pub room_name: String, pub created_at: chrono::DateTime, } #[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)] pub struct DraftResponse { pub room_id: Uuid, pub content: String, pub saved_at: chrono::DateTime, } #[derive(Debug, Clone, serde::Deserialize)] pub struct DraftSaveRequest { pub content: String, } impl RoomService { pub async fn save_message_edit_history( &self, message_id: Uuid, user_id: Uuid, old_content: String, new_content: String, ) -> Result<(), RoomError> { let history = room_message_edit_history::ActiveModel { id: Set(Uuid::now_v7()), message: Set(message_id), user: Set(user_id), old_content: Set(old_content), new_content: Set(new_content), edited_at: Set(Utc::now()), }; history.insert(&self.db).await?; Ok(()) } pub async fn get_message_edit_history( &self, message_id: Uuid, ctx: &WsUserContext, ) -> Result { let user_id = ctx.user_id; let message = models::rooms::room_message::Entity::find_by_id(message_id) .one(&self.db) .await? .ok_or_else(|| RoomError::NotFound("Message not found".to_string()))?; self.require_room_member(message.room, user_id).await?; let history = room_message_edit_history::Entity::find() .filter(room_message_edit_history::Column::Message.eq(message_id)) .order_by_asc(room_message_edit_history::Column::EditedAt) .all(&self.db) .await?; let total_edits = history.len() as i64; let history_entries = history .into_iter() .map(|h| MessageEditHistoryEntry { old_content: h.old_content, new_content: h.new_content, edited_at: h.edited_at, }) .collect(); Ok(MessageEditHistoryResponse { message_id, history: history_entries, total_edits, }) } pub async fn get_mention_notifications( &self, limit: Option, ctx: &WsUserContext, ) -> Result, RoomError> { let user_id = ctx.user_id; let limit = limit.unwrap_or(50); let notifications = models::rooms::room_notifications::Entity::find() .filter(models::rooms::room_notifications::Column::UserId.eq(user_id)) .filter( models::rooms::room_notifications::Column::NotificationType .eq(NotificationType::Mention), ) .order_by_desc(models::rooms::room_notifications::Column::CreatedAt) .limit(limit) .all(&self.db) .await?; // Batch fetch related users to avoid N+1 queries let related_user_ids: Vec = notifications .iter() .filter_map(|n| n.related_user_id) .collect(); let users: std::collections::HashMap = if !related_user_ids.is_empty() { user_model::Entity::find() .filter(user_model::Column::Uid.is_in(related_user_ids)) .all(&self.db) .await? .into_iter() .map(|u| (u.uid, u.display_name.unwrap_or(u.username))) .collect() } else { std::collections::HashMap::new() }; // Batch fetch room names to avoid N+1 queries let room_ids: Vec = notifications .iter() .filter_map(|n| n.room) .collect(); let rooms: std::collections::HashMap = if !room_ids.is_empty() { models::rooms::room::Entity::find() .filter(models::rooms::room::Column::Id.is_in(room_ids)) .all(&self.db) .await? .into_iter() .map(|r| (r.id, r.room_name)) .collect() } else { std::collections::HashMap::new() }; let result = notifications .into_iter() .map(|notification| { let mentioned_by_name = notification .related_user_id .and_then(|uid| users.get(&uid)) .cloned() .unwrap_or_else(|| "Unknown User".to_string()); let room_name = notification .room .and_then(|rid| rooms.get(&rid)) .cloned() .unwrap_or_else(|| "Unknown Room".to_string()); let content_preview = notification .content .unwrap_or_default() .chars() .take(100) .collect(); MentionNotificationResponse { message_id: notification.related_message_id.unwrap_or_default(), mentioned_by: notification.related_user_id.unwrap_or_default(), mentioned_by_name, content_preview, room_id: notification.room.unwrap_or_default(), room_name, created_at: notification.created_at, } }) .collect(); Ok(result) } pub async fn mark_mention_notifications_read( &self, ctx: &WsUserContext, ) -> Result<(), RoomError> { let user_id = ctx.user_id; use sea_orm::sea_query::Expr; let now = Utc::now(); models::rooms::room_notifications::Entity::update_many() .col_expr( models::rooms::room_notifications::Column::IsRead, Expr::value(true), ) .col_expr( models::rooms::room_notifications::Column::ReadAt, Expr::value(Some(now)), ) .filter(models::rooms::room_notifications::Column::UserId.eq(user_id)) .filter( models::rooms::room_notifications::Column::NotificationType .eq(NotificationType::Mention), ) .filter(models::rooms::room_notifications::Column::IsRead.eq(false)) .exec(&self.db) .await?; Ok(()) } }