- Add gitignore and prettier configuration files for project scaffolding - Implement room access control service with project member verification - Create user access key management with CRUD operations and activity logging - Add accordion UI component for frontend expandable sections - Implement room AI configuration with list, upsert, and delete operations - Add AI event types for agent join/leave/status change tracking - Create streaming AI processing services for mode and react patterns - Build room AI service with model detection and idempotency handling - Integrate chat service orchestration for AI message processing - Add typing indicators and stream cancellation for AI interactions - Implement mention parsing and context extraction for AI agents
138 lines
4.1 KiB
Rust
138 lines
4.1 KiB
Rust
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<chrono::Utc>,
|
|
}
|
|
|
|
#[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<i64, crate::error::AppTransportError> {
|
|
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<Vec<UnreadCount>, 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)?;
|
|
|
|
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: chrono::Utc::now(),
|
|
});
|
|
}
|
|
}
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
async fn invalidate_cache(
|
|
&self,
|
|
_user_id: Uuid,
|
|
_room_id: Uuid,
|
|
) -> Result<(), crate::error::AppTransportError> {
|
|
Ok(())
|
|
}
|
|
} |