gitdataai/libs/room/src/service/notifications.rs

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;
});
}