use export::notify::publish_notification; use models::pull_request::{pull_request_reviewer, pull_requests}; use models::users::user_notifications::{ActiveModel as NotificationModel, NotificationType}; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; use time::OffsetDateTime; use uuid::Uuid; impl super::AgentService { /// 评审完成后通知 PR 作者与当前待评审人。 /// /// 通知仅写库 + Redis 推送,前端按既有轮询/WS 机制刷新即可看到结果。 pub(crate) async fn notify_pull_ai( &self, pull: &pull_requests::Model, bot: Uuid, language: &str, summary: &str, findings: usize, ) { let mut recipients: Vec = vec![pull.author_id]; match pull_request_reviewer::Entity::find() .filter(pull_request_reviewer::Column::PullRequestId.eq(pull.id)) .all(self.db_read()) .await { Ok(rows) => { for row in rows { if !recipients.contains(&row.user_id) { recipients.push(row.user_id); } } } Err(error) => tracing::warn!(%error, "ai review recipient lookup failed"), } recipients.retain(|uid| *uid != bot); if recipients.is_empty() { return; } let title = crate::notifications::review_title(language, pull.autoincrement_id); let body = crate::notifications::review_body(language, findings, summary); let data = serde_json::json!({ "kind": "ai_review", "pull_request_id": pull.id, "repo_id": pull.repo_id, "findings": findings, }); let now = OffsetDateTime::now_utc(); for uid in recipients { let inserted = NotificationModel { id: Set(Uuid::now_v7()), user_id: Set(uid), notification_type: Set(NotificationType::System), title: Set(title.clone()), body: Set(Some(body.clone())), data_json: Set(Some(data.clone())), actor_uid: Set(Some(bot)), read_at: Set(None), created_at: Set(now), } .insert(self.db_write()) .await; match inserted { Ok(row) => { publish_notification(&self.state.redis, uid, row.to_event_value()).await; } Err(error) => tracing::warn!(%error, %uid, "ai review notification insert failed"), } } } }