821537186e
- 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
219 lines
6.3 KiB
Rust
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)
|
|
}
|
|
}
|