use models::users::{user, users}; use sea_orm::{ConnectionTrait, DbErr, EntityTrait}; use serde::Serialize; use serde_json::Value; use uuid::Uuid; use crate::{EventKind, Labels, label}; pub async fn emit( tx: &impl ConnectionTrait, kind: EventKind, mut labels: Labels, payload: impl Serialize, ) -> Result { let request_id = labels .get(label::REQUEST_ID) .map(str::to_owned) .or_else(envelope::context::request_id) .unwrap_or_default(); if !request_id.is_empty() { labels.insert(label::REQUEST_ID, &request_id); } let mut payload = serde_json::to_value(payload) .map_err(|error| DbErr::Custom(format!("event serialization failed: {error}")))?; let actor_id = labels .get(label::ACTOR_USER_ID) .and_then(|value| value.parse::().ok()) .or_else(|| { payload .get("notification") .and_then(|notification| notification.get("actor_id")) .and_then(Value::as_i64) }) .or_else(|| { payload .get("message") .and_then(|message| message.get("author_id")) .and_then(Value::as_i64) }) .or_else(|| { if labels.get(label::RECIPIENT_USER_ID).is_none() { labels .get(label::USER_ID) .and_then(|value| value.parse::().ok()) } else { None } }); if let Some(uid) = actor_id && let Some(snapshot) = actor_snapshot(tx, uid).await { if payload.is_object() && payload.get("actor").is_none_or(Value::is_null) { payload["actor"] = snapshot.clone(); } if let Some(notification) = payload.get_mut("notification") && notification.is_object() && notification.get("actor").is_none_or(Value::is_null) { notification["actor"] = snapshot; } } if let Some(message) = payload.get_mut("message") && message.get("author").is_none_or(Value::is_null) && let Some(uid) = message.get("author_id").and_then(Value::as_i64) && let Some(snapshot) = actor_snapshot(tx, uid).await { message["author"] = snapshot; } crate::outbox::enqueue_now(tx, kind, labels, payload, &request_id, Some(&request_id)).await } pub async fn actor_snapshot(tx: &impl ConnectionTrait, uid: i64) -> Option { let identity = users::Entity::find_by_id(uid).one(tx).await.ok()??; let local = user::Entity::find_by_id(uid).one(tx).await.ok().flatten(); let avatar = local.map(|user| user.local_avatar_url).unwrap_or_default(); let avatar = if avatar.is_empty() { String::new() } else if avatar.starts_with("http://") || avatar.starts_with("https://") { avatar } else { format!("/api/v1/users/{uid}/avatar") }; Some(serde_json::json!({ "id": identity.id, "username": identity.username, "display_name": identity.displayname, "avatar_url": avatar, })) }