feat(message): add message center module (Phase 5)
Implement the complete message center with: - Database migrations for message_templates, messages, message_subscriptions tables - erp-message crate with entities, DTOs, services, handlers - Message CRUD, send, read/unread tracking, soft delete - Template management with variable interpolation - Subscription preferences with DND support - Frontend: messages page, notification panel, unread count badge - Server integration with module registration and routing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
116
crates/erp-message/src/service/subscription_service.rs
Normal file
116
crates/erp-message/src/service/subscription_service.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use chrono::Utc;
|
||||
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::dto::{MessageSubscriptionResp, UpdateSubscriptionReq};
|
||||
use crate::entity::message_subscription;
|
||||
use crate::error::{MessageError, MessageResult};
|
||||
|
||||
/// 消息订阅偏好服务。
|
||||
pub struct SubscriptionService;
|
||||
|
||||
impl SubscriptionService {
|
||||
/// 获取用户订阅偏好。
|
||||
pub async fn get(
|
||||
tenant_id: Uuid,
|
||||
user_id: Uuid,
|
||||
db: &sea_orm::DatabaseConnection,
|
||||
) -> MessageResult<MessageSubscriptionResp> {
|
||||
let model = message_subscription::Entity::find()
|
||||
.filter(message_subscription::Column::TenantId.eq(tenant_id))
|
||||
.filter(message_subscription::Column::UserId.eq(user_id))
|
||||
.filter(message_subscription::Column::DeletedAt.is_null())
|
||||
.one(db)
|
||||
.await
|
||||
.map_err(|e| MessageError::Validation(e.to_string()))?
|
||||
.ok_or_else(|| MessageError::NotFound("订阅偏好不存在".to_string()))?;
|
||||
|
||||
Ok(Self::model_to_resp(&model))
|
||||
}
|
||||
|
||||
/// 创建或更新用户订阅偏好(upsert)。
|
||||
pub async fn upsert(
|
||||
tenant_id: Uuid,
|
||||
user_id: Uuid,
|
||||
req: &UpdateSubscriptionReq,
|
||||
db: &sea_orm::DatabaseConnection,
|
||||
) -> MessageResult<MessageSubscriptionResp> {
|
||||
let existing = message_subscription::Entity::find()
|
||||
.filter(message_subscription::Column::TenantId.eq(tenant_id))
|
||||
.filter(message_subscription::Column::UserId.eq(user_id))
|
||||
.filter(message_subscription::Column::DeletedAt.is_null())
|
||||
.one(db)
|
||||
.await
|
||||
.map_err(|e| MessageError::Validation(e.to_string()))?;
|
||||
|
||||
let now = Utc::now();
|
||||
|
||||
if let Some(model) = existing {
|
||||
let mut active: message_subscription::ActiveModel = model.into();
|
||||
if let Some(types) = &req.notification_types {
|
||||
active.notification_types = Set(Some(types.clone()));
|
||||
}
|
||||
if let Some(prefs) = &req.channel_preferences {
|
||||
active.channel_preferences = Set(Some(prefs.clone()));
|
||||
}
|
||||
if let Some(dnd) = req.dnd_enabled {
|
||||
active.dnd_enabled = Set(dnd);
|
||||
}
|
||||
if let Some(ref start) = req.dnd_start {
|
||||
active.dnd_start = Set(Some(start.clone()));
|
||||
}
|
||||
if let Some(ref end) = req.dnd_end {
|
||||
active.dnd_end = Set(Some(end.clone()));
|
||||
}
|
||||
active.updated_at = Set(now);
|
||||
active.updated_by = Set(user_id);
|
||||
|
||||
let updated = active
|
||||
.update(db)
|
||||
.await
|
||||
.map_err(|e| MessageError::Validation(e.to_string()))?;
|
||||
|
||||
Ok(Self::model_to_resp(&updated))
|
||||
} else {
|
||||
let id = Uuid::now_v7();
|
||||
|
||||
let model = message_subscription::ActiveModel {
|
||||
id: Set(id),
|
||||
tenant_id: Set(tenant_id),
|
||||
user_id: Set(user_id),
|
||||
notification_types: Set(req.notification_types.clone()),
|
||||
channel_preferences: Set(req.channel_preferences.clone()),
|
||||
dnd_enabled: Set(req.dnd_enabled.unwrap_or(false)),
|
||||
dnd_start: Set(req.dnd_start.clone()),
|
||||
dnd_end: Set(req.dnd_end.clone()),
|
||||
created_at: Set(now),
|
||||
updated_at: Set(now),
|
||||
created_by: Set(user_id),
|
||||
updated_by: Set(user_id),
|
||||
deleted_at: Set(None),
|
||||
};
|
||||
|
||||
let inserted = model
|
||||
.insert(db)
|
||||
.await
|
||||
.map_err(|e| MessageError::Validation(e.to_string()))?;
|
||||
|
||||
Ok(Self::model_to_resp(&inserted))
|
||||
}
|
||||
}
|
||||
|
||||
fn model_to_resp(m: &message_subscription::Model) -> MessageSubscriptionResp {
|
||||
MessageSubscriptionResp {
|
||||
id: m.id,
|
||||
tenant_id: m.tenant_id,
|
||||
user_id: m.user_id,
|
||||
notification_types: m.notification_types.clone(),
|
||||
channel_preferences: m.channel_preferences.clone(),
|
||||
dnd_enabled: m.dnd_enabled,
|
||||
dnd_start: m.dnd_start.clone(),
|
||||
dnd_end: m.dnd_end.clone(),
|
||||
created_at: m.created_at,
|
||||
updated_at: m.updated_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user