//! Reaction CRUD operations on `MessageRepo`. //! //! Each (message, user, content) tuple is unique via ON CONFLICT. //! Toggle semantics: same request adds/removes the reaction. use chrono::Utc; use uuid::Uuid; use crate::ImksResult; use crate::models::message_reaction::MessageReaction; use super::message_repo::MessageRepo; impl MessageRepo { /// Add or toggle a reaction. Returns the reaction if added, None if already exists. pub async fn add_reaction( &self, message_id: Uuid, channel_id: Uuid, user_id: Uuid, content: &str, ) -> ImksResult> { let id = Uuid::now_v7(); let now = Utc::now(); let row = sqlx::query_as::<_, MessageReaction>( r#" INSERT INTO message_reaction (id, message_id, channel_id, user_id, content, created_at) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (message_id, user_id, content) DO NOTHING RETURNING * "#, ) .bind(id) .bind(message_id) .bind(channel_id) .bind(user_id) .bind(content) .bind(now) .fetch_optional(self.pool()) .await?; Ok(row) } /// Remove a user's reaction from a message. pub async fn remove_reaction( &self, message_id: Uuid, user_id: Uuid, content: &str, ) -> ImksResult { let result = sqlx::query( r#" DELETE FROM message_reaction WHERE message_id = $1 AND user_id = $2 AND content = $3 "#, ) .bind(message_id) .bind(user_id) .bind(content) .execute(self.pool()) .await?; Ok(result.rows_affected() > 0) } /// Get all reactions on a message. pub async fn get_reactions(&self, message_id: Uuid) -> ImksResult> { sqlx::query_as::<_, MessageReaction>( "SELECT * FROM message_reaction WHERE message_id = $1 ORDER BY created_at", ) .bind(message_id) .fetch_all(self.pool()) .await .map_err(Into::into) } /// Get a specific user's reactions on a message. pub async fn get_user_reactions( &self, message_id: Uuid, user_id: Uuid, ) -> ImksResult> { sqlx::query_as::<_, MessageReaction>( "SELECT * FROM message_reaction WHERE message_id = $1 AND user_id = $2 ORDER BY created_at", ) .bind(message_id) .bind(user_id) .fetch_all(self.pool()) .await .map_err(Into::into) } }