This commit is contained in:
2026-08-17 01:12:07 +02:00
parent 07f8d5fe73
commit 429d82e64c
21 changed files with 733 additions and 6 deletions
+74 -3
View File
@@ -1,10 +1,14 @@
use crate::domain::events::message::{
MessageCreatedEvent, MessageDeletedEvent, MessageUpdatedEvent,
MessageCreatedEvent, MessageDeletedEvent, MessageReactionAddedEvent,
MessageReactionRemovedEvent, MessageUpdatedEvent,
};
use crate::models::user::Model as User;
use crate::routes::category::mapper::category_model_to_category_response;
use crate::routes::channel::mapper::channel_model_to_channel_response;
use crate::routes::message::mapper::message_model_to_message_response_with_server_id;
use crate::routes::message::mapper::{
message_model_to_message_response_with_reactions,
message_model_to_message_response_with_server_id, reaction_model_to_response,
};
use crate::routes::server::mapper::server_model_to_server_response;
use crate::services::Services;
use axum::extract::ws::Message;
@@ -70,12 +74,22 @@ impl GatewayManager {
event_bus.on_async::<MessageUpdatedEvent, _, _>("message_updated", move |event| {
let manager = Arc::clone(&manager);
async move {
let message_id = event.message.id;
let reactions = manager
.services
.message_reaction
.grouped_for_messages(&[message_id])
.await
.ok()
.and_then(|mut groups| groups.remove(&message_id))
.unwrap_or_default();
manager.broadcast_message(
event.channel_id,
"update",
message_model_to_message_response_with_server_id(
message_model_to_message_response_with_reactions(
event.message,
event.server_id,
reactions,
),
);
}
@@ -88,6 +102,36 @@ impl GatewayManager {
manager.broadcast_message(event.channel_id, "remove", event.message.id);
}
});
let manager = Arc::clone(self);
event_bus.on_async::<MessageReactionAddedEvent, _, _>(
"message_reaction_added",
move |event| {
let manager = Arc::clone(&manager);
async move {
manager.broadcast_reaction(
event.channel_id,
"add",
reaction_model_to_response(event.reaction),
);
}
},
);
let manager = Arc::clone(self);
event_bus.on_async::<MessageReactionRemovedEvent, _, _>(
"message_reaction_removed",
move |event| {
let manager = Arc::clone(&manager);
async move {
manager.broadcast_reaction(
event.channel_id,
"remove",
reaction_model_to_response(event.reaction),
);
}
},
);
}
pub(crate) fn add_client(&self, gateway_client: GatewayClient) {
@@ -126,6 +170,33 @@ impl GatewayManager {
}
}
}
fn broadcast_reaction<T: serde::Serialize>(
&self,
channel_id: Uuid,
action: &'static str,
content: T,
) {
let event = GatewayEvent {
namespace: "Reaction",
action,
content,
};
let Ok(json) = serde_json::to_string(&event) else {
return;
};
let users = self
.services
.realtime_registry
.users_for_channel(channel_id);
let clients = self.clients.read();
for (key, client) in clients.iter() {
if users.contains(&key.user_id) {
let _ = client.sender.send(Message::Text(json.clone().into()));
}
}
}
}
impl GatewayClient {
+97 -3
View File
@@ -3,6 +3,7 @@ use crate::domain::dto::message::{
CreateMessageRequest, MessagePageResponse, MessageQueryParams, MessageResponse,
UpdateMessageRequest,
};
use crate::domain::dto::reaction::{CreateReactionRequest, DeleteReactionQuery, ReactionResponse};
use crate::http::context::CurrentUser;
use crate::http::error::HTTPError;
use crate::routes::message::mapper;
@@ -39,6 +40,12 @@ pub async fn get_all(
let params = mapper::query_params_to_message_filter(filters);
let page = state.repositories.message.filter(params).await?;
let message_ids: Vec<_> = page.messages.iter().map(|message| message.id).collect();
let mut reactions = state
.services
.message_reaction
.grouped_for_messages(&message_ids)
.await?;
let oldest_id = page.messages.first().map(|message| message.id);
let newest_id = page.messages.last().map(|message| message.id);
@@ -46,7 +53,10 @@ pub async fn get_all(
messages: page
.messages
.into_iter()
.map(mapper::message_model_to_message_response)
.map(|message| {
let groups = reactions.remove(&message.id).unwrap_or_default();
mapper::message_model_to_message_response_with_reactions(message, None, groups)
})
.collect(),
oldest_id,
newest_id,
@@ -80,7 +90,17 @@ pub async fn get_by_id(
.await?
.ok_or(HTTPError::NotFound)?;
Ok(Json(mapper::message_model_to_message_response(message)))
let reactions = state
.services
.message_reaction
.grouped_for_messages(&[id])
.await?
.remove(&id)
.unwrap_or_default();
Ok(Json(
mapper::message_model_to_message_response_with_reactions(message, None, reactions),
))
}
/// Crée un nouveau message
@@ -181,7 +201,81 @@ pub async fn update(
.update_message(id, payload.content)
.await?;
Ok(Json(mapper::message_model_to_message_response(message)))
let reactions = state
.services
.message_reaction
.grouped_for_messages(&[id])
.await?
.remove(&id)
.unwrap_or_default();
Ok(Json(
mapper::message_model_to_message_response_with_reactions(message, None, reactions),
))
}
#[utoipa::path(
post,
path = "/messages/{message_id}/reactions",
request_body = CreateReactionRequest,
responses(
(status = 201, description = "Réaction ajoutée", body = ReactionResponse),
(status = 200, description = "Réaction déjà présente", body = ReactionResponse),
(status = 400, description = "Emoji ou ton invalide"),
(status = 404, description = "Message non trouvé")
),
params(("message_id" = Uuid, Path)),
tag = "Messages",
security(("bearerAuth" = []))
)]
pub async fn add_reaction(
user: CurrentUser,
State(state): State<AppState>,
Path(message_id): Path<Uuid>,
Json(payload): Json<CreateReactionRequest>,
) -> Result<(StatusCode, Json<ReactionResponse>), HTTPError> {
let (reaction, created) = state
.services
.message_reaction
.add(message_id, user.id, payload.emoji_id, payload.skin_tone)
.await?;
Ok((
if created {
StatusCode::CREATED
} else {
StatusCode::OK
},
Json(mapper::reaction_model_to_response(reaction)),
))
}
#[utoipa::path(
delete,
path = "/messages/{message_id}/reactions/{emoji_id}",
responses(
(status = 204, description = "Réaction supprimée"),
(status = 400, description = "Emoji ou ton invalide"),
(status = 404, description = "Réaction non trouvée")
),
params(
("message_id" = Uuid, Path),
("emoji_id" = Uuid, Path),
DeleteReactionQuery
),
tag = "Messages",
security(("bearerAuth" = []))
)]
pub async fn remove_reaction(
user: CurrentUser,
State(state): State<AppState>,
Path((message_id, emoji_id)): Path<(Uuid, Uuid)>,
Query(query): Query<DeleteReactionQuery>,
) -> Result<StatusCode, HTTPError> {
state
.services
.message_reaction
.remove(message_id, user.id, emoji_id, query.skin_tone)
.await?;
Ok(StatusCode::NO_CONTENT)
}
/// Supprime un message
+23
View File
@@ -1,7 +1,10 @@
use crate::domain::dto::message::{
CreateMessageRequest, MessageQueryParams, MessageResponse, UpdateMessageRequest,
};
use crate::domain::dto::reaction::ReactionGroupResponse;
use crate::domain::dto::reaction::ReactionResponse;
use crate::models::message;
use crate::models::message_reaction;
use crate::repositories::types::MessageFilter;
use chrono::Utc;
use sea_orm::Set;
@@ -14,6 +17,14 @@ pub fn message_model_to_message_response(model: message::Model) -> MessageRespon
pub fn message_model_to_message_response_with_server_id(
model: message::Model,
server_id: Option<Uuid>,
) -> MessageResponse {
message_model_to_message_response_with_reactions(model, server_id, Vec::new())
}
pub fn message_model_to_message_response_with_reactions(
model: message::Model,
server_id: Option<Uuid>,
reactions: Vec<ReactionGroupResponse>,
) -> MessageResponse {
MessageResponse {
id: model.id,
@@ -24,6 +35,7 @@ pub fn message_model_to_message_response_with_server_id(
created_at: model.created_at,
updated_at: model.updated_at,
reply_to_id: model.reply_to_id,
reactions,
}
}
@@ -62,3 +74,14 @@ pub fn query_params_to_message_filter(params: MessageQueryParams) -> MessageFilt
limit: params.limit,
}
}
pub fn reaction_model_to_response(model: message_reaction::Model) -> ReactionResponse {
ReactionResponse {
id: model.id,
message_id: model.message_id,
user_id: model.user_id,
emoji_id: model.emoji_id,
skin_tone: (model.skin_tone != 0).then_some(model.skin_tone as u8),
created_at: model.created_at,
}
}
+8
View File
@@ -11,4 +11,12 @@ pub fn router() -> Router<AppState> {
.put(handlers::update)
.delete(handlers::delete),
)
.route(
"/messages/{message_id}/reactions",
axum::routing::post(handlers::add_reaction),
)
.route(
"/messages/{message_id}/reactions/{emoji_id}",
axum::routing::delete(handlers::remove_reaction),
)
}
+5
View File
@@ -41,6 +41,8 @@ use utoipa::{Modify, OpenApi};
message::handlers::create,
message::handlers::update,
message::handlers::delete,
message::handlers::add_reaction,
message::handlers::remove_reaction,
core::handlers::join,
emoji::handlers::get_all,
emoji::handlers::get_by_id,
@@ -73,6 +75,9 @@ use utoipa::{Modify, OpenApi};
crate::domain::dto::message::MessageResponse,
crate::domain::dto::message::CreateMessageRequest,
crate::domain::dto::message::UpdateMessageRequest,
crate::domain::dto::reaction::CreateReactionRequest,
crate::domain::dto::reaction::ReactionResponse,
crate::domain::dto::reaction::ReactionGroupResponse,
crate::domain::dto::core::JoinRequest,
ChannelType,
crate::domain::dto::emoji::EmojiResponse,