132 lines
4.1 KiB
Rust
132 lines
4.1 KiB
Rust
use chrono::Utc;
|
|
use db::database::AppDatabase;
|
|
use models::projects::project_members;
|
|
use queue::ProjectRoomEvent;
|
|
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
|
use uuid::Uuid;
|
|
|
|
use crate::error::RoomError;
|
|
|
|
pub fn notify_project_members(
|
|
db: AppDatabase,
|
|
project_id: Uuid,
|
|
notification_type: crate::NotificationType,
|
|
title: String,
|
|
content: Option<String>,
|
|
related_room_id: Option<Uuid>,
|
|
) {
|
|
let notification_type_inner = notification_type;
|
|
let title_inner = title;
|
|
let content_inner = content;
|
|
let related_room_id_inner = related_room_id;
|
|
let project_id_inner = project_id;
|
|
|
|
tokio::spawn(async move {
|
|
let members = match project_members::Entity::find()
|
|
.filter(project_members::Column::Project.eq(project_id_inner))
|
|
.all(&db)
|
|
.await
|
|
{
|
|
Ok(m) => m,
|
|
Err(e) => {
|
|
tracing::error!(project_id = %project_id_inner, error = %e,
|
|
"notify_project_members: failed to fetch members");
|
|
return;
|
|
}
|
|
};
|
|
|
|
for member in members {
|
|
let user_id = member.user;
|
|
if let Err(e) = create_notification_sync(
|
|
&db,
|
|
notification_type_inner,
|
|
user_id,
|
|
title_inner.clone(),
|
|
content_inner.clone(),
|
|
related_room_id_inner,
|
|
project_id_inner,
|
|
)
|
|
.await
|
|
{
|
|
tracing::warn!(user_id = %user_id, project_id = %project_id_inner, error = %e,
|
|
"notify_project_members: failed to create notification for user");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
async fn create_notification_sync(
|
|
db: &AppDatabase,
|
|
notification_type: crate::NotificationType,
|
|
user_id: Uuid,
|
|
title: String,
|
|
content: Option<String>,
|
|
related_room_id: Option<Uuid>,
|
|
project_id: Uuid,
|
|
) -> Result<(), RoomError> {
|
|
use models::rooms::room_notifications;
|
|
use sea_orm::{ActiveModelTrait, Set};
|
|
|
|
let notification_type_model = match notification_type {
|
|
crate::NotificationType::Mention => room_notifications::NotificationType::Mention,
|
|
crate::NotificationType::Invitation => room_notifications::NotificationType::Invitation,
|
|
crate::NotificationType::RoleChange => room_notifications::NotificationType::RoleChange,
|
|
crate::NotificationType::RoomCreated => room_notifications::NotificationType::RoomCreated,
|
|
crate::NotificationType::RoomDeleted => room_notifications::NotificationType::RoomDeleted,
|
|
crate::NotificationType::SystemAnnouncement => {
|
|
room_notifications::NotificationType::SystemAnnouncement
|
|
}
|
|
crate::NotificationType::ProjectInvitation => {
|
|
room_notifications::NotificationType::ProjectInvitation
|
|
}
|
|
};
|
|
|
|
let _model = room_notifications::ActiveModel {
|
|
id: Set(Uuid::now_v7()),
|
|
room: Set(related_room_id),
|
|
project: Set(Some(project_id)),
|
|
user_id: Set(Some(user_id)),
|
|
notification_type: Set(notification_type_model),
|
|
related_message_id: Set(None),
|
|
related_user_id: Set(None),
|
|
related_room_id: Set(related_room_id),
|
|
title: Set(title),
|
|
content: Set(content),
|
|
metadata: Set(None),
|
|
is_read: Set(false),
|
|
is_archived: Set(false),
|
|
created_at: Set(Utc::now()),
|
|
read_at: Set(None),
|
|
expires_at: Set(None),
|
|
}
|
|
.insert(db)
|
|
.await
|
|
.map_err(|e| RoomError::Database(e))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn publish_room_event(
|
|
queue: &queue::MessageProducer,
|
|
project_id: Uuid,
|
|
event_type: crate::RoomEventType,
|
|
room_id: Option<Uuid>,
|
|
message_id: Option<Uuid>,
|
|
seq: Option<i64>,
|
|
) {
|
|
let event = ProjectRoomEvent {
|
|
event_type: event_type.as_str().into(),
|
|
project_id,
|
|
room_id,
|
|
category_id: None,
|
|
message_id,
|
|
seq,
|
|
timestamp: Utc::now(),
|
|
};
|
|
// Fire-and-forget — caller doesn't need to await.
|
|
let queue = queue.clone();
|
|
tokio::spawn(async move {
|
|
queue.publish_project_room_event(project_id, event).await;
|
|
});
|
|
}
|