229 lines
6.3 KiB
Rust
229 lines
6.3 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use sqlx::types::chrono::{DateTime, Utc};
|
|
use sqlx::{Postgres, Pool};
|
|
use std::collections::HashMap;
|
|
use tracing::{debug, info, instrument};
|
|
|
|
/// Émoji de réaction supporté
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
pub enum ReactionEmoji {
|
|
Like,
|
|
Love,
|
|
Haha,
|
|
Wow,
|
|
Sad,
|
|
Angry,
|
|
}
|
|
|
|
impl ReactionEmoji {
|
|
pub fn as_str(&self) -> &'static str {
|
|
match self {
|
|
ReactionEmoji::Like => "👍",
|
|
ReactionEmoji::Love => "❤️",
|
|
ReactionEmoji::Haha => "😂",
|
|
ReactionEmoji::Wow => "😮",
|
|
ReactionEmoji::Sad => "😢",
|
|
ReactionEmoji::Angry => "😠",
|
|
}
|
|
}
|
|
|
|
pub fn from_str(emoji: &str) -> Option<Self> {
|
|
match emoji {
|
|
"👍" => Some(ReactionEmoji::Like),
|
|
"❤️" => Some(ReactionEmoji::Love),
|
|
"😂" => Some(ReactionEmoji::Haha),
|
|
"😮" => Some(ReactionEmoji::Wow),
|
|
"😢" => Some(ReactionEmoji::Sad),
|
|
"😠" => Some(ReactionEmoji::Angry),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Représente une réaction sur un message
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MessageReaction {
|
|
pub message_id: i64,
|
|
pub user_id: i64,
|
|
pub emoji: ReactionEmoji,
|
|
pub created_at: DateTime<Utc>,
|
|
}
|
|
|
|
/// Manager pour gérer les réactions sur les messages
|
|
pub struct ReactionsManager {
|
|
pool: Pool<Postgres>,
|
|
}
|
|
|
|
impl ReactionsManager {
|
|
pub fn new(pool: Pool<Postgres>) -> Self {
|
|
Self { pool }
|
|
}
|
|
|
|
/// Ajouter une réaction à un message
|
|
#[instrument(skip(self))]
|
|
pub async fn add_reaction(
|
|
&self,
|
|
message_id: i64,
|
|
user_id: i64,
|
|
emoji: ReactionEmoji,
|
|
) -> Result<(), sqlx::Error> {
|
|
// Vérifier si l'utilisateur a déjà réagi à ce message
|
|
let existing: Option<(i64,)> = sqlx::query_as(
|
|
"SELECT id FROM message_reactions
|
|
WHERE message_id = $1 AND user_id = $2"
|
|
)
|
|
.bind(message_id)
|
|
.bind(user_id)
|
|
.fetch_optional(&self.pool)
|
|
.await?;
|
|
|
|
if let Some(_) = existing {
|
|
// L'utilisateur a déjà réagi, supprimer la réaction existante
|
|
sqlx::query(
|
|
"DELETE FROM message_reactions WHERE message_id = $1 AND user_id = $2"
|
|
)
|
|
.bind(message_id)
|
|
.bind(user_id)
|
|
.execute(&self.pool)
|
|
.await?;
|
|
|
|
debug!(
|
|
message_id = message_id,
|
|
user_id = user_id,
|
|
"Existing reaction removed"
|
|
);
|
|
}
|
|
|
|
// Ajouter la nouvelle réaction
|
|
sqlx::query(
|
|
"INSERT INTO message_reactions (message_id, user_id, emoji, created_at)
|
|
VALUES ($1, $2, $3, NOW())"
|
|
)
|
|
.bind(message_id)
|
|
.bind(user_id)
|
|
.bind(emoji.as_str())
|
|
.execute(&self.pool)
|
|
.await?;
|
|
|
|
info!(
|
|
message_id = message_id,
|
|
user_id = user_id,
|
|
emoji = %emoji.as_str(),
|
|
"Reaction added to message"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Retirer une réaction d'un message
|
|
#[instrument(skip(self))]
|
|
pub async fn remove_reaction(
|
|
&self,
|
|
message_id: i64,
|
|
user_id: i64,
|
|
) -> Result<(), sqlx::Error> {
|
|
sqlx::query(
|
|
"DELETE FROM message_reactions WHERE message_id = $1 AND user_id = $2"
|
|
)
|
|
.bind(message_id)
|
|
.bind(user_id)
|
|
.execute(&self.pool)
|
|
.await?;
|
|
|
|
info!(
|
|
message_id = message_id,
|
|
user_id = user_id,
|
|
"Reaction removed from message"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Obtenir toutes les réactions d'un message
|
|
#[instrument(skip(self))]
|
|
pub async fn get_message_reactions(
|
|
&self,
|
|
message_id: i64,
|
|
) -> Result<HashMap<ReactionEmoji, Vec<i64>>, sqlx::Error> {
|
|
let reactions: Vec<(String, i64)> = sqlx::query_as(
|
|
"SELECT emoji, user_id FROM message_reactions WHERE message_id = $1"
|
|
)
|
|
.bind(message_id)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
let mut result = HashMap::new();
|
|
|
|
for (emoji_str, user_id) in reactions {
|
|
if let Some(emoji) = ReactionEmoji::from_str(&emoji_str) {
|
|
result.entry(emoji).or_insert_with(Vec::new).push(user_id);
|
|
}
|
|
}
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
/// Obtenir le nombre de réactions par émoji pour un message
|
|
#[instrument(skip(self))]
|
|
pub async fn get_reaction_counts(
|
|
&self,
|
|
message_id: i64,
|
|
) -> Result<HashMap<ReactionEmoji, usize>, sqlx::Error> {
|
|
let reactions = self.get_message_reactions(message_id).await?;
|
|
|
|
let counts: HashMap<ReactionEmoji, usize> = reactions
|
|
.iter()
|
|
.map(|(emoji, users)| (emoji.clone(), users.len()))
|
|
.collect();
|
|
|
|
Ok(counts)
|
|
}
|
|
|
|
/// Obtenir les réactions d'un user pour tous les messages d'une conversation
|
|
#[instrument(skip(self))]
|
|
pub async fn get_user_reactions_in_conversation(
|
|
&self,
|
|
conversation_id: i64,
|
|
user_id: i64,
|
|
) -> Result<HashMap<i64, ReactionEmoji>, sqlx::Error> {
|
|
let reactions: Vec<(i64, String)> = sqlx::query_as(
|
|
"SELECT mr.message_id, mr.emoji
|
|
FROM message_reactions mr
|
|
JOIN messages m ON m.id = mr.message_id
|
|
WHERE m.conversation_id = $1 AND mr.user_id = $2"
|
|
)
|
|
.bind(conversation_id)
|
|
.bind(user_id)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
let mut result = HashMap::new();
|
|
|
|
for (message_id, emoji_str) in reactions {
|
|
if let Some(emoji) = ReactionEmoji::from_str(&emoji_str) {
|
|
result.insert(message_id, emoji);
|
|
}
|
|
}
|
|
|
|
Ok(result)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_reaction_emoji_conversion() {
|
|
assert_eq!(ReactionEmoji::Like.as_str(), "👍");
|
|
assert_eq!(ReactionEmoji::from_str("👍"), Some(ReactionEmoji::Like));
|
|
assert_eq!(ReactionEmoji::from_str("invalid"), None);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_reactions_manager() {
|
|
// Note: Ces tests nécessitent une base de données de test
|
|
// Pour l'instant, on teste juste que le code compile
|
|
assert!(true);
|
|
}
|
|
}
|