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

219 lines
6.3 KiB
Rust

//! Thread CRUD operations on `MessageRepo`.
use chrono::Utc;
use uuid::Uuid;
use crate::ImksResult;
use crate::models::message_thread::MessageThread;
use crate::models::message_thread_participant::MessageThreadParticipant;
use super::message_repo::MessageRepo;
impl MessageRepo {
/// Create a new thread anchored on a root message.
pub async fn create_thread(
&self,
root_message_id: Uuid,
channel_id: Uuid,
created_by: Uuid,
) -> ImksResult<MessageThread> {
let id = Uuid::now_v7();
let now = Utc::now();
sqlx::query_as::<_, MessageThread>(
r#"
INSERT INTO message_thread (
id, channel_id, root_message_id, created_by,
replies_count, participants_count, resolved,
created_at, updated_at
) VALUES ($1, $2, $3, $4, 0, 0, FALSE, $5, $5)
ON CONFLICT (root_message_id) DO NOTHING
RETURNING *
"#,
)
.bind(id)
.bind(channel_id)
.bind(root_message_id)
.bind(created_by)
.bind(now)
.fetch_optional(self.pool())
.await?
.ok_or_else(|| crate::ImksError::InvalidInput("Thread already exists".into()))
}
/// Get a thread by its ID.
pub async fn get_thread(&self, thread_id: Uuid) -> ImksResult<Option<MessageThread>> {
sqlx::query_as::<_, MessageThread>("SELECT * FROM message_thread WHERE id = $1")
.bind(thread_id)
.fetch_optional(self.pool())
.await
.map_err(Into::into)
}
/// Get a thread by its root message ID.
pub async fn get_thread_by_root(
&self,
root_message_id: Uuid,
) -> ImksResult<Option<MessageThread>> {
sqlx::query_as::<_, MessageThread>(
"SELECT * FROM message_thread WHERE root_message_id = $1",
)
.bind(root_message_id)
.fetch_optional(self.pool())
.await
.map_err(Into::into)
}
/// List threads in a channel.
pub async fn list_threads(&self, channel_id: Uuid) -> ImksResult<Vec<MessageThread>> {
sqlx::query_as::<_, MessageThread>(
r#"
SELECT * FROM message_thread
WHERE channel_id = $1
ORDER BY last_reply_at DESC NULLS LAST
"#,
)
.bind(channel_id)
.fetch_all(self.pool())
.await
.map_err(Into::into)
}
/// Increment thread reply counter and update last reply info.
pub async fn bump_thread(&self, thread_id: Uuid, message_id: Uuid) -> ImksResult<()> {
let now = Utc::now();
sqlx::query(
r#"
UPDATE message_thread
SET replies_count = replies_count + 1,
last_reply_message_id = $1,
last_reply_at = $2,
updated_at = $2
WHERE id = $3
"#,
)
.bind(message_id)
.bind(now)
.bind(thread_id)
.execute(self.pool())
.await?;
Ok(())
}
/// Resolve or unresolve a thread.
pub async fn resolve_thread(
&self,
thread_id: Uuid,
resolved_by: Uuid,
resolved: bool,
) -> ImksResult<()> {
if resolved {
sqlx::query(
r#"
UPDATE message_thread
SET resolved = TRUE, resolved_by = $1, resolved_at = $2, updated_at = $2
WHERE id = $3
"#,
)
.bind(resolved_by)
.bind(Utc::now())
.bind(thread_id)
.execute(self.pool())
.await?;
} else {
sqlx::query(
r#"
UPDATE message_thread
SET resolved = FALSE, resolved_by = NULL, resolved_at = NULL, updated_at = $1
WHERE id = $2
"#,
)
.bind(Utc::now())
.bind(thread_id)
.execute(self.pool())
.await?;
}
Ok(())
}
}
impl MessageRepo {
/// Add a participant to a thread (or update their join reason).
pub async fn add_thread_participant(
&self,
thread_id: Uuid,
user_id: Uuid,
joined_reason: &str,
) -> ImksResult<MessageThreadParticipant> {
let id = Uuid::now_v7();
let now = Utc::now();
let participant = sqlx::query_as::<_, MessageThreadParticipant>(
r#"
INSERT INTO message_thread_participant (id, thread_id, user_id, joined_reason, joined_at)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (thread_id, user_id) DO UPDATE SET joined_reason = EXCLUDED.joined_reason
RETURNING *
"#,
)
.bind(id)
.bind(thread_id)
.bind(user_id)
.bind(joined_reason)
.bind(now)
.fetch_one(self.pool())
.await?;
sqlx::query(
"UPDATE message_thread SET participants_count = (SELECT COUNT(*) FROM message_thread_participant WHERE thread_id = $1) WHERE id = $1",
)
.bind(thread_id)
.execute(self.pool())
.await?;
Ok(participant)
}
/// Remove a participant from a thread.
pub async fn remove_thread_participant(
&self,
thread_id: Uuid,
user_id: Uuid,
) -> ImksResult<bool> {
let result = sqlx::query(
"DELETE FROM message_thread_participant WHERE thread_id = $1 AND user_id = $2",
)
.bind(thread_id)
.bind(user_id)
.execute(self.pool())
.await?;
if result.rows_affected() > 0 {
sqlx::query(
"UPDATE message_thread SET participants_count = GREATEST(participants_count - 1, 0) WHERE id = $1",
)
.bind(thread_id)
.execute(self.pool())
.await?;
}
Ok(result.rows_affected() > 0)
}
/// List all participants in a thread.
pub async fn list_thread_participants(
&self,
thread_id: Uuid,
) -> ImksResult<Vec<MessageThreadParticipant>> {
sqlx::query_as::<_, MessageThreadParticipant>(
"SELECT * FROM message_thread_participant WHERE thread_id = $1 ORDER BY joined_at",
)
.bind(thread_id)
.fetch_all(self.pool())
.await
.map_err(Into::into)
}
}