Files
imks/repo/message_read_state.rs
T
zhenyi 821537186e refactor(tests): reformat code and update dependency management
- Reorganized import statements in adapter tests for better readability
- Replaced or_insert_with(Vec::new) with or_default() in test closures
- Updated Cargo.lock with new dependency versions and checksums
- Added TLS features to tonic dependency configuration
- Included sqlx, chrono, and uuid dependencies with specific features
- Added jsonwebtoken and arc-swap as project dependencies
- Reformatted assertion statements to comply with line length limits
- Adjusted base64 import order in engine codec module
- Updated protobuf include statement formatting
2026-06-11 12:11:05 +08:00

111 lines
3.4 KiB
Rust

//! Read state CRUD operations on `MessageRepo`.
//!
//! One row per (channel, user). Upserted on each read; ON CONFLICT DO UPDATE
//! advances the cursor and recalculates unread counts.
use chrono::Utc;
use uuid::Uuid;
use crate::ImksResult;
use crate::models::message_read_state::MessageReadState;
use super::message_repo::MessageRepo;
impl MessageRepo {
/// Mark a channel as read up to a given message for a user.
/// Recalculates unread_count and unread_mentions from the database.
pub async fn mark_read(
&self,
channel_id: Uuid,
user_id: Uuid,
last_read_message_id: Uuid,
) -> ImksResult<MessageReadState> {
let now = Utc::now();
let unread_count: i64 = sqlx::query_scalar(
r#"
SELECT COUNT(*)::BIGINT
FROM message
WHERE channel_id = $1
AND deleted_at IS NULL
AND id > $2
AND author_id != $3
"#,
)
.bind(channel_id)
.bind(last_read_message_id)
.bind(user_id)
.fetch_one(self.pool())
.await?;
let unread_mentions: i64 = sqlx::query_scalar(
r#"
SELECT COUNT(*)::BIGINT
FROM message_mention mm
WHERE mm.channel_id = $1
AND mm.mentioned_user_id = $2
AND mm.message_id > $3
AND mm.read_at IS NULL
"#,
)
.bind(channel_id)
.bind(user_id)
.bind(last_read_message_id)
.fetch_one(self.pool())
.await?;
let id = Uuid::now_v7();
sqlx::query_as::<_, MessageReadState>(
r#"
INSERT INTO message_read_state (
id, channel_id, user_id, last_read_message_id, last_read_at,
unread_count, unread_mentions, created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $8)
ON CONFLICT (channel_id, user_id) DO UPDATE SET
last_read_message_id = EXCLUDED.last_read_message_id,
last_read_at = EXCLUDED.last_read_at,
unread_count = EXCLUDED.unread_count,
unread_mentions = EXCLUDED.unread_mentions,
updated_at = EXCLUDED.updated_at
RETURNING *
"#,
)
.bind(id)
.bind(channel_id)
.bind(user_id)
.bind(last_read_message_id)
.bind(now)
.bind(unread_count)
.bind(unread_mentions)
.fetch_one(self.pool())
.await
.map_err(Into::into)
}
/// Get a user's read state for a channel.
pub async fn get_read_state(
&self,
channel_id: Uuid,
user_id: Uuid,
) -> ImksResult<Option<MessageReadState>> {
sqlx::query_as::<_, MessageReadState>(
"SELECT * FROM message_read_state WHERE channel_id = $1 AND user_id = $2",
)
.bind(channel_id)
.bind(user_id)
.fetch_optional(self.pool())
.await
.map_err(Into::into)
}
/// Get read state summaries for all channels a user participates in.
pub async fn get_user_read_states(&self, user_id: Uuid) -> ImksResult<Vec<MessageReadState>> {
sqlx::query_as::<_, MessageReadState>("SELECT * FROM message_read_state WHERE user_id = $1")
.bind(user_id)
.fetch_all(self.pool())
.await
.map_err(Into::into)
}
}