Files
imks/repo/message_query.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

170 lines
5.2 KiB
Rust

//! Message read operations — get single, list by channel, list by thread.
//!
//! All list queries use UUID v7 cursor-based pagination (no OFFSET).
use sqlx::Row;
use uuid::Uuid;
use crate::ImksResult;
use crate::models::message::Message;
use super::message_repo::MessageRepo;
use super::pagination::{CursorPage, clamp_limit};
impl MessageRepo {
/// Fetch a single message by ID.
///
/// Returns `None` if the message doesn't exist or has been soft-deleted.
pub async fn get(&self, message_id: Uuid) -> ImksResult<Option<Message>> {
let row = sqlx::query_as::<_, Message>(
"SELECT * FROM message WHERE id = $1 AND deleted_at IS NULL",
)
.bind(message_id)
.fetch_optional(self.pool())
.await?;
Ok(row)
}
/// List messages in a channel with cursor-based pagination.
///
/// Returns messages in reverse chronological order (newest first).
/// Pass `before` as the last message ID from the previous page to
/// fetch the next page.
pub async fn list_by_channel(
&self,
channel_id: Uuid,
before: Option<Uuid>,
limit: Option<i64>,
) -> ImksResult<CursorPage<Message>> {
let effective_limit = clamp_limit(limit);
// Fetch one extra row to determine `has_more`.
let fetch_limit = effective_limit + 1;
let rows = match before {
Some(cursor) => {
sqlx::query_as::<_, Message>(
r#"
SELECT * FROM message
WHERE channel_id = $1
AND deleted_at IS NULL
AND id < $2
ORDER BY id DESC
LIMIT $3
"#,
)
.bind(channel_id)
.bind(cursor)
.bind(fetch_limit)
.fetch_all(self.pool())
.await?
}
None => {
sqlx::query_as::<_, Message>(
r#"
SELECT * FROM message
WHERE channel_id = $1
AND deleted_at IS NULL
ORDER BY id DESC
LIMIT $2
"#,
)
.bind(channel_id)
.bind(fetch_limit)
.fetch_all(self.pool())
.await?
}
};
Ok(CursorPage::from_raw(rows, effective_limit, |m| m.id))
}
/// List messages in a thread with cursor-based pagination.
pub async fn list_by_thread(
&self,
thread_id: Uuid,
before: Option<Uuid>,
limit: Option<i64>,
) -> ImksResult<CursorPage<Message>> {
let effective_limit = clamp_limit(limit);
let fetch_limit = effective_limit + 1;
let rows = match before {
Some(cursor) => {
sqlx::query_as::<_, Message>(
r#"
SELECT * FROM message
WHERE thread_id = $1
AND deleted_at IS NULL
AND id < $2
ORDER BY id DESC
LIMIT $3
"#,
)
.bind(thread_id)
.bind(cursor)
.bind(fetch_limit)
.fetch_all(self.pool())
.await?
}
None => {
sqlx::query_as::<_, Message>(
r#"
SELECT * FROM message
WHERE thread_id = $1
AND deleted_at IS NULL
ORDER BY id DESC
LIMIT $2
"#,
)
.bind(thread_id)
.bind(fetch_limit)
.fetch_all(self.pool())
.await?
}
};
Ok(CursorPage::from_raw(rows, effective_limit, |m| m.id))
}
/// Get message reaction counts grouped by emoji content.
///
/// Returns `(content, count)` pairs for the given message.
pub async fn get_reaction_counts(&self, message_id: Uuid) -> ImksResult<Vec<(String, i64)>> {
let rows = sqlx::query(
r#"
SELECT content, COUNT(*)::BIGINT AS cnt
FROM message_reaction
WHERE message_id = $1
GROUP BY content
"#,
)
.bind(message_id)
.fetch_all(self.pool())
.await?;
Ok(rows
.into_iter()
.map(|r| (r.get("content"), r.get("cnt")))
.collect())
}
/// Count attachments and embeds for a message.
pub async fn get_content_counts(&self, message_id: Uuid) -> ImksResult<(i64, i64)> {
let att_row = sqlx::query(
"SELECT COUNT(*)::BIGINT AS cnt FROM message_attachment WHERE message_id = $1",
)
.bind(message_id)
.fetch_one(self.pool())
.await?;
let emb_row =
sqlx::query("SELECT COUNT(*)::BIGINT AS cnt FROM message_embed WHERE message_id = $1")
.bind(message_id)
.fetch_one(self.pool())
.await?;
Ok((att_row.get("cnt"), emb_row.get("cnt")))
}
}