use serde::{Deserialize, Serialize}; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UnreadCount { pub room_id: Uuid, pub user_id: Uuid, pub count: i64, pub last_read_seq: i64, pub updated_at: chrono::DateTime, } #[allow(dead_code)] #[derive(Clone)] pub struct UnreadManager { db: db::database::AppDatabase, cache: db::cache::AppCache, } impl UnreadManager { pub fn new(db: db::database::AppDatabase, cache: db::cache::AppCache) -> Self { Self { db, cache } } pub async fn mark_read( &self, user_id: Uuid, room_id: Uuid, seq: i64, ) -> Result<(), crate::error::AppTransportError> { use models::rooms::room_user_state; use sea_orm::*; let state = room_user_state::Entity::find_by_id((room_id, user_id)) .one(&self.db) .await .map_err(|_| crate::error::AppTransportError::Internal)?; if let Some(existing) = state { let mut active: room_user_state::ActiveModel = existing.into(); active.last_read_seq = Set(Some(seq)); active .update(&self.db) .await .map_err(|_| crate::error::AppTransportError::Internal)?; } else { // Lazy create room_user_state::ActiveModel { room: Set(room_id), user: Set(user_id), last_read_seq: Set(Some(seq)), do_not_disturb: Set(false), dnd_start_hour: Set(None), dnd_end_hour: Set(None), joined_at: Set(Some(chrono::Utc::now())), } .insert(&self.db) .await .map_err(|_| crate::error::AppTransportError::Internal)?; } self.invalidate_cache(user_id, room_id).await?; Ok(()) } pub async fn get_unread_count( &self, user_id: Uuid, room_id: Uuid, ) -> Result { use models::rooms::{room_user_state, room_message}; use sea_orm::*; let state = room_user_state::Entity::find_by_id((room_id, user_id)) .one(&self.db) .await .map_err(|_| crate::error::AppTransportError::Internal)?; let last_read_seq = state.and_then(|s| s.last_read_seq).unwrap_or(0); let count = room_message::Entity::find() .filter(room_message::Column::Room.eq(room_id)) .filter(room_message::Column::Seq.gt(last_read_seq)) .count(&self.db) .await .map_err(|_| crate::error::AppTransportError::Internal)?; Ok(count as i64) } pub async fn get_all_unread_counts( &self, user_id: Uuid, ) -> Result, crate::error::AppTransportError> { use models::rooms::{room_user_state, room_message}; use sea_orm::*; let states = room_user_state::Entity::find() .filter(room_user_state::Column::User.eq(user_id)) .all(&self.db) .await .map_err(|_| crate::error::AppTransportError::Internal)?; if states.is_empty() { return Ok(Vec::new()); } let now = chrono::Utc::now(); let mut result = Vec::new(); for state in states { let last_read_seq = state.last_read_seq.unwrap_or(0); let count = room_message::Entity::find() .filter(room_message::Column::Room.eq(state.room)) .filter(room_message::Column::Seq.gt(last_read_seq)) .count(&self.db) .await .map_err(|_| crate::error::AppTransportError::Internal)?; if count > 0 { result.push(UnreadCount { room_id: state.room, user_id, count: count as i64, last_read_seq, updated_at: now, }); } } Ok(result) } async fn invalidate_cache( &self, _user_id: Uuid, _room_id: Uuid, ) -> Result<(), crate::error::AppTransportError> { Ok(()) } }