feat(events): introduce EventKind for event type handling
ZhenYi committed
Modified lib/service/auth/src/services/mod.rs lib/service/auth/src/services/mod.rs +4 −4 View lib/service/auth/src/services/mod.rs
@@ -94,7 +94,7 @@
9494 }
9595 }
9696
97 pub async fn emit<T>(&self, kind: &str, payload: T, request_id: Option<&str>)
97 pub async fn emit<T>(&self, kind: events::EventKind, payload: T, request_id: Option<&str>)
9898 where
9999 T: Serialize + Clone,
100100 {
@@ -104,7 +104,7 @@
104104
105105 pub async fn emit_labeled<T>(
106106 &self,
107 kind: &str,
107 kind: events::EventKind,
108108 labels: Labels,
109109 payload: T,
110110 request_id: Option<&str>,
@@ -120,14 +120,14 @@
120120 .await;
121121
122122 if let Err(error) = enqueued {
123 tracing::error!(kind, %error, "failed to enqueue the event in the outbox");
123 tracing::error!(kind = %kind, %error, "failed to enqueue the event in the outbox");
124124 }
125125 }
126126
127127 pub async fn emit_tx<T>(
128128 &self,
129129 conn: &impl sea_orm::ConnectionTrait,
130 kind: &str,
130 kind: events::EventKind,
131131 labels: Labels,
132132 payload: T,
133133 request_id: Option<&str>,
Modified lib/service/chat/src/services/rooms.rs lib/service/chat/src/services/rooms.rs +1 −1 View lib/service/chat/src/services/rooms.rs
@@ -686,7 +686,7 @@
686686 }
687687 events::emit(
688688 tx,
689 "chat.room.update",
689 events::EventKind::ChatRoomUpdate,
690690 events::Labels::new()
691691 .with(events::label::ROOM_ID, room_id)
692692 .with(events::label::ACTOR_USER_ID, actor),
Modified lib/service/chat/src/share/events.rs lib/service/chat/src/share/events.rs +23 −23 View lib/service/chat/src/share/events.rs
@@ -8,34 +8,34 @@
88use crate::errors::ChatError;
99use crate::share::realtime::RoomEvent;
1010
11pub fn room_kind(event: &RoomEvent) -> &'static str {
11pub fn room_kind(event: &RoomEvent) -> events::EventKind {
1212 match event {
13 RoomEvent::MessagePublished { .. } => "chat.message.create",
14 RoomEvent::MessageEdited { .. } => "chat.message.update",
15 RoomEvent::MessageDeleted { .. } => "chat.message.delete",
16 RoomEvent::MessagesDeleted { .. } => "chat.messages.delete",
17 RoomEvent::MessagePinned { pinned: true, .. } => "chat.pin.create",
18 RoomEvent::MessagePinned { pinned: false, .. } => "chat.pin.delete",
19 RoomEvent::ReactionUpdated { .. } => "chat.reaction.update",
20 RoomEvent::Typing { .. } => "chat.typing.create",
21 RoomEvent::RoomUpdated { .. } => "chat.room.update",
22 RoomEvent::MemberJoined { .. } => "chat.member.create",
23 RoomEvent::MemberLeft { .. } => "chat.member.delete",
24 RoomEvent::ThreadUpdated { .. } => "chat.thread.update",
25 RoomEvent::VoicePresence { .. } => "chat.voice.presence",
26 RoomEvent::VoiceHands { .. } => "chat.voice.hand",
27 RoomEvent::VoiceLayout { .. } => "chat.voice.layout",
28 RoomEvent::VoiceModeration { .. } => "chat.voice.moderate",
13 RoomEvent::MessagePublished { .. } => events::EventKind::ChatMessageCreate,
14 RoomEvent::MessageEdited { .. } => events::EventKind::ChatMessageUpdate,
15 RoomEvent::MessageDeleted { .. } => events::EventKind::ChatMessageDelete,
16 RoomEvent::MessagesDeleted { .. } => events::EventKind::ChatMessagesDelete,
17 RoomEvent::MessagePinned { pinned: true, .. } => events::EventKind::ChatPinCreate,
18 RoomEvent::MessagePinned { pinned: false, .. } => events::EventKind::ChatPinDelete,
19 RoomEvent::ReactionUpdated { .. } => events::EventKind::ChatReactionUpdate,
20 RoomEvent::Typing { .. } => events::EventKind::ChatTypingCreate,
21 RoomEvent::RoomUpdated { .. } => events::EventKind::ChatRoomUpdate,
22 RoomEvent::MemberJoined { .. } => events::EventKind::ChatMemberCreate,
23 RoomEvent::MemberLeft { .. } => events::EventKind::ChatMemberDelete,
24 RoomEvent::ThreadUpdated { .. } => events::EventKind::ChatThreadUpdate,
25 RoomEvent::VoicePresence { .. } => events::EventKind::ChatVoicePresence,
26 RoomEvent::VoiceHands { .. } => events::EventKind::ChatVoiceHand,
27 RoomEvent::VoiceLayout { .. } => events::EventKind::ChatVoiceLayout,
28 RoomEvent::VoiceModeration { .. } => events::EventKind::ChatVoiceModerate,
2929 }
3030}
3131
32pub fn user_kind(event: &Value) -> Option<&'static str> {
32pub fn user_kind(event: &Value) -> Option<events::EventKind> {
3333 match event.get("kind").and_then(Value::as_str) {
34 Some("mention") => Some("chat.mention.create"),
35 Some("reminder") => Some("chat.reminder.create"),
36 Some("room_added") => Some("chat.membership.create"),
37 Some("read_marker") => Some("chat.read_state.update"),
38 Some("presence") => Some("chat.presence.update"),
34 Some("mention") => Some(events::EventKind::ChatMentionCreate),
35 Some("reminder") => Some(events::EventKind::ChatReminderCreate),
36 Some("room_added") => Some(events::EventKind::ChatMembershipCreate),
37 Some("read_marker") => Some(events::EventKind::ChatReadStateUpdate),
38 Some("presence") => Some(events::EventKind::ChatPresenceUpdate),
3939 _ => None,
4040 }
4141}
Modified lib/service/dir/src/lib.rs lib/service/dir/src/lib.rs +4 −4 View lib/service/dir/src/lib.rs
@@ -140,7 +140,7 @@
140140 .insert(tx)
141141 .await?;
142142 let dto = DirDto::from(created);
143 emit(tx, "dir.create", actor, &dto).await?;
143 emit(tx, events::EventKind::DirCreate, actor, &dto).await?;
144144 Ok(dto)
145145}
146146
@@ -191,7 +191,7 @@
191191 am.updated_at = Set(OffsetDateTime::now_utc());
192192 let updated = am.update(tx).await?;
193193 let dto = DirDto::from(updated);
194 emit(tx, "dir.update", actor, &dto).await?;
194 emit(tx, events::EventKind::DirUpdate, actor, &dto).await?;
195195 Ok(dto)
196196}
197197
@@ -211,7 +211,7 @@
211211 am.updated_at = Set(now);
212212 let updated = am.update(tx).await?;
213213 let dto = DirDto::from(updated);
214 emit(tx, "dir.archive", actor, &dto).await?;
214 emit(tx, events::EventKind::DirArchive, actor, &dto).await?;
215215 Ok(())
216216}
217217
@@ -308,7 +308,7 @@
308308
309309async fn emit(
310310 tx: &impl ConnectionTrait,
311 kind: &str,
311 kind: events::EventKind,
312312 actor: i64,
313313 dir: &DirDto,
314314) -> Result<(), Error> {
Modified lib/service/issues/src/services/service.rs lib/service/issues/src/services/service.rs +25 −25 View lib/service/issues/src/services/service.rs
@@ -200,32 +200,32 @@
200200 }
201201}
202202
203fn event_kind(event_type: IssueEventType) -> &'static str {
203fn event_kind(event_type: IssueEventType) -> events::EventKind {
204204 match event_type {
205 IssueEventType::Created => "issue.issue.create",
206 IssueEventType::TitleUpdated => "issue.title.update",
207 IssueEventType::DescriptionUpdated => "issue.description.update",
208 IssueEventType::ContentUpdated => "issue.content.update",
209 IssueEventType::MilestoneUpdated => "issue.milestone.update",
210 IssueEventType::PriorityUpdated => "issue.priority.update",
211 IssueEventType::DueUpdated => "issue.due.update",
212 IssueEventType::Closed => "issue.issue.close",
213 IssueEventType::Reopened => "issue.issue.reopen",
214 IssueEventType::CommentCreated => "issue.comment.create",
215 IssueEventType::CommentUpdated => "issue.comment.update",
216 IssueEventType::CommentDeleted => "issue.comment.delete",
217 IssueEventType::MemberAdded => "issue.member.create",
218 IssueEventType::MemberRemoved => "issue.member.delete",
219 IssueEventType::LabelAdded => "issue.label.create",
220 IssueEventType::LabelRemoved => "issue.label.delete",
221 IssueEventType::DependencyAdded => "issue.dependency.create",
222 IssueEventType::DependencyRemoved => "issue.dependency.delete",
223 IssueEventType::Pinned => "issue.pin.create",
224 IssueEventType::Unpinned => "issue.pin.delete",
225 IssueEventType::RepoRelationAdded => "issue.repo-relation.create",
226 IssueEventType::RepoRelationRemoved => "issue.repo-relation.delete",
227 IssueEventType::PullLinked => "issue.pull-link.create",
228 IssueEventType::PullUnlinked => "issue.pull-link.delete",
205 IssueEventType::Created => events::EventKind::IssueIssueCreate,
206 IssueEventType::TitleUpdated => events::EventKind::IssueTitleUpdate,
207 IssueEventType::DescriptionUpdated => events::EventKind::IssueDescriptionUpdate,
208 IssueEventType::ContentUpdated => events::EventKind::IssueContentUpdate,
209 IssueEventType::MilestoneUpdated => events::EventKind::IssueMilestoneUpdate,
210 IssueEventType::PriorityUpdated => events::EventKind::IssuePriorityUpdate,
211 IssueEventType::DueUpdated => events::EventKind::IssueDueUpdate,
212 IssueEventType::Closed => events::EventKind::IssueIssueClose,
213 IssueEventType::Reopened => events::EventKind::IssueIssueReopen,
214 IssueEventType::CommentCreated => events::EventKind::IssueCommentCreate,
215 IssueEventType::CommentUpdated => events::EventKind::IssueCommentUpdate,
216 IssueEventType::CommentDeleted => events::EventKind::IssueCommentDelete,
217 IssueEventType::MemberAdded => events::EventKind::IssueMemberCreate,
218 IssueEventType::MemberRemoved => events::EventKind::IssueMemberDelete,
219 IssueEventType::LabelAdded => events::EventKind::IssueLabelCreate,
220 IssueEventType::LabelRemoved => events::EventKind::IssueLabelDelete,
221 IssueEventType::DependencyAdded => events::EventKind::IssueDependencyCreate,
222 IssueEventType::DependencyRemoved => events::EventKind::IssueDependencyDelete,
223 IssueEventType::Pinned => events::EventKind::IssuePinCreate,
224 IssueEventType::Unpinned => events::EventKind::IssuePinDelete,
225 IssueEventType::RepoRelationAdded => events::EventKind::IssueRepoRelationCreate,
226 IssueEventType::RepoRelationRemoved => events::EventKind::IssueRepoRelationDelete,
227 IssueEventType::PullLinked => events::EventKind::IssuePullLinkCreate,
228 IssueEventType::PullUnlinked => events::EventKind::IssuePullLinkDelete,
229229 }
230230}
231231
Modified lib/service/pulls/src/services/events.rs lib/service/pulls/src/services/events.rs +20 −20 View lib/service/pulls/src/services/events.rs
@@ -115,27 +115,27 @@
115115 events::emit(db, kind, labels, payload).await.map(|_| ())
116116}
117117
118fn event_kind(event_type: &str) -> Option<&'static str> {
118fn event_kind(event_type: &str) -> Option<events::EventKind> {
119119 Some(match event_type {
120 EVENT_CREATED => "pull.pull.create",
121 EVENT_UPDATED => "pull.pull.update",
122 EVENT_CLOSED => "pull.pull.close",
123 EVENT_REOPENED => "pull.pull.reopen",
124 EVENT_MERGED => "pull.pull.merge",
125 EVENT_COMMENTED => "pull.comment.create",
126 EVENT_REVIEW_SUBMITTED => "pull.review.create",
127 EVENT_REVIEW_DISMISSED => "pull.review.delete",
128 EVENT_REVIEW_REQUESTED => "pull.review-request.create",
129 EVENT_REVIEW_REQUEST_CANCELLED => "pull.review-request.delete",
130 EVENT_HEAD_UPDATED => "pull.head.update",
131 EVENT_LABELED => "pull.label.create",
132 EVENT_UNLABELED => "pull.label.delete",
133 EVENT_ASSIGNED => "pull.assignee.create",
134 EVENT_UNASSIGNED => "pull.assignee.delete",
135 EVENT_LOCKED => "pull.lock.create",
136 EVENT_UNLOCKED => "pull.lock.delete",
137 EVENT_AUTO_MERGE_ENABLED => "pull.auto-merge.create",
138 EVENT_AUTO_MERGE_DISABLED => "pull.auto-merge.delete",
120 EVENT_CREATED => events::EventKind::PullPullCreate,
121 EVENT_UPDATED => events::EventKind::PullPullUpdate,
122 EVENT_CLOSED => events::EventKind::PullPullClose,
123 EVENT_REOPENED => events::EventKind::PullPullReopen,
124 EVENT_MERGED => events::EventKind::PullPullMerge,
125 EVENT_COMMENTED => events::EventKind::PullCommentCreate,
126 EVENT_REVIEW_SUBMITTED => events::EventKind::PullReviewCreate,
127 EVENT_REVIEW_DISMISSED => events::EventKind::PullReviewDelete,
128 EVENT_REVIEW_REQUESTED => events::EventKind::PullReviewRequestCreate,
129 EVENT_REVIEW_REQUEST_CANCELLED => events::EventKind::PullReviewRequestDelete,
130 EVENT_HEAD_UPDATED => events::EventKind::PullHeadUpdate,
131 EVENT_LABELED => events::EventKind::PullLabelCreate,
132 EVENT_UNLABELED => events::EventKind::PullLabelDelete,
133 EVENT_ASSIGNED => events::EventKind::PullAssigneeCreate,
134 EVENT_UNASSIGNED => events::EventKind::PullAssigneeDelete,
135 EVENT_LOCKED => events::EventKind::PullLockCreate,
136 EVENT_UNLOCKED => events::EventKind::PullLockDelete,
137 EVENT_AUTO_MERGE_ENABLED => events::EventKind::PullAutoMergeCreate,
138 EVENT_AUTO_MERGE_DISABLED => events::EventKind::PullAutoMergeDelete,
139139 _ => return None,
140140 })
141141}
Modified lib/service/push/src/notify.rs lib/service/push/src/notify.rs +1 −1 View lib/service/push/src/notify.rs
@@ -108,7 +108,7 @@
108108 .with(events::label::USER_ID, recipient);
109109 events::emit(
110110 tx,
111 "notify.notification.create",
111 events::EventKind::NotifyNotificationCreate,
112112 labels,
113113 serde_json::json!({ "kind": "notification", "notification": row.to_event_value() }),
114114 )
Modified lib/service/space/src/services/service.rs lib/service/space/src/services/service.rs +2 −2 View lib/service/space/src/services/service.rs
@@ -38,7 +38,7 @@
3838 pub async fn emit_tx<T>(
3939 &self,
4040 conn: &impl sea_orm::ConnectionTrait,
41 kind: &str,
41 kind: events::EventKind,
4242 labels: Labels,
4343 payload: T,
4444 request_id: Option<&str>,
@@ -201,7 +201,7 @@
201201 async fn emit_org<T: serde::Serialize>(
202202 &self,
203203 txn: &sea_orm::DatabaseTransaction,
204 kind: &str,
204 kind: events::EventKind,
205205 organization_id: Uuid,
206206 actor_user_id: i64,
207207 payload: T,
Modified lib/service/user/src/services/ssh_key/service.rs lib/service/user/src/services/ssh_key/service.rs +2 −2 View lib/service/user/src/services/ssh_key/service.rs
@@ -298,7 +298,7 @@
298298impl SshKeyService {
299299 async fn emit_labeled(
300300 &self,
301 kind: &str,
301 kind: events::EventKind,
302302 labels: Labels,
303303 payload: impl serde::Serialize,
304304 request_id: Option<&str>,
@@ -311,7 +311,7 @@
311311 )
312312 .await
313313 {
314 tracing::warn!(%error, kind, "SSH key event enqueue failed");
314 tracing::warn!(%error, kind = %kind, "SSH key event enqueue failed");
315315 }
316316 }
317317}
Modified lib/service/user/Cargo.toml lib/service/user/Cargo.toml +1 −0 View lib/service/user/Cargo.toml
@@ -9,6 +9,7 @@
99db = { workspace = true }
1010media = { workspace = true }
1111auth = { workspace = true }
12events = { workspace = true }
1213chrono = { version = "0.4.45", features = ["serde"] }
1314
1415async-trait = { workspace = true }
Modified lib/share/email/src/consumer.rs lib/share/email/src/consumer.rs +3 −3 View lib/share/email/src/consumer.rs
@@ -19,7 +19,7 @@
1919
2020async fn publish_event<T: Serialize>(
2121 events: &EventHandler,
22 kind: &str,
22 kind: events::EventKind,
2323 labels: Labels,
2424 payload: &T,
2525 request_id: &str,
@@ -32,11 +32,11 @@
3232 {
3333 Ok(()) => return true,
3434 Err(error) if attempt >= EVENT_PUBLISH_ATTEMPTS => {
35 tracing::error!(kind, attempt, %error, "failed to publish the email event");
35 tracing::error!(kind = %kind, attempt, %error, "failed to publish the email event");
3636 return false;
3737 }
3838 Err(error) => {
39 tracing::warn!(kind, attempt, %error, "retrying the email event publish");
39 tracing::warn!(kind = %kind, attempt, %error, "retrying the email event publish");
4040 tokio::time::sleep(std::time::Duration::from_millis(50) * attempt as u32).await;
4141 attempt += 1;
4242 }
Deleted lib/share/events/data/events.json lib/share/events/data/events.json +0 −3390
@@ -1,3390 +0,0 @@
1{
2 "generated_at": "2026-10-30",
3 "sources": {
4 "kenshell": {
5 "path": "../KenShell",
6 "version": "1.0.0",
7 "producer_group": "kenshell",
8 "topic_prefix": "kenshell",
9 "event_definition": "src/events/event.rs",
10 "emit": "src/svc/mod.rs:19 (AppSvc::emit)",
11 "publish": "src/events/outbox.rs (durable outbox + relay)"
12 },
13 "kenapi": {
14 "path": "../KenApi",
15 "version": "1.0.0",
16 "producer_group": "gitdata",
17 "topic_prefix": "gitdata",
18 "event_definition": "lib/chat/src/share/events.rs (room_kind / user_kind), lib/service/push/src/notify.rs (notification)",
19 "routing": "lib/queue/src/route.rs",
20 "emit": "lib/export/src/events.rs::enqueue <- lib/chat/src/share/realtime.rs::publish_room/publish_user (post-commit), lib/service/push/src/protocol.rs::enqueue (post-commit), lib/issues/src/services/service.rs::write_event (same connection as timeline row), lib/pulls/src/services/events.rs::write_event (same connection as timeline row)",
21 "publish": "lib/queue/src/relay.rs (durable outbox + relay, max 50 attempts)",
22 "ordering_key": {
23 "chat.room": "chat:room:{room_id}",
24 "chat.user": "chat:user:{user_id}",
25 "notify": "notify:user:{user_id}",
26 "issue": "issue:{issue_id}",
27 "pull": "pull:{pull_request_id}"
28 },
29 "atomicity": {
30 "chat": "post-commit",
31 "notify": "post-commit",
32 "issue": "same connection as issue_event_line (transactional when caller passes a txn)",
33 "pull": "same connection as pull_request_event_line (transactional when caller passes a txn)"
34 },
35 "enabled": "config [rocketmq] enabled (default false); when false nothing is enqueued"
36 }
37 },
38 "message_model": {
39 "topic": "<topic_prefix>-<area>",
40 "body_fields": [
41 "id",
42 "kind",
43 "occurred_at",
44 "summary"
45 ],
46 "summary_equals_tag": true,
47 "payload_on_wire": false,
48 "keys": "envelope id (UUID v7)"
49 },
50 "notes": [
51 "三个项目都不把业务 payload 写进消息体;payload 只用于决定 tag 和派生消息属性。",
52 "KenOAuth 自 2026-10-30 起走 outbox 投递:组织域在业务事务内入队,其余域提交后入队。",
53 "KenOAuth 组织写路径(26 处)写不进 outbox 即让业务事务回滚,与 KenShell 同级;auth/email 域写库失败时退回直接投递,50 次重试后丢弃。",
54 "KenOAuth 已投递行保留 7 天;KenShell 的 event_outbox 已投递行不清理。",
55 "properties 中 user/org/repo_id/repo_name 都是条件出现的,request_id 在两个项目里都存在(KenOAuth 的 email 事件亦同)。",
56 "KenOAuth 的 route 未映射 organization.member.update 的 action=leave,实际 tag 为 update-member。",
57 "KenApi 默认 rocketmq.enabled=false,此时不写 outbox、relay 不启动,Redis pub/sub 与推送照常工作。",
58 "KenApi 的聊天与通知事件是提交后入队;议题/PR 事件与 timeline 行写在同一个连接上,调用方传事务时同事务提交。",
59 "KenApi 只投递本地事件:出站 webhook、应用推送、邮件、LiveKit、KenShell git RPC 属于对外部 RPC 的调用,不产生 MQ 事件。",
60 "KenApi 的 route 要求 kind 恰好三段,tag 恒为 name-operate,没有 KenOAuth 那种别名映射;chat.typing.create 与 10 个 issue / 2 个 pull 的 kind 已定义但无调用点。"
61 ],
62 "events": [
63 {
64 "project": "kenoauth",
65 "topic": "oauth-login",
66 "area": "login",
67 "tag": "login",
68 "kind": "auth.login.create",
69 "payload": {
70 "user_id": "i64",
71 "username": "string",
72 "mfa_required": "bool"
73 },
74 "labels": [
75 "user_id",
76 "request_id"
77 ],
78 "properties": [
79 "user",
80 "request_id"
81 ],
82 "source": "src/auth/login/service.rs:148,274,341",
83 "atomicity": "post-commit"
84 },
85 {
86 "project": "kenoauth",
87 "topic": "oauth-logout",
88 "area": "logout",
89 "tag": "logout",
90 "kind": "auth.logout.delete",
91 "payload": {
92 "user_id": "i64|null"
93 },
94 "labels": [
95 "user_id (optional)",
96 "request_id"
97 ],
98 "properties": [
99 "user",
100 "request_id"
101 ],
102 "source": "src/auth/logout/service.rs:75",
103 "atomicity": "post-commit"
104 },
105 {
106 "project": "kenoauth",
107 "topic": "oauth-register",
108 "area": "register",
109 "tag": "send-code",
110 "kind": "auth.register.create",
111 "payload": {
112 "expires_in_secs": "u64"
113 },
114 "labels": [
115 "request_id"
116 ],
117 "properties": [
118 "request_id"
119 ],
120 "source": "src/auth/register/service.rs:82",
121 "atomicity": "post-commit"
122 },
123 {
124 "project": "kenoauth",
125 "topic": "oauth-register",
126 "area": "register",
127 "tag": "register",
128 "kind": "auth.register.create",
129 "payload": {
130 "user_id": "i64",
131 "username": "string",
132 "displayname": "string|null"
133 },
134 "labels": [
135 "user_id",
136 "request_id"
137 ],
138 "properties": [
139 "user",
140 "request_id"
141 ],
142 "source": "src/auth/register/service.rs:206",
143 "atomicity": "post-commit"
144 },
145 {
146 "project": "kenoauth",
147 "topic": "oauth-verification",
148 "area": "verification",
149 "tag": "send-code",
150 "kind": "auth.verification_token.create",
151 "payload": {
152 "expires_in_secs": "u64"
153 },
154 "labels": [
155 "user_id",
156 "request_id"
157 ],
158 "properties": [
159 "user",
160 "request_id"
161 ],
162 "source": "src/auth/login/service.rs:227",
163 "atomicity": "post-commit"
164 },
165 {
166 "project": "kenoauth",
167 "topic": "oauth-password",
168 "area": "password",
169 "tag": "request-reset",
170 "kind": "auth.password.create",
171 "payload": {
172 "expires_in_secs": "u64"
173 },
174 "labels": [
175 "user_id",
176 "request_id"
177 ],
178 "properties": [
179 "user",
180 "request_id"
181 ],
182 "source": "src/auth/password_reset/service.rs:127",
183 "atomicity": "post-commit"
184 },
185 {
186 "project": "kenoauth",
187 "topic": "oauth-password",
188 "area": "password",
189 "tag": "reset-password",
190 "kind": "auth.password.update",
191 "payload": {
192 "user_id": "i64"
193 },
194 "labels": [
195 "user_id",
196 "request_id"
197 ],
198 "properties": [
199 "user",
200 "request_id"
201 ],
202 "source": "src/auth/password_reset/service.rs:249",
203 "atomicity": "post-commit"
204 },
205 {
206 "project": "kenoauth",
207 "topic": "oauth-sshkey",
208 "area": "sshkey",
209 "tag": "add-sshkey",
210 "kind": "auth.ssh_key.create",
211 "payload": {
212 "id": "uuid",
213 "name": "string",
214 "description": "string|null",
215 "algorithm": "enum",
216 "fingerprint": "string",
217 "public_key": "string",
218 "created_at": "datetime",
219 "expires_at": "datetime|null",
220 "last_used_at": "datetime|null",
221 "revoked_at": "datetime|null"
222 },
223 "labels": [
224 "user_id",
225 "request_id"
226 ],
227 "properties": [
228 "user",
229 "request_id"
230 ],
231 "source": "src/auth/ssh_key/service.rs:174",
232 "atomicity": "post-commit"
233 },
234 {
235 "project": "kenoauth",
236 "topic": "oauth-sshkey",
237 "area": "sshkey",
238 "tag": "delete-sshkey",
239 "kind": "auth.ssh_key.delete",
240 "payload": {
241 "user_id": "i64",
242 "ssh_key_id": "uuid"
243 },
244 "labels": [
245 "user_id",
246 "request_id"
247 ],
248 "properties": [
249 "user",
250 "request_id"
251 ],
252 "source": "src/auth/ssh_key/service.rs:279",
253 "atomicity": "post-commit"
254 },
255 {
256 "project": "kenoauth",
257 "topic": "oauth-accesskey",
258 "area": "accesskey",
259 "tag": "create-accesskey",
260 "kind": "auth.access_key.create",
261 "payload": {
262 "id": "uuid",
263 "name": "string",
264 "scope": "string",
265 "created_at": "datetime",
266 "expires_at": "datetime|null"
267 },
268 "labels": [
269 "user_id",
270 "request_id"
271 ],
272 "properties": [
273 "user",
274 "request_id"
275 ],
276 "source": "src/auth/access_token/service.rs:211",
277 "atomicity": "post-commit"
278 },
279 {
280 "project": "kenoauth",
281 "topic": "oauth-accesskey",
282 "area": "accesskey",
283 "tag": "rotate-accesskey",
284 "kind": "auth.access_key.create",
285 "payload": {
286 "id": "uuid",
287 "rotated": "bool"
288 },
289 "labels": [
290 "user_id",
291 "request_id"
292 ],
293 "properties": [
294 "user",
295 "request_id"
296 ],
297 "source": "src/auth/access_token/service.rs:301",
298 "atomicity": "post-commit"
299 },
300 {
301 "project": "kenoauth",
302 "topic": "oauth-accesskey",
303 "area": "accesskey",
304 "tag": "delete-accesskey",
305 "kind": "auth.access_key.delete",
306 "payload": {
307 "user_id": "i64",
308 "token_id": "uuid"
309 },
310 "labels": [
311 "user_id",
312 "request_id"
313 ],
314 "properties": [
315 "user",
316 "request_id"
317 ],
318 "source": "src/auth/access_token/service.rs:395",
319 "atomicity": "post-commit"
320 },
321 {
322 "project": "kenoauth",
323 "topic": "oauth-accesskey",
324 "area": "accesskey",
325 "tag": "use-accesskey",
326 "kind": "auth.access_key.used",
327 "payload": {
328 "token_id": "uuid",
329 "user_id": "i64",
330 "username": "string",
331 "scope": "string"
332 },
333 "labels": [
334 "user_id",
335 "request_id"
336 ],
337 "properties": [
338 "user",
339 "request_id"
340 ],
341 "source": "src/auth/access_token/service.rs:471",
342 "atomicity": "post-commit"
343 },
344 {
345 "project": "kenoauth",
346 "topic": "oauth-token",
347 "area": "token",
348 "tag": "issue-token",
349 "kind": "auth.app_token.create",
350 "payload": {
351 "app_id": "string",
352 "audience": "string",
353 "key_id": "string",
354 "issued_at": "string",
355 "expires_at": "string"
356 },
357 "labels": [
358 "user_id"
359 ],
360 "properties": [
361 "user"
362 ],
363 "source": "src/auth/app_token/service.rs:45",
364 "atomicity": "post-commit"
365 },
366 {
367 "project": "kenoauth",
368 "topic": "oauth-token",
369 "area": "token",
370 "tag": "issue-token",
371 "kind": "auth.app_token.create",
372 "payload": {
373 "audience": "string",
374 "kid": "string",
375 "issued_at": "string",
376 "expires_at": "string"
377 },
378 "labels": [
379 "user_id"
380 ],
381 "properties": [
382 "user"
383 ],
384 "source": "src/auth/token/service.rs:50",
385 "atomicity": "post-commit"
386 },
387 {
388 "project": "kenoauth",
389 "topic": "oauth-challenge",
390 "area": "challenge",
391 "tag": "challenge",
392 "kind": "auth.challenge.create",
393 "payload": {
394 "context": "string",
395 "difficulty": "u32",
396 "issued_at": "u64",
397 "expires_at": "u64"
398 },
399 "labels": [
400 "user_id (optional)",
401 "request_id"
402 ],
403 "properties": [
404 "user",
405 "request_id"
406 ],
407 "source": "src/auth/challenge/service.rs:17",
408 "atomicity": "post-commit"
409 },
410 {
411 "project": "kenoauth",
412 "topic": "oauth-challenge",
413 "area": "challenge",
414 "tag": "verify-challenge",
415 "kind": "auth.challenge.used",
416 "payload": {
417 "user_id": "i64|null"
418 },
419 "labels": [
420 "user_id (optional)",
421 "request_id"
422 ],
423 "properties": [
424 "user",
425 "request_id"
426 ],
427 "source": "src/auth/challenge/service.rs:40",
428 "atomicity": "post-commit"
429 },
430 {
431 "project": "kenoauth",
432 "topic": "oauth-email",
433 "area": "email",
434 "tag": "delivery",
435 "kind": "email.delivery.create",
436 "payload": {
437 "request_id": "uuid",
438 "recipient_count": "usize",
439 "smtp_message_id": "string",
440 "sent_at_ms": "i64",
441 "metadata": "map<string,string>"
442 },
443 "labels": [
444 "request_id"
445 ],
446 "properties": [
447 "request_id"
448 ],
449 "source": "src/email/service.rs:270,381,398",
450 "atomicity": "post-commit"
451 },
452 {
453 "project": "kenoauth",
454 "topic": "oauth-email",
455 "area": "email",
456 "tag": "failure",
457 "kind": "email.failure.create",
458 "payload": {
459 "request_id": "uuid",
460 "error": "string",
461 "recipient_count": "usize",
462 "failed_at_ms": "i64",
463 "metadata": "map<string,string>"
464 },
465 "labels": [
466 "request_id"
467 ],
468 "properties": [
469 "request_id"
470 ],
471 "source": "src/email/service.rs:295,433",
472 "atomicity": "post-commit"
473 },
474 {
475 "project": "kenoauth",
476 "topic": "oauth-org",
477 "area": "org",
478 "tag": "create",
479 "kind": "organization.life_cycle.create",
480 "payload": {
481 "id": "uuid",
482 "slug": "string",
483 "name": "string",
484 "request_id": "string|null"
485 },
486 "labels": [
487 "organization_id",
488 "actor_user_id"
489 ],
490 "properties": [
491 "user",
492 "org",
493 "request_id"
494 ],
495 "source": "src/auth/organization/service.rs:225",
496 "atomicity": "transactional"
497 },
498 {
499 "project": "kenoauth",
500 "topic": "oauth-org",
501 "area": "org",
502 "tag": "update",
503 "kind": "organization.life_cycle.update",
504 "payload": {
505 "id": "uuid",
506 "name": "string",
507 "request_id": "string|null"
508 },
509 "labels": [
510 "organization_id",
511 "actor_user_id"
512 ],
513 "properties": [
514 "user",
515 "org",
516 "request_id"
517 ],
518 "source": "src/auth/organization/service.rs:447",
519 "atomicity": "transactional"
520 },
521 {
522 "project": "kenoauth",
523 "topic": "oauth-org",
524 "area": "org",
525 "tag": "archive",
526 "kind": "organization.life_cycle.update",
527 "payload": {
528 "id": "uuid",
529 "action": "archive",
530 "request_id": "string|null"
531 },
532 "labels": [
533 "organization_id",
534 "actor_user_id"
535 ],
536 "properties": [
537 "user",
538 "org",
539 "request_id"
540 ],
541 "source": "src/auth/organization/service.rs:498",
542 "atomicity": "transactional"
543 },
544 {
545 "project": "kenoauth",
546 "topic": "oauth-org",
547 "area": "org",
548 "tag": "restore",
549 "kind": "organization.life_cycle.update",
550 "payload": {
551 "id": "uuid",
552 "action": "restore",
553 "request_id": "string|null"
554 },
555 "labels": [
556 "organization_id",
557 "actor_user_id"
558 ],
559 "properties": [
560 "user",
561 "org",
562 "request_id"
563 ],
564 "source": "src/auth/organization/service.rs:558",
565 "atomicity": "transactional"
566 },
567 {
568 "project": "kenoauth",
569 "topic": "oauth-org",
570 "area": "org",
571 "tag": "delete",
572 "kind": "organization.life_cycle.delete",
573 "payload": {
574 "id": "uuid",
575 "force": "bool",
576 "reason": "string|null",
577 "request_id": "string|null"
578 },
579 "labels": [
580 "organization_id",
581 "actor_user_id"
582 ],
583 "properties": [
584 "user",
585 "org",
586 "request_id"
587 ],
588 "source": "src/auth/organization/service.rs:681",
589 "atomicity": "transactional"
590 },
591 {
592 "project": "kenoauth",
593 "topic": "oauth-org",
594 "area": "org",
595 "tag": "transfer-owner",
596 "kind": "organization.member.update",
597 "payload": {
598 "id": "uuid",
599 "action": "transfer_ownership",
600 "previous_owner": "i64",
601 "new_owner": "i64",
602 "request_id": "string|null"
603 },
604 "labels": [
605 "organization_id",
606 "actor_user_id"
607 ],
608 "properties": [
609 "user",
610 "org",
611 "request_id"
612 ],
613 "source": "src/auth/organization/service.rs:760",
614 "atomicity": "transactional"
615 },
616 {
617 "project": "kenoauth",
618 "topic": "oauth-org",
619 "area": "org",
620 "tag": "invite",
621 "kind": "organization.invite.create",
622 "payload": {
623 "id": "uuid",
624 "method": "invite_link|invite_targeted|invite_email|direct",
625 "target_user_id": "i64|null",
626 "max_uses": "u32|null",
627 "request_id": "string|null"
628 },
629 "labels": [
630 "organization_id",
631 "actor_user_id"
632 ],
633 "properties": [
634 "user",
635 "org",
636 "request_id"
637 ],
638 "source": "src/auth/organization/invite.rs:157",
639 "atomicity": "transactional"
640 },
641 {
642 "project": "kenoauth",
643 "topic": "oauth-org",
644 "area": "org",
645 "tag": "revoke-invite",
646 "kind": "organization.invite.create",
647 "payload": {
648 "id": "uuid",
649 "action": "revoke",
650 "reason": "string|null",
651 "version": "i64",
652 "request_id": "string|null"
653 },
654 "labels": [
655 "organization_id",
656 "actor_user_id"
657 ],
658 "properties": [
659 "user",
660 "org",
661 "request_id"
662 ],
663 "source": "src/auth/organization/invite.rs:242",
664 "atomicity": "transactional"
665 },
666 {
667 "project": "kenoauth",
668 "topic": "oauth-org",
669 "area": "org",
670 "tag": "redeem-invite",
671 "kind": "organization.invite.create",
672 "payload": {
673 "id": "uuid",
674 "action": "redeem",
675 "user_id": "i64",
676 "joined": "bool",
677 "version": "i64",
678 "request_id": "string|null"
679 },
680 "labels": [
681 "organization_id",
682 "actor_user_id"
683 ],
684 "properties": [
685 "user",
686 "org",
687 "request_id"
688 ],
689 "source": "src/auth/organization/invite.rs:403",
690 "atomicity": "transactional"
691 },
692 {
693 "project": "kenoauth",
694 "topic": "oauth-org",
695 "area": "org",
696 "tag": "add-member",
697 "kind": "organization.member.update",
698 "payload": {
699 "id": "uuid",
700 "user_id": "i64",
701 "action": "add",
702 "created": "bool",
703 "request_id": "string|null"
704 },
705 "labels": [
706 "organization_id",
707 "actor_user_id"
708 ],
709 "properties": [
710 "user",
711 "org",
712 "request_id"
713 ],
714 "source": "src/auth/organization/member.rs:208",
715 "atomicity": "transactional"
716 },
717 {
718 "project": "kenoauth",
719 "topic": "oauth-org",
720 "area": "org",
721 "tag": "remove-member",
722 "kind": "organization.member.update",
723 "payload": {
724 "id": "uuid",
725 "user_id": "i64",
726 "action": "remove",
727 "version": "i64",
728 "request_id": "string|null"
729 },
730 "labels": [
731 "organization_id",
732 "actor_user_id"
733 ],
734 "properties": [
735 "user",
736 "org",
737 "request_id"
738 ],
739 "source": "src/auth/organization/member.rs:280",
740 "atomicity": "transactional"
741 },
742 {
743 "project": "kenoauth",
744 "topic": "oauth-org",
745 "area": "org",
746 "tag": "update-member",
747 "kind": "organization.member.update",
748 "payload": {
749 "id": "uuid",
750 "user_id": "i64",
751 "action": "leave",
752 "version": "i64",
753 "request_id": "string|null"
754 },
755 "labels": [
756 "organization_id",
757 "actor_user_id"
758 ],
759 "properties": [
760 "user",
761 "org",
762 "request_id"
763 ],
764 "source": "src/auth/organization/member.rs:336",
765 "remark": "action=leave 未在 route 中映射,实际 tag 落到默认值 update-member",
766 "atomicity": "transactional"
767 },
768 {
769 "project": "kenoauth",
770 "topic": "oauth-org",
771 "area": "org",
772 "tag": "set-role",
773 "kind": "organization.member.update",
774 "payload": {
775 "id": "uuid",
776 "user_id": "i64",
777 "action": "set_role",
778 "role": "string",
779 "request_id": "string|null"
780 },
781 "labels": [
782 "organization_id",
783 "actor_user_id"
784 ],
785 "properties": [
786 "user",
787 "org",
788 "request_id"
789 ],
790 "source": "src/auth/organization/member.rs:413",
791 "atomicity": "transactional"
792 },
793 {
794 "project": "kenoauth",
795 "topic": "oauth-org",
796 "area": "org",
797 "tag": "ban",
798 "kind": "organization.member.update",
799 "payload": {
800 "id": "uuid",
801 "user_id": "i64",
802 "action": "ban",
803 "reason": "string|null",
804 "request_id": "string|null"
805 },
806 "labels": [
807 "organization_id",
808 "actor_user_id"
809 ],
810 "properties": [
811 "user",
812 "org",
813 "request_id"
814 ],
815 "source": "src/auth/organization/member.rs:484",
816 "atomicity": "transactional"
817 },
818 {
819 "project": "kenoauth",
820 "topic": "oauth-org",
821 "area": "org",
822 "tag": "unban",
823 "kind": "organization.member.update",
824 "payload": {
825 "id": "uuid",
826 "user_id": "i64",
827 "action": "unban",
828 "request_id": "string|null"
829 },
830 "labels": [
831 "organization_id",
832 "actor_user_id"
833 ],
834 "properties": [
835 "user",
836 "org",
837 "request_id"
838 ],
839 "source": "src/auth/organization/member.rs:568",
840 "atomicity": "transactional"
841 },
842 {
843 "project": "kenoauth",
844 "topic": "oauth-org",
845 "area": "org",
846 "tag": "update-member",
847 "kind": "organization.member.update",
848 "payload": {
849 "id": "uuid",
850 "user_id": "i64",
851 "action": "update|self_update",
852 "alias_name": "string|null",
853 "alias_avatar_url": "string|null",
854 "request_id": "string|null"
855 },
856 "labels": [
857 "organization_id",
858 "actor_user_id"
859 ],
860 "properties": [
861 "user",
862 "org",
863 "request_id"
864 ],
865 "source": "src/auth/organization/member.rs:764",
866 "atomicity": "transactional"
867 },
868 {
869 "project": "kenoauth",
870 "topic": "oauth-org",
871 "area": "org",
872 "tag": "update-ban",
873 "kind": "organization.member.update",
874 "payload": {
875 "id": "uuid",
876 "user_id": "i64",
877 "action": "update_ban",
878 "expires_at": "datetime|null",
879 "request_id": "string|null"
880 },
881 "labels": [
882 "organization_id",
883 "actor_user_id"
884 ],
885 "properties": [
886 "user",
887 "org",
888 "request_id"
889 ],
890 "source": "src/auth/organization/member.rs:889",
891 "atomicity": "transactional"
892 },
893 {
894 "project": "kenoauth",
895 "topic": "oauth-org",
896 "area": "org",
897 "tag": "join",
898 "kind": "organization.member.update",
899 "payload": {
900 "id": "uuid",
901 "user_id": "i64",
902 "action": "join_role",
903 "role_id": "uuid",
904 "request_id": "string|null"
905 },
906 "labels": [
907 "organization_id",
908 "actor_user_id"
909 ],
910 "properties": [
911 "user",
912 "org",
913 "request_id"
914 ],
915 "source": "src/auth/organization/role.rs:801",
916 "atomicity": "transactional"
917 },
918 {
919 "project": "kenoauth",
920 "topic": "oauth-org",
921 "area": "org",
922 "tag": "leave",
923 "kind": "organization.member.update",
924 "payload": {
925 "id": "uuid",
926 "user_id": "i64",
927 "action": "leave_role",
928 "role_id": "uuid",
929 "request_id": "string|null"
930 },
931 "labels": [
932 "organization_id",
933 "actor_user_id"
934 ],
935 "properties": [
936 "user",
937 "org",
938 "request_id"
939 ],
940 "source": "src/auth/organization/role.rs:844",
941 "atomicity": "transactional"
942 },
943 {
944 "project": "kenoauth",
945 "topic": "oauth-org",
946 "area": "org",
947 "tag": "create-role",
948 "kind": "organization.role.update",
949 "payload": {
950 "id": "uuid",
951 "key": "string",
952 "action": "create",
953 "request_id": "string|null"
954 },
955 "labels": [
956 "organization_id",
957 "actor_user_id"
958 ],
959 "properties": [
960 "user",
961 "org",
962 "request_id"
963 ],
964 "source": "src/auth/organization/role.rs:306",
965 "atomicity": "transactional"
966 },
967 {
968 "project": "kenoauth",
969 "topic": "oauth-org",
970 "area": "org",
971 "tag": "set-permissions",
972 "kind": "organization.role.update",
973 "payload": {
974 "id": "uuid",
975 "key": "string",
976 "action": "set_permissions",
977 "permissions": "string[]",
978 "request_id": "string|null"
979 },
980 "labels": [
981 "organization_id",
982 "actor_user_id"
983 ],
984 "properties": [
985 "user",
986 "org",
987 "request_id"
988 ],
989 "source": "src/auth/organization/role.rs:367",
990 "atomicity": "transactional"
991 },
992 {
993 "project": "kenoauth",
994 "topic": "oauth-org",
995 "area": "org",
996 "tag": "update-role",
997 "kind": "organization.role.update",
998 "payload": {
999 "id": "uuid",
1000 "key": "string",
1001 "action": "update",
1002 "request_id": "string|null"
1003 },
1004 "labels": [
1005 "organization_id",
1006 "actor_user_id"
1007 ],
1008 "properties": [
1009 "user",
1010 "org",
1011 "request_id"
1012 ],
1013 "source": "src/auth/organization/role.rs:458",
1014 "atomicity": "transactional"
1015 },
1016 {
1017 "project": "kenoauth",
1018 "topic": "oauth-org",
1019 "area": "org",
1020 "tag": "delete-role",
1021 "kind": "organization.role.update",
1022 "payload": {
1023 "id": "uuid",
1024 "key": "string",
1025 "action": "delete",
1026 "version": "i64",
1027 "request_id": "string|null"
1028 },
1029 "labels": [
1030 "organization_id",
1031 "actor_user_id"
1032 ],
1033 "properties": [
1034 "user",
1035 "org",
1036 "request_id"
1037 ],
1038 "source": "src/auth/organization/role.rs:511",
1039 "atomicity": "transactional"
1040 },
1041 {
1042 "project": "kenoauth",
1043 "topic": "oauth-org",
1044 "area": "org",
1045 "tag": "assign-role",
1046 "kind": "organization.role.update",
1047 "payload": {
1048 "member_id": "uuid",
1049 "role_key": "string",
1050 "action": "assign",
1051 "version": "i64",
1052 "request_id": "string|null"
1053 },
1054 "labels": [
1055 "organization_id",
1056 "actor_user_id"
1057 ],
1058 "properties": [
1059 "user",
1060 "org",
1061 "request_id"
1062 ],
1063 "source": "src/auth/organization/role.rs:574",
1064 "atomicity": "transactional"
1065 },
1066 {
1067 "project": "kenoauth",
1068 "topic": "oauth-org",
1069 "area": "org",
1070 "tag": "unassign-role",
1071 "kind": "organization.role.update",
1072 "payload": {
1073 "member_id": "uuid",
1074 "role_key": "string",
1075 "action": "unassign",
1076 "version": "i64",
1077 "request_id": "string|null"
1078 },
1079 "labels": [
1080 "organization_id",
1081 "actor_user_id"
1082 ],
1083 "properties": [
1084 "user",
1085 "org",
1086 "request_id"
1087 ],
1088 "source": "src/auth/organization/role.rs:634",
1089 "atomicity": "transactional"
1090 },
1091 {
1092 "project": "kenoauth",
1093 "topic": "oauth-org",
1094 "area": "org",
1095 "tag": "grant",
1096 "kind": "organization.role.update",
1097 "payload": {
1098 "member_id": "uuid",
1099 "granted": "usize",
1100 "version": "i64",
1101 "request_id": "string|null"
1102 },
1103 "labels": [
1104 "organization_id",
1105 "actor_user_id"
1106 ],
1107 "properties": [
1108 "user",
1109 "org",
1110 "request_id"
1111 ],
1112 "source": "src/auth/organization/role.rs:704",
1113 "remark": "成员直挂权限整表替换;payload 无 action 字段,命中 route 默认值 grant",
1114 "atomicity": "transactional"
1115 },
1116 {
1117 "project": "kenshell",
1118 "topic": "kenshell-repo",
1119 "area": "repo",
1120 "tag": "create",
1121 "kind": "repo.repository.create",
1122 "ordering_key": "repo:{repo_id}",
1123 "labels": [
1124 "repo_id",
1125 "repo_name",
1126 "user_id",
1127 "organization_id",
1128 "request_id"
1129 ],
1130 "properties": [
1131 "repo_id",
1132 "repo_name",
1133 "user",
1134 "org",
1135 "request_id",
1136 "timestamp"
1137 ],
1138 "source": "src/svc/repo/usecase/create.rs:120"
1139 },
1140 {
1141 "project": "kenshell",
1142 "topic": "kenshell-repo",
1143 "area": "repo",
1144 "tag": "update",
1145 "kind": "repo.repository.update",
1146 "ordering_key": "repo:{repo_id}",
1147 "labels": [
1148 "repo_id",
1149 "repo_name",
1150 "user_id",
1151 "organization_id",
1152 "request_id"
1153 ],
1154 "properties": [
1155 "repo_id",
1156 "repo_name",
1157 "user",
1158 "org",
1159 "request_id",
1160 "timestamp"
1161 ],
1162 "source": "src/svc/commands.rs:428"
1163 },
1164 {
1165 "project": "kenshell",
1166 "topic": "kenshell-repo",
1167 "area": "repo",
1168 "tag": "delete",
1169 "kind": "repo.repository.delete",
1170 "ordering_key": "repo:{repo_id}",
1171 "labels": [
1172 "repo_id",
1173 "repo_name",
1174 "user_id",
1175 "organization_id",
1176 "request_id"
1177 ],
1178 "properties": [
1179 "repo_id",
1180 "repo_name",
1181 "user",
1182 "org",
1183 "request_id",
1184 "timestamp"
1185 ],
1186 "source": "src/svc/repo/usecase/delete.rs:89"
1187 },
1188 {
1189 "project": "kenshell",
1190 "topic": "kenshell-repo",
1191 "area": "repo",
1192 "tag": "placement-change",
1193 "kind": "repo.placement.update",
1194 "ordering_key": "repo:{repo_id}",
1195 "labels": [
1196 "repo_id",
1197 "repo_name",
1198 "user_id",
1199 "organization_id",
1200 "request_id"
1201 ],
1202 "properties": [
1203 "repo_id",
1204 "repo_name",
1205 "user",
1206 "org",
1207 "request_id",
1208 "timestamp"
1209 ],
1210 "source": "src/svc/repo/usecase/replicate.rs:167, serve.rs:170, serve.rs:221"
1211 },
1212 {
1213 "project": "kenshell",
1214 "topic": "kenshell-repo",
1215 "area": "repo",
1216 "tag": "language-update",
1217 "kind": "repo.language.update",
1218 "ordering_key": "repo:{repo_id}",
1219 "labels": [
1220 "repo_id",
1221 "repo_name",
1222 "user_id",
1223 "organization_id",
1224 "request_id"
1225 ],
1226 "properties": [
1227 "repo_id",
1228 "repo_name",
1229 "user",
1230 "org",
1231 "request_id",
1232 "timestamp"
1233 ],
1234 "source": "src/svc/git/analysis.rs:155"
1235 },
1236 {
1237 "project": "kenshell",
1238 "topic": "kenshell-ref",
1239 "area": "ref",
1240 "tag": "create-branch",
1241 "kind": "ref.branch.create",
1242 "ordering_key": "repo:{repo_id}",
1243 "labels": [
1244 "repo_id",
1245 "repo_name",
1246 "user_id",
1247 "organization_id",
1248 "request_id"
1249 ],
1250 "properties": [
1251 "repo_id",
1252 "repo_name",
1253 "user",
1254 "org",
1255 "request_id",
1256 "timestamp"
1257 ],
1258 "source": "src/svc/commands.rs:142"
1259 },
1260 {
1261 "project": "kenshell",
1262 "topic": "kenshell-ref",
1263 "area": "ref",
1264 "tag": "update-branch",
1265 "kind": "ref.branch.update",
1266 "ordering_key": "repo:{repo_id}",
1267 "labels": [
1268 "repo_id",
1269 "repo_name",
1270 "user_id",
1271 "organization_id",
1272 "request_id"
1273 ],
1274 "properties": [
1275 "repo_id",
1276 "repo_name",
1277 "user",
1278 "org",
1279 "request_id",
1280 "timestamp"
1281 ],
1282 "source": "src/svc/commands.rs:184"
1283 },
1284 {
1285 "project": "kenshell",
1286 "topic": "kenshell-ref",
1287 "area": "ref",
1288 "tag": "delete-branch",
1289 "kind": "ref.branch.delete",
1290 "ordering_key": "repo:{repo_id}",
1291 "labels": [
1292 "repo_id",
1293 "repo_name",
1294 "user_id",
1295 "organization_id",
1296 "request_id"
1297 ],
1298 "properties": [
1299 "repo_id",
1300 "repo_name",
1301 "user",
1302 "org",
1303 "request_id",
1304 "timestamp"
1305 ],
1306 "source": "src/svc/commands.rs:226"
1307 },
1308 {
1309 "project": "kenshell",
1310 "topic": "kenshell-ref",
1311 "area": "ref",
1312 "tag": "rename-branch",
1313 "kind": "ref.branch.rename",
1314 "ordering_key": "repo:{repo_id}",
1315 "labels": [
1316 "repo_id",
1317 "repo_name",
1318 "user_id",
1319 "organization_id",
1320 "request_id"
1321 ],
1322 "properties": [
1323 "repo_id",
1324 "repo_name",
1325 "user",
1326 "org",
1327 "request_id",
1328 "timestamp"
1329 ],
1330 "source": "src/svc/commands.rs:379"
1331 },
1332 {
1333 "project": "kenshell",
1334 "topic": "kenshell-ref",
1335 "area": "ref",
1336 "tag": "set-default-branch",
1337 "kind": "ref.default_branch.update",
1338 "ordering_key": "repo:{repo_id}",
1339 "labels": [
1340 "repo_id",
1341 "repo_name",
1342 "user_id",
1343 "organization_id",
1344 "request_id"
1345 ],
1346 "properties": [
1347 "repo_id",
1348 "repo_name",
1349 "user",
1350 "org",
1351 "request_id",
1352 "timestamp"
1353 ],
1354 "source": "src/svc/commands.rs:421"
1355 },
1356 {
1357 "project": "kenshell",
1358 "topic": "kenshell-ref",
1359 "area": "ref",
1360 "tag": "create-tag",
1361 "kind": "ref.tag.create",
1362 "ordering_key": "repo:{repo_id}",
1363 "labels": [
1364 "repo_id",
1365 "repo_name",
1366 "user_id",
1367 "organization_id",
1368 "request_id"
1369 ],
1370 "properties": [
1371 "repo_id",
1372 "repo_name",
1373 "user",
1374 "org",
1375 "request_id",
1376 "timestamp"
1377 ],
1378 "source": "src/svc/commands.rs:548"
1379 },
1380 {
1381 "project": "kenshell",
1382 "topic": "kenshell-ref",
1383 "area": "ref",
1384 "tag": "delete-tag",
1385 "kind": "ref.tag.delete",
1386 "ordering_key": "repo:{repo_id}",
1387 "labels": [
1388 "repo_id",
1389 "repo_name",
1390 "user_id",
1391 "organization_id",
1392 "request_id"
1393 ],
1394 "properties": [
1395 "repo_id",
1396 "repo_name",
1397 "user",
1398 "org",
1399 "request_id",
1400 "timestamp"
1401 ],
1402 "source": "src/svc/commands.rs:332"
1403 },
1404 {
1405 "project": "kenshell",
1406 "topic": "kenshell-ref",
1407 "area": "ref",
1408 "tag": "create-protection",
1409 "kind": "ref.protection.create",
1410 "ordering_key": "repo:{repo_id}",
1411 "labels": [
1412 "repo_id",
1413 "repo_name",
1414 "user_id",
1415 "organization_id",
1416 "request_id"
1417 ],
1418 "properties": [
1419 "repo_id",
1420 "repo_name",
1421 "user",
1422 "org",
1423 "request_id",
1424 "timestamp"
1425 ],
1426 "source": "src/svc/git/protection.rs:78"
1427 },
1428 {
1429 "project": "kenshell",
1430 "topic": "kenshell-ref",
1431 "area": "ref",
1432 "tag": "delete-protection",
1433 "kind": "ref.protection.delete",
1434 "ordering_key": "repo:{repo_id}",
1435 "labels": [
1436 "repo_id",
1437 "repo_name",
1438 "user_id",
1439 "organization_id",
1440 "request_id"
1441 ],
1442 "properties": [
1443 "repo_id",
1444 "repo_name",
1445 "user",
1446 "org",
1447 "request_id",
1448 "timestamp"
1449 ],
1450 "source": "src/svc/git/protection.rs:113"
1451 },
1452 {
1453 "project": "kenshell",
1454 "topic": "kenshell-commit",
1455 "area": "commit",
1456 "tag": "sync",
1457 "kind": "commit.commit.sync",
1458 "ordering_key": "repo:{repo_id}",
1459 "labels": [
1460 "repo_id",
1461 "repo_name",
1462 "user_id",
1463 "organization_id",
1464 "request_id"
1465 ],
1466 "properties": [
1467 "repo_id",
1468 "repo_name",
1469 "user",
1470 "org",
1471 "request_id",
1472 "timestamp"
1473 ],
1474 "source": "src/svc/git/metadata.rs:248, src/svc/git/commit.rs:331"
1475 },
1476 {
1477 "project": "kenshell",
1478 "topic": "kenshell-commit",
1479 "area": "commit",
1480 "tag": "set-status",
1481 "kind": "commit.status.set",
1482 "ordering_key": "repo:{repo_id}",
1483 "labels": [
1484 "repo_id",
1485 "repo_name",
1486 "user_id",
1487 "organization_id",
1488 "request_id"
1489 ],
1490 "properties": [
1491 "repo_id",
1492 "repo_name",
1493 "user",
1494 "org",
1495 "request_id",
1496 "timestamp"
1497 ],
1498 "source": "src/svc/git/commit.rs:247"
1499 },
1500 {
1501 "project": "kenshell",
1502 "topic": "kenshell-commit",
1503 "area": "commit",
1504 "tag": "comment-create",
1505 "kind": "commit.code_comment.create",
1506 "ordering_key": "repo:{repo_id}:comment:{comment_id}",
1507 "labels": [
1508 "repo_id",
1509 "repo_name",
1510 "user_id",
1511 "organization_id",
1512 "request_id"
1513 ],
1514 "properties": [
1515 "repo_id",
1516 "repo_name",
1517 "user",
1518 "org",
1519 "request_id",
1520 "timestamp"
1521 ],
1522 "source": "src/svc/comment.rs:87"
1523 },
1524 {
1525 "project": "kenshell",
1526 "topic": "kenshell-commit",
1527 "area": "commit",
1528 "tag": "comment-update",
1529 "kind": "commit.code_comment.update",
1530 "ordering_key": "repo:{repo_id}:comment:{comment_id}",
1531 "labels": [
1532 "repo_id",
1533 "repo_name",
1534 "user_id",
1535 "organization_id",
1536 "request_id"
1537 ],
1538 "properties": [
1539 "repo_id",
1540 "repo_name",
1541 "user",
1542 "org",
1543 "request_id",
1544 "timestamp"
1545 ],
1546 "source": "src/svc/comment.rs:123"
1547 },
1548 {
1549 "project": "kenshell",
1550 "topic": "kenshell-commit",
1551 "area": "commit",
1552 "tag": "comment-delete",
1553 "kind": "commit.code_comment.delete",
1554 "ordering_key": "repo:{repo_id}:comment:{comment_id}",
1555 "labels": [
1556 "repo_id",
1557 "repo_name",
1558 "user_id",
1559 "organization_id",
1560 "request_id"
1561 ],
1562 "properties": [
1563 "repo_id",
1564 "repo_name",
1565 "user",
1566 "org",
1567 "request_id",
1568 "timestamp"
1569 ],
1570 "source": "src/svc/comment.rs:156"
1571 },
1572 {
1573 "project": "kenshell",
1574 "topic": "kenshell-lfs",
1575 "area": "lfs",
1576 "tag": "upload",
1577 "kind": "lfs.object.upload",
1578 "ordering_key": "repo:{repo_id}",
1579 "labels": [
1580 "repo_id",
1581 "repo_name",
1582 "user_id",
1583 "organization_id",
1584 "request_id"
1585 ],
1586 "properties": [
1587 "repo_id",
1588 "repo_name",
1589 "user",
1590 "org",
1591 "request_id",
1592 "timestamp"
1593 ],
1594 "source": "src/svc/lfs/usecase/upload.rs:67,155"
1595 },
1596 {
1597 "project": "kenshell",
1598 "topic": "kenshell-lfs",
1599 "area": "lfs",
1600 "tag": "unreference",
1601 "kind": "lfs.object.unreference",
1602 "ordering_key": "repo:{repo_id}",
1603 "labels": [
1604 "repo_id",
1605 "repo_name",
1606 "user_id",
1607 "organization_id",
1608 "request_id"
1609 ],
1610 "properties": [
1611 "repo_id",
1612 "repo_name",
1613 "user",
1614 "org",
1615 "request_id",
1616 "timestamp"
1617 ],
1618 "source": "src/svc/lfs/usecase/collect.rs:59"
1619 },
1620 {
1621 "project": "kenshell",
1622 "topic": "kenshell-lfs",
1623 "area": "lfs",
1624 "tag": "purge",
1625 "kind": "lfs.object.purge",
1626 "ordering_key": "repo:{repo_id}",
1627 "labels": [
1628 "repo_id",
1629 "repo_name",
1630 "user_id",
1631 "organization_id",
1632 "request_id"
1633 ],
1634 "properties": [
1635 "repo_id",
1636 "repo_name",
1637 "user",
1638 "org",
1639 "request_id",
1640 "timestamp"
1641 ],
1642 "source": "src/svc/lfs/usecase/collect.rs:75"
1643 },
1644 {
1645 "project": "kenshell",
1646 "topic": "kenshell-archive",
1647 "area": "archive",
1648 "tag": "create",
1649 "kind": "archive.artifact.create",
1650 "ordering_key": "repo:{repo_id}",
1651 "labels": [
1652 "repo_id",
1653 "repo_name",
1654 "user_id",
1655 "organization_id",
1656 "request_id"
1657 ],
1658 "properties": [
1659 "repo_id",
1660 "repo_name",
1661 "user",
1662 "org",
1663 "request_id",
1664 "timestamp"
1665 ],
1666 "source": "src/svc/git/archive.rs:356,418"
1667 },
1668 {
1669 "project": "kenshell",
1670 "topic": "kenshell-job",
1671 "area": "job",
1672 "tag": "enqueue",
1673 "kind": "job.job.enqueue",
1674 "ordering_key": "job:{job_id}",
1675 "labels": [
1676 "repo_id",
1677 "repo_name",
1678 "user_id",
1679 "organization_id",
1680 "request_id"
1681 ],
1682 "properties": [
1683 "repo_id",
1684 "repo_name",
1685 "user",
1686 "org",
1687 "request_id",
1688 "timestamp"
1689 ],
1690 "source": "src/svc/jobs.rs:190"
1691 },
1692 {
1693 "project": "kenshell",
1694 "topic": "kenshell-job",
1695 "area": "job",
1696 "tag": "claim",
1697 "kind": "job.job.claim",
1698 "ordering_key": "job:{job_id}",
1699 "labels": [
1700 "repo_id",
1701 "repo_name",
1702 "user_id",
1703 "organization_id",
1704 "request_id"
1705 ],
1706 "properties": [
1707 "repo_id",
1708 "repo_name",
1709 "user",
1710 "org",
1711 "request_id",
1712 "timestamp"
1713 ],
1714 "source": "src/svc/jobs.rs:348"
1715 },
1716 {
1717 "project": "kenshell",
1718 "topic": "kenshell-job",
1719 "area": "job",
1720 "tag": "progress",
1721 "kind": "job.job.progress",
1722 "ordering_key": "job:{job_id}",
1723 "labels": [
1724 "repo_id",
1725 "repo_name",
1726 "user_id",
1727 "organization_id",
1728 "request_id"
1729 ],
1730 "properties": [
1731 "repo_id",
1732 "repo_name",
1733 "user",
1734 "org",
1735 "request_id",
1736 "timestamp"
1737 ],
1738 "source": "src/svc/jobs.rs:376"
1739 },
1740 {
1741 "project": "kenshell",
1742 "topic": "kenshell-job",
1743 "area": "job",
1744 "tag": "heartbeat",
1745 "kind": "job.job.heartbeat",
1746 "ordering_key": "job:{job_id}",
1747 "labels": [
1748 "repo_id",
1749 "repo_name",
1750 "user_id",
1751 "organization_id",
1752 "request_id"
1753 ],
1754 "properties": [
1755 "repo_id",
1756 "repo_name",
1757 "user",
1758 "org",
1759 "request_id",
1760 "timestamp"
1761 ],
1762 "source": "src/svc/jobs.rs:398"
1763 },
1764 {
1765 "project": "kenshell",
1766 "topic": "kenshell-job",
1767 "area": "job",
1768 "tag": "cancel",
1769 "kind": "job.job.cancel",
1770 "ordering_key": "job:{job_id}",
1771 "labels": [
1772 "repo_id",
1773 "repo_name",
1774 "user_id",
1775 "organization_id",
1776 "request_id"
1777 ],
1778 "properties": [
1779 "repo_id",
1780 "repo_name",
1781 "user",
1782 "org",
1783 "request_id",
1784 "timestamp"
1785 ],
1786 "source": "src/svc/jobs.rs:254"
1787 },
1788 {
1789 "project": "kenshell",
1790 "topic": "kenshell-job",
1791 "area": "job",
1792 "tag": "retry",
1793 "kind": "job.job.retry",
1794 "ordering_key": "job:{job_id}",
1795 "labels": [
1796 "repo_id",
1797 "repo_name",
1798 "user_id",
1799 "organization_id",
1800 "request_id"
1801 ],
1802 "properties": [
1803 "repo_id",
1804 "repo_name",
1805 "user",
1806 "org",
1807 "request_id",
1808 "timestamp"
1809 ],
1810 "source": "src/svc/jobs.rs:289"
1811 },
1812 {
1813 "project": "kenshell",
1814 "topic": "kenshell-job",
1815 "area": "job",
1816 "tag": "finish",
1817 "kind": "job.job.finish",
1818 "ordering_key": "job:{job_id} | job:lease-expired",
1819 "labels": [
1820 "repo_id",
1821 "repo_name",
1822 "user_id",
1823 "organization_id",
1824 "request_id"
1825 ],
1826 "properties": [
1827 "repo_id",
1828 "repo_name",
1829 "user",
1830 "org",
1831 "request_id",
1832 "timestamp"
1833 ],
1834 "source": "src/svc/jobs.rs:323,440",
1835 "remark": "job:lease-expired 不是 UUID,无法解析仓库,实际不会有 repo_id/repo_name"
1836 },
1837 {
1838 "project": "kenshell",
1839 "topic": "kenshell-node",
1840 "area": "node",
1841 "tag": "create",
1842 "kind": "node.node.create",
1843 "ordering_key": "node:{node_id}",
1844 "labels": [
1845 "user_id",
1846 "organization_id",
1847 "request_id"
1848 ],
1849 "properties": [
1850 "user",
1851 "org",
1852 "request_id",
1853 "timestamp"
1854 ],
1855 "source": "src/svc/nodes/usecase/create.rs:57"
1856 },
1857 {
1858 "project": "kenshell",
1859 "topic": "kenshell-node",
1860 "area": "node",
1861 "tag": "update",
1862 "kind": "node.node.update",
1863 "ordering_key": "node:{node_id}",
1864 "labels": [
1865 "user_id",
1866 "organization_id",
1867 "request_id"
1868 ],
1869 "properties": [
1870 "user",
1871 "org",
1872 "request_id",
1873 "timestamp"
1874 ],
1875 "source": "src/svc/nodes/usecase/update.rs:93"
1876 },
1877 {
1878 "project": "kenshell",
1879 "topic": "kenshell-node",
1880 "area": "node",
1881 "tag": "delete",
1882 "kind": "node.node.delete",
1883 "ordering_key": "node:{node_id}",
1884 "labels": [
1885 "user_id",
1886 "organization_id",
1887 "request_id"
1888 ],
1889 "properties": [
1890 "user",
1891 "org",
1892 "request_id",
1893 "timestamp"
1894 ],
1895 "source": "src/svc/nodes/usecase/delete.rs:34"
1896 },
1897 {
1898 "project": "kenshell",
1899 "topic": "kenshell-node",
1900 "area": "node",
1901 "tag": "probe",
1902 "kind": "node.node.probe",
1903 "ordering_key": "node:{node_id}",
1904 "labels": [
1905 "user_id",
1906 "organization_id",
1907 "request_id"
1908 ],
1909 "properties": [
1910 "user",
1911 "org",
1912 "request_id",
1913 "timestamp"
1914 ],
1915 "source": "src/svc/nodes/usecase/health.rs:57, src/discovery/reconcile.rs:261,290"
1916 },
1917 {
1918 "project": "kenshell",
1919 "topic": "kenshell-volume",
1920 "area": "volume",
1921 "tag": "create",
1922 "kind": "volume.volume.create",
1923 "ordering_key": "volume:{volume_id}",
1924 "labels": [
1925 "user_id",
1926 "organization_id",
1927 "request_id"
1928 ],
1929 "properties": [
1930 "user",
1931 "org",
1932 "request_id",
1933 "timestamp"
1934 ],
1935 "source": "src/svc/volumes/usecase/create.rs:63"
1936 },
1937 {
1938 "project": "kenshell",
1939 "topic": "kenshell-volume",
1940 "area": "volume",
1941 "tag": "update",
1942 "kind": "volume.volume.update",
1943 "ordering_key": "volume:{volume_id}",
1944 "labels": [
1945 "user_id",
1946 "organization_id",
1947 "request_id"
1948 ],
1949 "properties": [
1950 "user",
1951 "org",
1952 "request_id",
1953 "timestamp"
1954 ],
1955 "source": "src/svc/volumes/usecase/update.rs:83"
1956 },
1957 {
1958 "project": "kenshell",
1959 "topic": "kenshell-volume",
1960 "area": "volume",
1961 "tag": "delete",
1962 "kind": "volume.volume.delete",
1963 "ordering_key": "volume:{volume_id}",
1964 "labels": [
1965 "user_id",
1966 "organization_id",
1967 "request_id"
1968 ],
1969 "properties": [
1970 "user",
1971 "org",
1972 "request_id",
1973 "timestamp"
1974 ],
1975 "source": "src/svc/volumes/usecase/delete.rs:38"
1976 },
1977 {
1978 "project": "kenshell",
1979 "topic": "kenshell-volume",
1980 "area": "volume",
1981 "tag": "mount",
1982 "kind": "volume.mount.create",
1983 "ordering_key": "volume:{volume_id}",
1984 "labels": [
1985 "user_id",
1986 "organization_id",
1987 "request_id"
1988 ],
1989 "properties": [
1990 "user",
1991 "org",
1992 "request_id",
1993 "timestamp"
1994 ],
1995 "source": "src/svc/volumes/usecase/mount.rs:121, src/discovery/reconcile.rs:379"
1996 },
1997 {
1998 "project": "kenshell",
1999 "topic": "kenshell-volume",
2000 "area": "volume",
2001 "tag": "unmount",
2002 "kind": "volume.mount.delete",
2003 "ordering_key": "volume:{volume_id}",
2004 "labels": [
2005 "user_id",
2006 "organization_id",
2007 "request_id"
2008 ],
2009 "properties": [
2010 "user",
2011 "org",
2012 "request_id",
2013 "timestamp"
2014 ],
2015 "source": "src/svc/volumes/usecase/mount.rs:174"
2016 },
2017 {
2018 "project": "kenshell",
2019 "topic": "kenshell-volume",
2020 "area": "volume",
2021 "tag": "probe",
2022 "kind": "volume.volume.probe",
2023 "ordering_key": "volume:{volume_id}",
2024 "labels": [
2025 "user_id",
2026 "organization_id",
2027 "request_id"
2028 ],
2029 "properties": [
2030 "user",
2031 "org",
2032 "request_id",
2033 "timestamp"
2034 ],
2035 "source": "src/svc/volumes/usecase/probe.rs:93"
2036 },
2037 {
2038 "project": "kenapi",
2039 "topic": "gitdata-chat",
2040 "area": "chat",
2041 "tag": "message-create",
2042 "kind": "chat.message.create",
2043 "payload": {
2044 "kind": "message_published",
2045 "room_id": "Uuid",
2046 "seq": "i64",
2047 "message": "MessageDto"
2048 },
2049 "labels": [
2050 "room_id",
2051 "user_id",
2052 "request_id"
2053 ],
2054 "properties": [
2055 "room_id",
2056 "user",
2057 "request_id"
2058 ],
2059 "source": "lib/chat/src/share/notify.rs:313 (callers: lib/chat/src/services/messages.rs:755, lib/chat/src/services/webhooks.rs:413, lib/chat/src/services/rooms.rs:507)",
2060 "atomicity": "post-commit",
2061 "ordering_key": "chat:room:{room_id}"
2062 },
2063 {
2064 "project": "kenapi",
2065 "topic": "gitdata-chat",
2066 "area": "chat",
2067 "tag": "message-update",
2068 "kind": "chat.message.update",
2069 "payload": {
2070 "kind": "message_edited",
2071 "room_id": "Uuid",
2072 "seq": "i64",
2073 "message": "MessageDto"
2074 },
2075 "labels": [
2076 "room_id",
2077 "user_id",
2078 "request_id"
2079 ],
2080 "properties": [
2081 "room_id",
2082 "user",
2083 "request_id"
2084 ],
2085 "source": "lib/chat/src/services/messages.rs:1126, lib/chat/src/services/unfurl.rs:520",
2086 "atomicity": "post-commit",
2087 "ordering_key": "chat:room:{room_id}"
2088 },
2089 {
2090 "project": "kenapi",
2091 "topic": "gitdata-chat",
2092 "area": "chat",
2093 "tag": "message-delete",
2094 "kind": "chat.message.delete",
2095 "payload": {
2096 "kind": "message_deleted",
2097 "room_id": "Uuid",
2098 "seq": "i64",
2099 "message_id": "Uuid"
2100 },
2101 "labels": [
2102 "room_id",
2103 "request_id"
2104 ],
2105 "properties": [
2106 "room_id",
2107 "request_id"
2108 ],
2109 "source": "lib/chat/src/services/messages.rs:1270",
2110 "atomicity": "post-commit",
2111 "ordering_key": "chat:room:{room_id}"
2112 },
2113 {
2114 "project": "kenapi",
2115 "topic": "gitdata-chat",
2116 "area": "chat",
2117 "tag": "messages-delete",
2118 "kind": "chat.messages.delete",
2119 "payload": {
2120 "kind": "messages_deleted",
2121 "room_id": "Uuid",
2122 "seq": "i64",
2123 "message_ids": "Uuid[]"
2124 },
2125 "labels": [
2126 "room_id",
2127 "request_id"
2128 ],
2129 "properties": [
2130 "room_id",
2131 "request_id"
2132 ],
2133 "source": "lib/chat/src/services/messages.rs:1276",
2134 "atomicity": "post-commit",
2135 "ordering_key": "chat:room:{room_id}"
2136 },
2137 {
2138 "project": "kenapi",
2139 "topic": "gitdata-chat",
2140 "area": "chat",
2141 "tag": "pin-create",
2142 "kind": "chat.pin.create",
2143 "payload": {
2144 "kind": "message_pinned",
2145 "room_id": "Uuid",
2146 "seq": "i64",
2147 "message_id": "Uuid",
2148 "pinned": true
2149 },
2150 "labels": [
2151 "room_id",
2152 "request_id"
2153 ],
2154 "properties": [
2155 "room_id",
2156 "request_id"
2157 ],
2158 "source": "lib/chat/src/services/messages.rs:1352",
2159 "atomicity": "post-commit",
2160 "ordering_key": "chat:room:{room_id}"
2161 },
2162 {
2163 "project": "kenapi",
2164 "topic": "gitdata-chat",
2165 "area": "chat",
2166 "tag": "pin-delete",
2167 "kind": "chat.pin.delete",
2168 "payload": {
2169 "kind": "message_pinned",
2170 "room_id": "Uuid",
2171 "seq": "i64",
2172 "message_id": "Uuid",
2173 "pinned": false
2174 },
2175 "labels": [
2176 "room_id",
2177 "request_id"
2178 ],
2179 "properties": [
2180 "room_id",
2181 "request_id"
2182 ],
2183 "source": "lib/chat/src/services/messages.rs:1352",
2184 "atomicity": "post-commit",
2185 "ordering_key": "chat:room:{room_id}"
2186 },
2187 {
2188 "project": "kenapi",
2189 "topic": "gitdata-chat",
2190 "area": "chat",
2191 "tag": "reaction-update",
2192 "kind": "chat.reaction.update",
2193 "payload": {
2194 "kind": "reaction_updated",
2195 "room_id": "Uuid",
2196 "message_id": "Uuid",
2197 "reactions": "ReactionAggDto[]"
2198 },
2199 "labels": [
2200 "room_id"
2201 ],
2202 "properties": [
2203 "room_id"
2204 ],
2205 "source": "lib/chat/src/services/reactions.rs:154",
2206 "atomicity": "post-commit",
2207 "ordering_key": "chat:room:{room_id}"
2208 },
2209 {
2210 "project": "kenapi",
2211 "topic": "gitdata-chat",
2212 "area": "chat",
2213 "tag": "room-update",
2214 "kind": "chat.room.update",
2215 "payload": {
2216 "kind": "room_updated",
2217 "room_id": "Uuid"
2218 },
2219 "labels": [
2220 "room_id"
2221 ],
2222 "properties": [
2223 "room_id"
2224 ],
2225 "source": "lib/chat/src/services/rooms.rs:440",
2226 "atomicity": "post-commit",
2227 "ordering_key": "chat:room:{room_id}"
2228 },
2229 {
2230 "project": "kenapi",
2231 "topic": "gitdata-chat",
2232 "area": "chat",
2233 "tag": "member-create",
2234 "kind": "chat.member.create",
2235 "payload": {
2236 "kind": "member_joined",
2237 "room_id": "Uuid",
2238 "user_id": "i64"
2239 },
2240 "labels": [
2241 "room_id",
2242 "user_id"
2243 ],
2244 "properties": [
2245 "room_id",
2246 "user"
2247 ],
2248 "source": "lib/chat/src/services/rooms.rs:553,694",
2249 "atomicity": "post-commit",
2250 "ordering_key": "chat:room:{room_id}"
2251 },
2252 {
2253 "project": "kenapi",
2254 "topic": "gitdata-chat",
2255 "area": "chat",
2256 "tag": "member-delete",
2257 "kind": "chat.member.delete",
2258 "payload": {
2259 "kind": "member_left",
2260 "room_id": "Uuid",
2261 "user_id": "i64"
2262 },
2263 "labels": [
2264 "room_id",
2265 "user_id"
2266 ],
2267 "properties": [
2268 "room_id",
2269 "user"
2270 ],
2271 "source": "lib/chat/src/services/rooms.rs:604, lib/chat/src/services/dms.rs:409, lib/chat/src/services/room_admin.rs:151,195",
2272 "atomicity": "post-commit",
2273 "ordering_key": "chat:room:{room_id}"
2274 },
2275 {
2276 "project": "kenapi",
2277 "topic": "gitdata-chat",
2278 "area": "chat",
2279 "tag": "thread-update",
2280 "kind": "chat.thread.update",
2281 "payload": {
2282 "kind": "thread_updated",
2283 "room_id": "Uuid",
2284 "thread_id": "Uuid"
2285 },
2286 "labels": [
2287 "room_id"
2288 ],
2289 "properties": [
2290 "room_id"
2291 ],
2292 "source": "lib/chat/src/services/threads.rs:238,348, lib/chat/src/services/messages.rs:759",
2293 "atomicity": "post-commit",
2294 "ordering_key": "chat:room:{room_id}"
2295 },
2296 {
2297 "project": "kenapi",
2298 "topic": "gitdata-chat",
2299 "area": "chat",
2300 "tag": "voice-presence",
2301 "kind": "chat.voice.presence",
2302 "payload": {
2303 "kind": "voice_presence",
2304 "room_id": "Uuid"
2305 },
2306 "labels": [
2307 "room_id"
2308 ],
2309 "properties": [
2310 "room_id"
2311 ],
2312 "source": "lib/chat/src/handlers/voice.rs:444,453,487,500,537",
2313 "atomicity": "post-commit",
2314 "ordering_key": "chat:room:{room_id}"
2315 },
2316 {
2317 "project": "kenapi",
2318 "topic": "gitdata-chat",
2319 "area": "chat",
2320 "tag": "voice-hand",
2321 "kind": "chat.voice.hand",
2322 "payload": {
2323 "kind": "voice_hands",
2324 "room_id": "Uuid"
2325 },
2326 "labels": [
2327 "room_id"
2328 ],
2329 "properties": [
2330 "room_id"
2331 ],
2332 "source": "lib/chat/src/handlers/voice.rs:504, lib/chat/src/services/voice.rs:871,916,988,1056",
2333 "atomicity": "post-commit",
2334 "ordering_key": "chat:room:{room_id}"
2335 },
2336 {
2337 "project": "kenapi",
2338 "topic": "gitdata-chat",
2339 "area": "chat",
2340 "tag": "voice-layout",
2341 "kind": "chat.voice.layout",
2342 "payload": {
2343 "kind": "voice_layout",
2344 "room_id": "Uuid"
2345 },
2346 "labels": [
2347 "room_id"
2348 ],
2349 "properties": [
2350 "room_id"
2351 ],
2352 "source": "lib/chat/src/services/voice.rs:846",
2353 "atomicity": "post-commit",
2354 "ordering_key": "chat:room:{room_id}"
2355 },
2356 {
2357 "project": "kenapi",
2358 "topic": "gitdata-chat",
2359 "area": "chat",
2360 "tag": "voice-moderate",
2361 "kind": "chat.voice.moderate",
2362 "payload": {
2363 "kind": "voice_moderation",
2364 "room_id": "Uuid",
2365 "user_id": "i64",
2366 "action": "string"
2367 },
2368 "labels": [
2369 "room_id",
2370 "user_id"
2371 ],
2372 "properties": [
2373 "room_id",
2374 "user"
2375 ],
2376 "source": "lib/chat/src/services/voice.rs:996,1059,1178,1211",
2377 "atomicity": "post-commit",
2378 "ordering_key": "chat:room:{room_id}"
2379 },
2380 {
2381 "project": "kenapi",
2382 "topic": "gitdata-chat",
2383 "area": "chat",
2384 "tag": "mention-create",
2385 "kind": "chat.mention.create",
2386 "payload": {
2387 "kind": "mention",
2388 "room_id": "Uuid",
2389 "message_id": "Uuid"
2390 },
2391 "labels": [
2392 "user_id",
2393 "room_id",
2394 "request_id"
2395 ],
2396 "properties": [
2397 "user",
2398 "room_id",
2399 "request_id"
2400 ],
2401 "source": "lib/chat/src/share/notify.rs:269, lib/chat/src/services/messages.rs:806,1144",
2402 "atomicity": "post-commit",
2403 "ordering_key": "chat:user:{user_id}"
2404 },
2405 {
2406 "project": "kenapi",
2407 "topic": "gitdata-chat",
2408 "area": "chat",
2409 "tag": "reminder-create",
2410 "kind": "chat.reminder.create",
2411 "payload": {
2412 "kind": "reminder",
2413 "room_id": "Uuid",
2414 "message_id": "Uuid"
2415 },
2416 "labels": [
2417 "user_id",
2418 "room_id",
2419 "request_id"
2420 ],
2421 "properties": [
2422 "user",
2423 "room_id",
2424 "request_id"
2425 ],
2426 "source": "lib/chat/src/services/reminders.rs:273",
2427 "atomicity": "post-commit",
2428 "ordering_key": "chat:user:{user_id}"
2429 },
2430 {
2431 "project": "kenapi",
2432 "topic": "gitdata-chat",
2433 "area": "chat",
2434 "tag": "membership-create",
2435 "kind": "chat.membership.create",
2436 "payload": {
2437 "kind": "room_added",
2438 "room_id": "Uuid"
2439 },
2440 "labels": [
2441 "user_id",
2442 "room_id",
2443 "request_id"
2444 ],
2445 "properties": [
2446 "user",
2447 "room_id",
2448 "request_id"
2449 ],
2450 "source": "lib/chat/src/services/rooms.rs:702",
2451 "atomicity": "post-commit",
2452 "ordering_key": "chat:user:{user_id}"
2453 },
2454 {
2455 "project": "kenapi",
2456 "topic": "gitdata-chat",
2457 "area": "chat",
2458 "tag": "read_state-update",
2459 "kind": "chat.read_state.update",
2460 "payload": {
2461 "kind": "read_marker",
2462 "room_id": "Uuid",
2463 "last_read_message_id": "Uuid|null",
2464 "last_read_seq": "i64"
2465 },
2466 "labels": [
2467 "user_id",
2468 "room_id",
2469 "request_id"
2470 ],
2471 "properties": [
2472 "user",
2473 "room_id",
2474 "request_id"
2475 ],
2476 "source": "lib/chat/src/services/read_state.rs:93",
2477 "atomicity": "post-commit",
2478 "ordering_key": "chat:user:{user_id}"
2479 },
2480 {
2481 "project": "kenapi",
2482 "topic": "gitdata-chat",
2483 "area": "chat",
2484 "tag": "presence-update",
2485 "kind": "chat.presence.update",
2486 "payload": {
2487 "kind": "presence",
2488 "status": "string"
2489 },
2490 "labels": [
2491 "user_id",
2492 "request_id"
2493 ],
2494 "properties": [
2495 "user",
2496 "request_id"
2497 ],
2498 "source": "lib/chat/src/services/presence.rs:118,150,238",
2499 "atomicity": "post-commit",
2500 "ordering_key": "chat:user:{user_id}"
2501 },
2502 {
2503 "project": "kenapi",
2504 "topic": "gitdata-notify",
2505 "area": "notify",
2506 "tag": "notification-create",
2507 "kind": "notify.notification.create",
2508 "payload": {
2509 "kind": "notification",
2510 "notification": "NotificationDto"
2511 },
2512 "labels": [
2513 "user_id",
2514 "request_id"
2515 ],
2516 "properties": [
2517 "user",
2518 "request_id"
2519 ],
2520 "source": "lib/service/push/src/notify.rs",
2521 "atomicity": "post-commit",
2522 "ordering_key": "notify:user:{user_id}"
2523 },
2524 {
2525 "project": "kenapi",
2526 "topic": "gitdata-issue",
2527 "area": "issue",
2528 "tag": "issue-create",
2529 "kind": "issue.issue.create",
2530 "payload": {
2531 "issue_id": "Uuid",
2532 "user_id": "i64",
2533 "event_type": "created",
2534 "comment_id": "Uuid|null",
2535 "old_title": "string|null",
2536 "new_title": "string|null",
2537 "content": "string|null"
2538 },
2539 "labels": [
2540 "issue_id",
2541 "user_id",
2542 "request_id"
2543 ],
2544 "properties": [
2545 "issue_id",
2546 "user",
2547 "request_id"
2548 ],
2549 "source": "lib/issues/src/services/issues.rs:608",
2550 "atomicity": "same connection as issue_event_line",
2551 "ordering_key": "issue:{issue_id}"
2552 },
2553 {
2554 "project": "kenapi",
2555 "topic": "gitdata-issue",
2556 "area": "issue",
2557 "tag": "title-update",
2558 "kind": "issue.title.update",
2559 "payload": {
2560 "issue_id": "Uuid",
2561 "user_id": "i64",
2562 "event_type": "title_updated",
2563 "comment_id": "Uuid|null",
2564 "old_title": "string|null",
2565 "new_title": "string|null",
2566 "content": "string|null"
2567 },
2568 "labels": [
2569 "issue_id",
2570 "user_id",
2571 "request_id"
2572 ],
2573 "properties": [
2574 "issue_id",
2575 "user",
2576 "request_id"
2577 ],
2578 "source": "lib/issues/src/services/issues.rs:1040",
2579 "atomicity": "same connection as issue_event_line",
2580 "ordering_key": "issue:{issue_id}"
2581 },
2582 {
2583 "project": "kenapi",
2584 "topic": "gitdata-issue",
2585 "area": "issue",
2586 "tag": "milestone-update",
2587 "kind": "issue.milestone.update",
2588 "payload": {
2589 "issue_id": "Uuid",
2590 "user_id": "i64",
2591 "event_type": "milestone_updated",
2592 "comment_id": "Uuid|null",
2593 "old_title": "string|null",
2594 "new_title": "string|null",
2595 "content": "string|null"
2596 },
2597 "labels": [
2598 "issue_id",
2599 "user_id",
2600 "request_id"
2601 ],
2602 "properties": [
2603 "issue_id",
2604 "user",
2605 "request_id"
2606 ],
2607 "source": "lib/issues/src/services/issues.rs:1112",
2608 "atomicity": "same connection as issue_event_line",
2609 "ordering_key": "issue:{issue_id}"
2610 },
2611 {
2612 "project": "kenapi",
2613 "topic": "gitdata-issue",
2614 "area": "issue",
2615 "tag": "issue-close",
2616 "kind": "issue.issue.close",
2617 "payload": {
2618 "issue_id": "Uuid",
2619 "user_id": "i64",
2620 "event_type": "closed",
2621 "comment_id": "Uuid|null",
2622 "old_title": "string|null",
2623 "new_title": "string|null",
2624 "content": "string|null"
2625 },
2626 "labels": [
2627 "issue_id",
2628 "user_id",
2629 "request_id"
2630 ],
2631 "properties": [
2632 "issue_id",
2633 "user",
2634 "request_id"
2635 ],
2636 "source": "lib/issues/src/services/issues.rs:1197,1291",
2637 "atomicity": "same connection as issue_event_line",
2638 "ordering_key": "issue:{issue_id}"
2639 },
2640 {
2641 "project": "kenapi",
2642 "topic": "gitdata-issue",
2643 "area": "issue",
2644 "tag": "issue-reopen",
2645 "kind": "issue.issue.reopen",
2646 "payload": {
2647 "issue_id": "Uuid",
2648 "user_id": "i64",
2649 "event_type": "reopened",
2650 "comment_id": "Uuid|null",
2651 "old_title": "string|null",
2652 "new_title": "string|null",
2653 "content": "string|null"
2654 },
2655 "labels": [
2656 "issue_id",
2657 "user_id",
2658 "request_id"
2659 ],
2660 "properties": [
2661 "issue_id",
2662 "user",
2663 "request_id"
2664 ],
2665 "source": "lib/issues/src/services/issues.rs:1197,1291",
2666 "atomicity": "same connection as issue_event_line",
2667 "ordering_key": "issue:{issue_id}"
2668 },
2669 {
2670 "project": "kenapi",
2671 "topic": "gitdata-issue",
2672 "area": "issue",
2673 "tag": "comment-create",
2674 "kind": "issue.comment.create",
2675 "payload": {
2676 "issue_id": "Uuid",
2677 "user_id": "i64",
2678 "event_type": "comment_created",
2679 "comment_id": "Uuid|null",
2680 "old_title": "string|null",
2681 "new_title": "string|null",
2682 "content": "string|null"
2683 },
2684 "labels": [
2685 "issue_id",
2686 "user_id",
2687 "request_id"
2688 ],
2689 "properties": [
2690 "issue_id",
2691 "user",
2692 "request_id"
2693 ],
2694 "source": "lib/issues/src/services/comments.rs:157",
2695 "atomicity": "same connection as issue_event_line",
2696 "ordering_key": "issue:{issue_id}"
2697 },
2698 {
2699 "project": "kenapi",
2700 "topic": "gitdata-issue",
2701 "area": "issue",
2702 "tag": "comment-update",
2703 "kind": "issue.comment.update",
2704 "payload": {
2705 "issue_id": "Uuid",
2706 "user_id": "i64",
2707 "event_type": "comment_updated",
2708 "comment_id": "Uuid|null",
2709 "old_title": "string|null",
2710 "new_title": "string|null",
2711 "content": "string|null"
2712 },
2713 "labels": [
2714 "issue_id",
2715 "user_id",
2716 "request_id"
2717 ],
2718 "properties": [
2719 "issue_id",
2720 "user",
2721 "request_id"
2722 ],
2723 "source": "lib/issues/src/services/comments.rs:500",
2724 "atomicity": "same connection as issue_event_line",
2725 "ordering_key": "issue:{issue_id}"
2726 },
2727 {
2728 "project": "kenapi",
2729 "topic": "gitdata-issue",
2730 "area": "issue",
2731 "tag": "comment-delete",
2732 "kind": "issue.comment.delete",
2733 "payload": {
2734 "issue_id": "Uuid",
2735 "user_id": "i64",
2736 "event_type": "comment_deleted",
2737 "comment_id": "Uuid|null",
2738 "old_title": "string|null",
2739 "new_title": "string|null",
2740 "content": "string|null"
2741 },
2742 "labels": [
2743 "issue_id",
2744 "user_id",
2745 "request_id"
2746 ],
2747 "properties": [
2748 "issue_id",
2749 "user",
2750 "request_id"
2751 ],
2752 "source": "lib/issues/src/services/comments.rs:567",
2753 "atomicity": "same connection as issue_event_line",
2754 "ordering_key": "issue:{issue_id}"
2755 },
2756 {
2757 "project": "kenapi",
2758 "topic": "gitdata-issue",
2759 "area": "issue",
2760 "tag": "member-create",
2761 "kind": "issue.member.create",
2762 "payload": {
2763 "issue_id": "Uuid",
2764 "user_id": "i64",
2765 "event_type": "member_added",
2766 "comment_id": "Uuid|null",
2767 "old_title": "string|null",
2768 "new_title": "string|null",
2769 "content": "string|null"
2770 },
2771 "labels": [
2772 "issue_id",
2773 "user_id",
2774 "request_id"
2775 ],
2776 "properties": [
2777 "issue_id",
2778 "user",
2779 "request_id"
2780 ],
2781 "source": "lib/issues/src/services/members.rs:122",
2782 "atomicity": "same connection as issue_event_line",
2783 "ordering_key": "issue:{issue_id}"
2784 },
2785 {
2786 "project": "kenapi",
2787 "topic": "gitdata-issue",
2788 "area": "issue",
2789 "tag": "member-delete",
2790 "kind": "issue.member.delete",
2791 "payload": {
2792 "issue_id": "Uuid",
2793 "user_id": "i64",
2794 "event_type": "member_removed",
2795 "comment_id": "Uuid|null",
2796 "old_title": "string|null",
2797 "new_title": "string|null",
2798 "content": "string|null"
2799 },
2800 "labels": [
2801 "issue_id",
2802 "user_id",
2803 "request_id"
2804 ],
2805 "properties": [
2806 "issue_id",
2807 "user",
2808 "request_id"
2809 ],
2810 "source": "lib/issues/src/services/members.rs:287",
2811 "atomicity": "same connection as issue_event_line",
2812 "ordering_key": "issue:{issue_id}"
2813 },
2814 {
2815 "project": "kenapi",
2816 "topic": "gitdata-issue",
2817 "area": "issue",
2818 "tag": "label-create",
2819 "kind": "issue.label.create",
2820 "payload": {
2821 "issue_id": "Uuid",
2822 "user_id": "i64",
2823 "event_type": "label_added",
2824 "comment_id": "Uuid|null",
2825 "old_title": "string|null",
2826 "new_title": "string|null",
2827 "content": "string|null"
2828 },
2829 "labels": [
2830 "issue_id",
2831 "user_id",
2832 "request_id"
2833 ],
2834 "properties": [
2835 "issue_id",
2836 "user",
2837 "request_id"
2838 ],
2839 "source": "lib/issues/src/services/labels.rs:404",
2840 "atomicity": "same connection as issue_event_line",
2841 "ordering_key": "issue:{issue_id}"
2842 },
2843 {
2844 "project": "kenapi",
2845 "topic": "gitdata-issue",
2846 "area": "issue",
2847 "tag": "label-delete",
2848 "kind": "issue.label.delete",
2849 "payload": {
2850 "issue_id": "Uuid",
2851 "user_id": "i64",
2852 "event_type": "label_removed",
2853 "comment_id": "Uuid|null",
2854 "old_title": "string|null",
2855 "new_title": "string|null",
2856 "content": "string|null"
2857 },
2858 "labels": [
2859 "issue_id",
2860 "user_id",
2861 "request_id"
2862 ],
2863 "properties": [
2864 "issue_id",
2865 "user",
2866 "request_id"
2867 ],
2868 "source": "lib/issues/src/services/labels.rs:458",
2869 "atomicity": "same connection as issue_event_line",
2870 "ordering_key": "issue:{issue_id}"
2871 },
2872 {
2873 "project": "kenapi",
2874 "topic": "gitdata-issue",
2875 "area": "issue",
2876 "tag": "dependency-create",
2877 "kind": "issue.dependency.create",
2878 "payload": {
2879 "issue_id": "Uuid",
2880 "user_id": "i64",
2881 "event_type": "dependency_added",
2882 "comment_id": "Uuid|null",
2883 "old_title": "string|null",
2884 "new_title": "string|null",
2885 "content": "string|null"
2886 },
2887 "labels": [
2888 "issue_id",
2889 "user_id",
2890 "request_id"
2891 ],
2892 "properties": [
2893 "issue_id",
2894 "user",
2895 "request_id"
2896 ],
2897 "source": "lib/issues/src/services/dependencies.rs:102",
2898 "atomicity": "same connection as issue_event_line",
2899 "ordering_key": "issue:{issue_id}"
2900 },
2901 {
2902 "project": "kenapi",
2903 "topic": "gitdata-issue",
2904 "area": "issue",
2905 "tag": "dependency-delete",
2906 "kind": "issue.dependency.delete",
2907 "payload": {
2908 "issue_id": "Uuid",
2909 "user_id": "i64",
2910 "event_type": "dependency_removed",
2911 "comment_id": "Uuid|null",
2912 "old_title": "string|null",
2913 "new_title": "string|null",
2914 "content": "string|null"
2915 },
2916 "labels": [
2917 "issue_id",
2918 "user_id",
2919 "request_id"
2920 ],
2921 "properties": [
2922 "issue_id",
2923 "user",
2924 "request_id"
2925 ],
2926 "source": "lib/issues/src/services/dependencies.rs:191",
2927 "atomicity": "same connection as issue_event_line",
2928 "ordering_key": "issue:{issue_id}"
2929 },
2930 {
2931 "project": "kenapi",
2932 "topic": "gitdata-pull",
2933 "area": "pull",
2934 "tag": "pull-create",
2935 "kind": "pull.pull.create",
2936 "payload": {
2937 "pull_request_id": "Uuid",
2938 "user_id": "i64",
2939 "event_type": "created",
2940 "commit_sha": "string|null",
2941 "content": "string|null"
2942 },
2943 "labels": [
2944 "pull_id",
2945 "user_id",
2946 "request_id"
2947 ],
2948 "properties": [
2949 "pull_id",
2950 "user",
2951 "request_id"
2952 ],
2953 "source": "lib/pulls/src/services/pulls.rs:404",
2954 "atomicity": "same connection as pull_request_event_line",
2955 "ordering_key": "pull:{pull_request_id}"
2956 },
2957 {
2958 "project": "kenapi",
2959 "topic": "gitdata-pull",
2960 "area": "pull",
2961 "tag": "pull-update",
2962 "kind": "pull.pull.update",
2963 "payload": {
2964 "pull_request_id": "Uuid",
2965 "user_id": "i64",
2966 "event_type": "updated",
2967 "commit_sha": "string|null",
2968 "content": "string|null"
2969 },
2970 "labels": [
2971 "pull_id",
2972 "user_id",
2973 "request_id"
2974 ],
2975 "properties": [
2976 "pull_id",
2977 "user",
2978 "request_id"
2979 ],
2980 "source": "lib/pulls/src/services/pulls.rs:501",
2981 "atomicity": "same connection as pull_request_event_line",
2982 "ordering_key": "pull:{pull_request_id}"
2983 },
2984 {
2985 "project": "kenapi",
2986 "topic": "gitdata-pull",
2987 "area": "pull",
2988 "tag": "pull-close",
2989 "kind": "pull.pull.close",
2990 "payload": {
2991 "pull_request_id": "Uuid",
2992 "user_id": "i64",
2993 "event_type": "closed",
2994 "commit_sha": "string|null",
2995 "content": "string|null"
2996 },
2997 "labels": [
2998 "pull_id",
2999 "user_id",
3000 "request_id"
3001 ],
3002 "properties": [
3003 "pull_id",
3004 "user",
3005 "request_id"
3006 ],
3007 "source": "lib/pulls/src/services/pulls.rs:532",
3008 "atomicity": "same connection as pull_request_event_line",
3009 "ordering_key": "pull:{pull_request_id}"
3010 },
3011 {
3012 "project": "kenapi",
3013 "topic": "gitdata-pull",
3014 "area": "pull",
3015 "tag": "pull-reopen",
3016 "kind": "pull.pull.reopen",
3017 "payload": {
3018 "pull_request_id": "Uuid",
3019 "user_id": "i64",
3020 "event_type": "reopened",
3021 "commit_sha": "string|null",
3022 "content": "string|null"
3023 },
3024 "labels": [
3025 "pull_id",
3026 "user_id",
3027 "request_id"
3028 ],
3029 "properties": [
3030 "pull_id",
3031 "user",
3032 "request_id"
3033 ],
3034 "source": "lib/pulls/src/services/pulls.rs:532",
3035 "atomicity": "same connection as pull_request_event_line",
3036 "ordering_key": "pull:{pull_request_id}"
3037 },
3038 {
3039 "project": "kenapi",
3040 "topic": "gitdata-pull",
3041 "area": "pull",
3042 "tag": "pull-merge",
3043 "kind": "pull.pull.merge",
3044 "payload": {
3045 "pull_request_id": "Uuid",
3046 "user_id": "i64",
3047 "event_type": "merged",
3048 "commit_sha": "string|null",
3049 "content": "string|null"
3050 },
3051 "labels": [
3052 "pull_id",
3053 "user_id",
3054 "request_id"
3055 ],
3056 "properties": [
3057 "pull_id",
3058 "user",
3059 "request_id"
3060 ],
3061 "source": "lib/pulls/src/services/pulls.rs:673",
3062 "atomicity": "same connection as pull_request_event_line",
3063 "ordering_key": "pull:{pull_request_id}"
3064 },
3065 {
3066 "project": "kenapi",
3067 "topic": "gitdata-pull",
3068 "area": "pull",
3069 "tag": "comment-create",
3070 "kind": "pull.comment.create",
3071 "payload": {
3072 "pull_request_id": "Uuid",
3073 "user_id": "i64",
3074 "event_type": "commented",
3075 "commit_sha": "string|null",
3076 "content": "string|null"
3077 },
3078 "labels": [
3079 "pull_id",
3080 "user_id",
3081 "request_id"
3082 ],
3083 "properties": [
3084 "pull_id",
3085 "user",
3086 "request_id"
3087 ],
3088 "source": "lib/pulls/src/services/comments.rs:169",
3089 "atomicity": "same connection as pull_request_event_line",
3090 "ordering_key": "pull:{pull_request_id}"
3091 },
3092 {
3093 "project": "kenapi",
3094 "topic": "gitdata-pull",
3095 "area": "pull",
3096 "tag": "review-create",
3097 "kind": "pull.review.create",
3098 "payload": {
3099 "pull_request_id": "Uuid",
3100 "user_id": "i64",
3101 "event_type": "review_submitted",
3102 "commit_sha": "string|null",
3103 "content": "string|null"
3104 },
3105 "labels": [
3106 "pull_id",
3107 "user_id",
3108 "request_id"
3109 ],
3110 "properties": [
3111 "pull_id",
3112 "user",
3113 "request_id"
3114 ],
3115 "source": "lib/pulls/src/services/reviews.rs:139",
3116 "atomicity": "same connection as pull_request_event_line",
3117 "ordering_key": "pull:{pull_request_id}"
3118 },
3119 {
3120 "project": "kenapi",
3121 "topic": "gitdata-pull",
3122 "area": "pull",
3123 "tag": "review-delete",
3124 "kind": "pull.review.delete",
3125 "payload": {
3126 "pull_request_id": "Uuid",
3127 "user_id": "i64",
3128 "event_type": "review_dismissed",
3129 "commit_sha": "string|null",
3130 "content": "string|null"
3131 },
3132 "labels": [
3133 "pull_id",
3134 "user_id",
3135 "request_id"
3136 ],
3137 "properties": [
3138 "pull_id",
3139 "user",
3140 "request_id"
3141 ],
3142 "source": "lib/pulls/src/services/reviews.rs:232",
3143 "atomicity": "same connection as pull_request_event_line",
3144 "ordering_key": "pull:{pull_request_id}"
3145 },
3146 {
3147 "project": "kenapi",
3148 "topic": "gitdata-pull",
3149 "area": "pull",
3150 "tag": "review-request-create",
3151 "kind": "pull.review-request.create",
3152 "payload": {
3153 "pull_request_id": "Uuid",
3154 "user_id": "i64",
3155 "event_type": "review_requested",
3156 "commit_sha": "string|null",
3157 "content": "string|null"
3158 },
3159 "labels": [
3160 "pull_id",
3161 "user_id",
3162 "request_id"
3163 ],
3164 "properties": [
3165 "pull_id",
3166 "user",
3167 "request_id"
3168 ],
3169 "source": "lib/pulls/src/services/reviewers.rs:131",
3170 "atomicity": "same connection as pull_request_event_line",
3171 "ordering_key": "pull:{pull_request_id}"
3172 },
3173 {
3174 "project": "kenapi",
3175 "topic": "gitdata-pull",
3176 "area": "pull",
3177 "tag": "review-request-delete",
3178 "kind": "pull.review-request.delete",
3179 "payload": {
3180 "pull_request_id": "Uuid",
3181 "user_id": "i64",
3182 "event_type": "review_request_cancelled",
3183 "commit_sha": "string|null",
3184 "content": "string|null"
3185 },
3186 "labels": [
3187 "pull_id",
3188 "user_id",
3189 "request_id"
3190 ],
3191 "properties": [
3192 "pull_id",
3193 "user",
3194 "request_id"
3195 ],
3196 "source": "lib/pulls/src/services/reviewers.rs:165",
3197 "atomicity": "same connection as pull_request_event_line",
3198 "ordering_key": "pull:{pull_request_id}"
3199 },
3200 {
3201 "project": "kenapi",
3202 "topic": "gitdata-pull",
3203 "area": "pull",
3204 "tag": "head-update",
3205 "kind": "pull.head.update",
3206 "payload": {
3207 "pull_request_id": "Uuid",
3208 "user_id": "i64",
3209 "event_type": "head_updated",
3210 "commit_sha": "string|null",
3211 "content": "string|null"
3212 },
3213 "labels": [
3214 "pull_id",
3215 "user_id",
3216 "request_id"
3217 ],
3218 "properties": [
3219 "pull_id",
3220 "user",
3221 "request_id"
3222 ],
3223 "source": "lib/pulls/src/services/changes.rs:147",
3224 "atomicity": "same connection as pull_request_event_line",
3225 "ordering_key": "pull:{pull_request_id}"
3226 },
3227 {
3228 "project": "kenapi",
3229 "topic": "gitdata-pull",
3230 "area": "pull",
3231 "tag": "label-create",
3232 "kind": "pull.label.create",
3233 "payload": {
3234 "pull_request_id": "Uuid",
3235 "user_id": "i64",
3236 "event_type": "labeled",
3237 "commit_sha": "string|null",
3238 "content": "string|null"
3239 },
3240 "labels": [
3241 "pull_id",
3242 "user_id",
3243 "request_id"
3244 ],
3245 "properties": [
3246 "pull_id",
3247 "user",
3248 "request_id"
3249 ],
3250 "source": "lib/pulls/src/services/labels.rs:106",
3251 "atomicity": "same connection as pull_request_event_line",
3252 "ordering_key": "pull:{pull_request_id}"
3253 },
3254 {
3255 "project": "kenapi",
3256 "topic": "gitdata-pull",
3257 "area": "pull",
3258 "tag": "label-delete",
3259 "kind": "pull.label.delete",
3260 "payload": {
3261 "pull_request_id": "Uuid",
3262 "user_id": "i64",
3263 "event_type": "unlabeled",
3264 "commit_sha": "string|null",
3265 "content": "string|null"
3266 },
3267 "labels": [
3268 "pull_id",
3269 "user_id",
3270 "request_id"
3271 ],
3272 "properties": [
3273 "pull_id",
3274 "user",
3275 "request_id"
3276 ],
3277 "source": "lib/pulls/src/services/labels.rs:133",
3278 "atomicity": "same connection as pull_request_event_line",
3279 "ordering_key": "pull:{pull_request_id}"
3280 },
3281 {
3282 "project": "kenapi",
3283 "topic": "gitdata-pull",
3284 "area": "pull",
3285 "tag": "assignee-create",
3286 "kind": "pull.assignee.create",
3287 "payload": {
3288 "pull_request_id": "Uuid",
3289 "user_id": "i64",
3290 "event_type": "assigned",
3291 "commit_sha": "string|null",
3292 "content": "string|null"
3293 },
3294 "labels": [
3295 "pull_id",
3296 "user_id",
3297 "request_id"
3298 ],
3299 "properties": [
3300 "pull_id",
3301 "user",
3302 "request_id"
3303 ],
3304 "source": "lib/pulls/src/services/assignees.rs:72",
3305 "atomicity": "same connection as pull_request_event_line",
3306 "ordering_key": "pull:{pull_request_id}"
3307 },
3308 {
3309 "project": "kenapi",
3310 "topic": "gitdata-pull",
3311 "area": "pull",
3312 "tag": "assignee-delete",
3313 "kind": "pull.assignee.delete",
3314 "payload": {
3315 "pull_request_id": "Uuid",
3316 "user_id": "i64",
3317 "event_type": "unassigned",
3318 "commit_sha": "string|null",
3319 "content": "string|null"
3320 },
3321 "labels": [
3322 "pull_id",
3323 "user_id",
3324 "request_id"
3325 ],
3326 "properties": [
3327 "pull_id",
3328 "user",
3329 "request_id"
3330 ],
3331 "source": "lib/pulls/src/services/assignees.rs:98",
3332 "atomicity": "same connection as pull_request_event_line",
3333 "ordering_key": "pull:{pull_request_id}"
3334 },
3335 {
3336 "project": "kenapi",
3337 "topic": "gitdata-pull",
3338 "area": "pull",
3339 "tag": "lock-create",
3340 "kind": "pull.lock.create",
3341 "payload": {
3342 "pull_request_id": "Uuid",
3343 "user_id": "i64",
3344 "event_type": "locked",
3345 "commit_sha": "string|null",
3346 "content": "string|null"
3347 },
3348 "labels": [
3349 "pull_id",
3350 "user_id",
3351 "request_id"
3352 ],
3353 "properties": [
3354 "pull_id",
3355 "user",
3356 "request_id"
3357 ],
3358 "source": "lib/pulls/src/services/pulls.rs:694",
3359 "atomicity": "same connection as pull_request_event_line",
3360 "ordering_key": "pull:{pull_request_id}"
3361 },
3362 {
3363 "project": "kenapi",
3364 "topic": "gitdata-pull",
3365 "area": "pull",
3366 "tag": "lock-delete",
3367 "kind": "pull.lock.delete",
3368 "payload": {
3369 "pull_request_id": "Uuid",
3370 "user_id": "i64",
3371 "event_type": "unlocked",
3372 "commit_sha": "string|null",
3373 "content": "string|null"
3374 },
3375 "labels": [
3376 "pull_id",
3377 "user_id",
3378 "request_id"
3379 ],
3380 "properties": [
3381 "pull_id",
3382 "user",
3383 "request_id"
3384 ],
3385 "source": "lib/pulls/src/services/pulls.rs:694",
3386 "atomicity": "same connection as pull_request_event_line",
3387 "ordering_key": "pull:{pull_request_id}"
3388 }
3389 ]
3390}
Modified lib/share/events/src/gateway/action/gitdata.rs lib/share/events/src/gateway/action/gitdata.rs +6 −55 View lib/share/events/src/gateway/action/gitdata.rs
@@ -19,59 +19,10 @@
1919}
2020
2121pub(super) fn is_supported(topic: &str, tag: &str) -> bool {
22 matches!(
23 (topic, tag),
24 ("gitdata-chat", "message-create")
25 | ("gitdata-chat", "message-update")
26 | ("gitdata-chat", "message-delete")
27 | ("gitdata-chat", "messages-delete")
28 | ("gitdata-chat", "pin-create")
29 | ("gitdata-chat", "pin-delete")
30 | ("gitdata-chat", "reaction-update")
31 | ("gitdata-chat", "room-update")
32 | ("gitdata-chat", "member-create")
33 | ("gitdata-chat", "member-delete")
34 | ("gitdata-chat", "thread-update")
35 | ("gitdata-chat", "voice-presence")
36 | ("gitdata-chat", "voice-hand")
37 | ("gitdata-chat", "voice-layout")
38 | ("gitdata-chat", "voice-moderate")
39 | ("gitdata-chat", "mention-create")
40 | ("gitdata-chat", "reminder-create")
41 | ("gitdata-chat", "membership-create")
42 | ("gitdata-chat", "read_state-update")
43 | ("gitdata-chat", "presence-update")
44 | ("gitdata-notify", "notification-create")
45 | ("gitdata-issue", "issue-create")
46 | ("gitdata-issue", "title-update")
47 | ("gitdata-issue", "milestone-update")
48 | ("gitdata-issue", "issue-close")
49 | ("gitdata-issue", "issue-reopen")
50 | ("gitdata-issue", "comment-create")
51 | ("gitdata-issue", "comment-update")
52 | ("gitdata-issue", "comment-delete")
53 | ("gitdata-issue", "member-create")
54 | ("gitdata-issue", "member-delete")
55 | ("gitdata-issue", "label-create")
56 | ("gitdata-issue", "label-delete")
57 | ("gitdata-issue", "dependency-create")
58 | ("gitdata-issue", "dependency-delete")
59 | ("gitdata-pull", "pull-create")
60 | ("gitdata-pull", "pull-update")
61 | ("gitdata-pull", "pull-close")
62 | ("gitdata-pull", "pull-reopen")
63 | ("gitdata-pull", "pull-merge")
64 | ("gitdata-pull", "comment-create")
65 | ("gitdata-pull", "review-create")
66 | ("gitdata-pull", "review-delete")
67 | ("gitdata-pull", "review-request-create")
68 | ("gitdata-pull", "review-request-delete")
69 | ("gitdata-pull", "head-update")
70 | ("gitdata-pull", "label-create")
71 | ("gitdata-pull", "label-delete")
72 | ("gitdata-pull", "assignee-create")
73 | ("gitdata-pull", "assignee-delete")
74 | ("gitdata-pull", "lock-create")
75 | ("gitdata-pull", "lock-delete")
76 )
22 let Some((prefix, area)) = super::split_topic(topic) else {
23 return false;
24 };
25 prefix == "gitdata"
26 && !super::identity_area(area)
27 && super::catalog().any(|(_, known_area, known_tag)| known_area == area && known_tag == tag)
7728}
Modified lib/share/events/src/gateway/action/kenshell.rs lib/share/events/src/gateway/action/kenshell.rs +7 −43 View lib/share/events/src/gateway/action/kenshell.rs
@@ -19,47 +19,11 @@
1919}
2020
2121pub(super) fn is_supported(topic: &str, tag: &str) -> bool {
22 matches!(
23 (topic, tag),
24 ("kenshell-repo", "create")
25 | ("kenshell-repo", "update")
26 | ("kenshell-repo", "delete")
27 | ("kenshell-repo", "placement-change")
28 | ("kenshell-repo", "language-update")
29 | ("kenshell-ref", "create-branch")
30 | ("kenshell-ref", "update-branch")
31 | ("kenshell-ref", "delete-branch")
32 | ("kenshell-ref", "rename-branch")
33 | ("kenshell-ref", "set-default-branch")
34 | ("kenshell-ref", "create-tag")
35 | ("kenshell-ref", "delete-tag")
36 | ("kenshell-ref", "create-protection")
37 | ("kenshell-ref", "delete-protection")
38 | ("kenshell-commit", "sync")
39 | ("kenshell-commit", "set-status")
40 | ("kenshell-commit", "comment-create")
41 | ("kenshell-commit", "comment-update")
42 | ("kenshell-commit", "comment-delete")
43 | ("kenshell-lfs", "upload")
44 | ("kenshell-lfs", "unreference")
45 | ("kenshell-lfs", "purge")
46 | ("kenshell-archive", "create")
47 | ("kenshell-job", "enqueue")
48 | ("kenshell-job", "claim")
49 | ("kenshell-job", "progress")
50 | ("kenshell-job", "heartbeat")
51 | ("kenshell-job", "cancel")
52 | ("kenshell-job", "retry")
53 | ("kenshell-job", "finish")
54 | ("kenshell-node", "create")
55 | ("kenshell-node", "update")
56 | ("kenshell-node", "delete")
57 | ("kenshell-node", "probe")
58 | ("kenshell-volume", "create")
59 | ("kenshell-volume", "update")
60 | ("kenshell-volume", "delete")
61 | ("kenshell-volume", "mount")
62 | ("kenshell-volume", "unmount")
63 | ("kenshell-volume", "probe")
64 )
22 let Some((prefix, area)) = super::split_topic(topic) else {
23 return false;
24 };
25 prefix == "kenshell"
26 && super::KENSHELL
27 .iter()
28 .any(|(known_area, known_tag)| *known_area == area && *known_tag == tag)
6529}
Modified lib/share/events/src/gateway/action/mod.rs lib/share/events/src/gateway/action/mod.rs +80 −0 View lib/share/events/src/gateway/action/mod.rs
@@ -9,8 +9,88 @@
99
1010use serde_json::Value;
1111
12use crate::EventKind;
1213use crate::gateway::Subscription;
1314
15/// 网关支持订阅的 (项目前缀, area, tag) 全集:本仓库事件来自 `EventKind`,
16/// kenshell 事件由对端仓库产生,这里是唯一的外来清单。
17const KENSHELL: &[(&str, &str)] = &[
18 ("repo", "create"),
19 ("repo", "update"),
20 ("repo", "delete"),
21 ("repo", "placement-change"),
22 ("repo", "language-update"),
23 ("ref", "create-branch"),
24 ("ref", "update-branch"),
25 ("ref", "delete-branch"),
26 ("ref", "rename-branch"),
27 ("ref", "set-default-branch"),
28 ("ref", "create-tag"),
29 ("ref", "delete-tag"),
30 ("ref", "create-protection"),
31 ("ref", "delete-protection"),
32 ("commit", "sync"),
33 ("commit", "set-status"),
34 ("commit", "comment-create"),
35 ("commit", "comment-update"),
36 ("commit", "comment-delete"),
37 ("lfs", "upload"),
38 ("lfs", "unreference"),
39 ("lfs", "purge"),
40 ("archive", "create"),
41 ("job", "enqueue"),
42 ("job", "claim"),
43 ("job", "progress"),
44 ("job", "heartbeat"),
45 ("job", "cancel"),
46 ("job", "retry"),
47 ("job", "finish"),
48 ("node", "create"),
49 ("node", "update"),
50 ("node", "delete"),
51 ("node", "probe"),
52 ("volume", "create"),
53 ("volume", "update"),
54 ("volume", "delete"),
55 ("volume", "mount"),
56 ("volume", "unmount"),
57 ("volume", "probe"),
58];
59
60pub(crate) fn catalog() -> impl Iterator<Item = (&'static str, &'static str, &'static str)> {
61 EventKind::ALL
62 .iter()
63 .flat_map(|kind| {
64 kind.tags()
65 .iter()
66 .map(move |tag| ("gitdata", kind.topic(), *tag))
67 })
68 .chain(KENSHELL.iter().map(|(area, tag)| ("kenshell", *area, *tag)))
69}
70
71pub(crate) fn split_topic(topic: &str) -> Option<(&str, &str)> {
72 config::event::consumer::DEFAULT_TOPIC_PREFIXES
73 .iter()
74 .find_map(|prefix| {
75 topic
76 .strip_prefix(prefix)
77 .and_then(|rest| rest.strip_prefix('-'))
78 .map(|area| (*prefix, area))
79 })
80}
81
82pub(crate) fn identity_area(area: &str) -> bool {
83 EventKind::ALL
84 .iter()
85 .any(|kind| kind.topic() == area && kind.identity())
86}
87
88pub(crate) fn personal(area: &str, tag: &str) -> bool {
89 EventKind::ALL
90 .iter()
91 .any(|kind| kind.topic() == area && kind.personal() && kind.tags().contains(&tag))
92}
93
1494/// 与 MQ 扇出一致的下行 payload 形状(可选附带命中的订阅列表)。
1595pub fn common_payload(
1696 topic: &str,
Modified lib/share/events/src/gateway/action/platform_events.rs lib/share/events/src/gateway/action/platform_events.rs +9 −49 View lib/share/events/src/gateway/action/platform_events.rs
@@ -17,59 +17,19 @@
1717 return unimplemented(message);
1818 }
1919
20 if topic == "gitdata-org" {
21 deliver_org(topic, tag, message, properties, context).await
22 } else {
23 deliver_user(topic, tag, message, properties, context).await
20 match super::split_topic(topic) {
21 Some((_, "org")) => deliver_org(topic, tag, message, properties, context).await,
22 _ => deliver_user(topic, tag, message, properties, context).await,
2423 }
2524}
2625
2726pub fn is_supported(topic: &str, tag: &str) -> bool {
28 matches!(
29 (topic, tag),
30 ("gitdata-auth", "login")
31 | ("gitdata-auth", "logout")
32 | ("gitdata-auth", "send-code")
33 | ("gitdata-auth", "register")
34 | ("gitdata-auth", "request-reset")
35 | ("gitdata-auth", "reset-password")
36 | ("gitdata-auth", "add-sshkey")
37 | ("gitdata-auth", "delete-sshkey")
38 | ("gitdata-auth", "create-accesskey")
39 | ("gitdata-auth", "rotate-accesskey")
40 | ("gitdata-auth", "delete-accesskey")
41 | ("gitdata-auth", "use-accesskey")
42 | ("gitdata-auth", "issue-token")
43 | ("gitdata-auth", "challenge")
44 | ("gitdata-auth", "verify-challenge")
45 | ("gitdata-auth", "delivery")
46 | ("gitdata-auth", "failure")
47 | ("gitdata-org", "create")
48 | ("gitdata-org", "update")
49 | ("gitdata-org", "archive")
50 | ("gitdata-org", "restore")
51 | ("gitdata-org", "delete")
52 | ("gitdata-org", "transfer-owner")
53 | ("gitdata-org", "invite")
54 | ("gitdata-org", "revoke-invite")
55 | ("gitdata-org", "redeem-invite")
56 | ("gitdata-org", "add-member")
57 | ("gitdata-org", "remove-member")
58 | ("gitdata-org", "update-member")
59 | ("gitdata-org", "set-role")
60 | ("gitdata-org", "ban")
61 | ("gitdata-org", "unban")
62 | ("gitdata-org", "update-ban")
63 | ("gitdata-org", "join")
64 | ("gitdata-org", "leave")
65 | ("gitdata-org", "create-role")
66 | ("gitdata-org", "set-permissions")
67 | ("gitdata-org", "update-role")
68 | ("gitdata-org", "delete-role")
69 | ("gitdata-org", "assign-role")
70 | ("gitdata-org", "unassign-role")
71 | ("gitdata-org", "grant")
72 )
27 let Some((prefix, area)) = super::split_topic(topic) else {
28 return false;
29 };
30 prefix == "gitdata"
31 && super::identity_area(area)
32 && super::catalog().any(|(_, known_area, known_tag)| known_area == area && known_tag == tag)
7333}
7434
7535async fn deliver_user(
Modified lib/share/events/src/gateway/pipe.rs lib/share/events/src/gateway/pipe.rs +2 −6 View lib/share/events/src/gateway/pipe.rs
@@ -1,7 +1,7 @@
11use rocketmq_client_rust::consumer::default_mq_push_consumer::DefaultMQPushConsumer;
22use rocketmq_client_rust::consumer::mq_push_consumer::MQPushConsumer;
3use rocketmq_common::RocketMQResult;
34use rocketmq_common::common::message::message_ext::MessageExt;
4use rocketmq_common::{RocketMQError, RocketMQResult};
55use std::collections::{HashMap, VecDeque};
66use std::sync::mpsc::SyncSender;
77use std::time::Duration;
@@ -94,11 +94,7 @@
9494 }
9595
9696 pub async fn subscribe_all(&mut self) -> RocketMQResult<()> {
97 let plan = plan::plan(&self.prefixes).map_err(|error| {
98 RocketMQError::illegal_argument(format!("invalid docs/events.json: {error}"))
99 })?;
100
101 for (topic, expression) in plan {
97 for (topic, expression) in plan::plan(&self.prefixes) {
10298 self.consumer.subscribe(topic, expression).await?;
10399 }
104100
Modified lib/share/events/src/gateway/plan.rs lib/share/events/src/gateway/plan.rs +12 −28 View lib/share/events/src/gateway/plan.rs
@@ -1,37 +1,21 @@
11use std::collections::BTreeMap;
22
3use serde::Deserialize;
3use crate::gateway::action;
44
5#[derive(Deserialize)]
6pub struct EventDocs {
7 events: Vec<EventDoc>,
8}
9
10#[derive(Deserialize)]
11pub struct EventDoc {
12 topic: String,
13 tag: String,
14}
15
16pub fn plan(prefixes: &[String]) -> Result<BTreeMap<String, String>, serde_json::Error> {
17 let docs: EventDocs = serde_json::from_str(include_str!("../../data/events.json"))?;
18
19 let mut subscriptions: BTreeMap<&str, Vec<&str>> = BTreeMap::new();
20 for event in &docs.events {
21 if !prefixes
22 .iter()
23 .any(|prefix| event.topic.starts_with(&format!("{prefix}-")))
24 {
5/// 订阅表达式:topic → `tag1 || tag2 ...`,与投递端共用同一份目录。
6pub fn plan(prefixes: &[String]) -> BTreeMap<String, String> {
7 let mut topics: BTreeMap<String, Vec<&'static str>> = BTreeMap::new();
8 for (prefix, area, tag) in action::catalog() {
9 if !prefixes.iter().any(|configured| configured == prefix) {
2510 continue;
2611 }
27 let tags = subscriptions.entry(event.topic.as_str()).or_default();
28 if !tags.contains(&event.tag.as_str()) {
29 tags.push(event.tag.as_str());
12 let tags = topics.entry(format!("{prefix}-{area}")).or_default();
13 if !tags.contains(&tag) {
14 tags.push(tag);
3015 }
3116 }
32
33 Ok(subscriptions
17 topics
3418 .into_iter()
35 .map(|(topic, tags)| (topic.to_string(), tags.join(" || ")))
36 .collect())
19 .map(|(topic, tags)| (topic, tags.join(" || ")))
20 .collect()
3721}
Modified lib/share/events/src/gateway/subscriptions.rs lib/share/events/src/gateway/subscriptions.rs +4 −18 View lib/share/events/src/gateway/subscriptions.rs
@@ -38,12 +38,7 @@
3838 }
3939
4040 fn validate(topic: &str, tag: &str) -> Result<(), String> {
41 let prefixes = DEFAULT_TOPIC_PREFIXES.map(str::to_string);
42 let plan = plan::plan(&prefixes).map_err(|error| error.to_string())?;
43 if !plan
44 .get(topic)
45 .is_some_and(|tags| tags.split(" || ").any(|known| known == tag))
46 {
41 if !crate::gateway::action::is_supported(topic, tag) {
4742 return Err("unsupported subscription topic or tag".to_string());
4843 }
4944 Ok(())
@@ -257,16 +252,8 @@
257252}
258253
259254pub fn is_personal(topic: &str, tag: &str) -> bool {
260 topic == "gitdata-notify"
261 || (topic == "gitdata-chat"
262 && matches!(
263 tag,
264 "mention-create"
265 | "reminder-create"
266 | "membership-create"
267 | "read_state-update"
268 | "presence-update"
269 ))
255 crate::gateway::action::split_topic(topic)
256 .is_some_and(|(_, area)| crate::gateway::action::personal(area, tag))
270257}
271258
272259fn base(topic: &str, tag: &str) -> String {
@@ -385,10 +372,9 @@
385372 namespace: &str,
386373 ) -> Result<Vec<Subscription>, String> {
387374 let prefixes = DEFAULT_TOPIC_PREFIXES.map(str::to_string);
388 let plan = plan::plan(&prefixes).map_err(|error| error.to_string())?;
389375 let owner = owner(user_id, device_id, namespace);
390376 let mut out = Vec::new();
391 for (topic, tags) in plan {
377 for (topic, tags) in plan::plan(&prefixes) {
392378 for tag in tags.split(" || ") {
393379 let base = base(&topic, tag);
394380 let mut command = redis::cmd("SMEMBERS");
Modified lib/share/events/src/emit.rs lib/share/events/src/emit.rs +2 −2 View lib/share/events/src/emit.rs
@@ -4,11 +4,11 @@
44use serde_json::Value;
55use uuid::Uuid;
66
7use crate::{Labels, label};
7use crate::{EventKind, Labels, label};
88
99pub async fn emit(
1010 tx: &impl ConnectionTrait,
11 kind: &str,
11 kind: EventKind,
1212 mut labels: Labels,
1313 payload: impl Serialize,
1414) -> Result<Uuid, DbErr> {
Modified lib/share/events/src/handler.rs lib/share/events/src/handler.rs +14 −39 View lib/share/events/src/handler.rs
@@ -1,20 +1,8 @@
11use serde::Serialize;
22
3use crate::{label, topic};
3use crate::EventKind;
44use config::rocketmq::RocketMqConfig;
55use queue::{EVENT_PREFIX, Envelope, Labels, Publisher, QueueError, QueueResult, RocketMqConn};
6
7pub const DURABLE_DOMAINS: &[&str] = &[
8 "auth",
9 "email",
10 "guild",
11 "chat",
12 "git",
13 "issue",
14 "notify",
15 "organization",
16 "pull",
17];
186
197#[derive(Clone, Debug)]
208pub struct EventHandler {
@@ -40,14 +28,14 @@
4028 }
4129 }
4230
43 pub fn envelope<T>(&self, kind: &str, payload: T) -> Envelope<T> {
44 Envelope::new(kind, payload)
31 pub fn envelope<T>(&self, kind: EventKind, payload: T) -> Envelope<T> {
32 Envelope::new(kind.as_str(), payload)
4533 }
4634
47 #[tracing::instrument(skip_all, fields(kind = kind, request_id = %envelope::context::resolve_request_id(request_id)))]
35 #[tracing::instrument(skip_all, fields(kind = %kind, request_id = %envelope::context::resolve_request_id(request_id)))]
4836 pub async fn send_labeled<T>(
4937 &self,
50 kind: &str,
38 kind: EventKind,
5139 labels: Labels,
5240 payload: T,
5341 request_id: Option<&str>,
@@ -59,10 +47,10 @@
5947 .await
6048 }
6149
62 #[tracing::instrument(skip_all, fields(kind = kind, request_id = %envelope::context::resolve_request_id(request_id)))]
50 #[tracing::instrument(skip_all, fields(kind = %kind, request_id = %envelope::context::resolve_request_id(request_id)))]
6351 pub async fn send_keyed<T>(
6452 &self,
65 kind: &str,
53 kind: EventKind,
6654 labels: Labels,
6755 idempotency_key: Option<String>,
6856 payload: T,
@@ -73,7 +61,7 @@
7361 {
7462 let mut envelope = self
7563 .envelope(kind, payload)
76 .with_source(domain_of(kind))
64 .with_source(kind.domain())
7765 .with_labels(labels);
7866 if let Some(key) = idempotency_key {
7967 envelope = envelope.with_idempotency_key(key);
@@ -124,17 +112,16 @@
124112 envelope: &Envelope<T>,
125113 request_id: Option<&str>,
126114 ) -> QueueResult<()> {
127 ensure_kind(&envelope.kind)?;
115 let kind = concrete_kind(&envelope.kind)?;
128116 let payload = serde_json::to_value(&envelope.payload).map_err(QueueError::from)?;
129 let area = topic::of(&envelope.kind);
130 let tag = label::tag(&envelope.kind, Some(&payload));
117 let tag = kind.tag(Some(&payload));
131118 let request_id = envelope::context::request_id().or_else(|| {
132119 request_id
133120 .filter(|request_id| !request_id.is_empty())
134121 .map(str::to_owned)
135122 });
136123 let properties = envelope.properties(Some(&payload), request_id.as_deref());
137 let topic = self.publisher.topic(area);
124 let topic = self.publisher.topic(kind.topic());
138125 let body = envelope.encode()?;
139126
140127 self.publisher
@@ -143,19 +130,7 @@
143130 }
144131}
145132
146fn ensure_kind(kind: &str) -> QueueResult<()> {
147 let mut parts = kind.split('.');
148 let domain = parts.next().unwrap_or_default();
149 let name = parts.next().unwrap_or_default();
150 let operate = parts.next().unwrap_or_default();
151 if domain.is_empty() || name.is_empty() || operate.is_empty() {
152 return Err(QueueError::payload(format!(
153 "event kind `{kind}` is not a concrete event"
154 )));
155 }
156 Ok(())
157}
158
159fn domain_of(kind: &str) -> &str {
160 kind.split('.').next().unwrap_or_default()
133pub(crate) fn concrete_kind(kind: &str) -> QueueResult<EventKind> {
134 EventKind::parse(kind)
135 .ok_or_else(|| QueueError::payload(format!("event kind `{kind}` is not a declared event")))
161136}
Added lib/share/events/src/kind.rs lib/share/events/src/kind.rs +324 −0 View lib/share/events/src/kind.rs
@@ -0,0 +1,324 @@
1use queue::Labels;
2use sea_orm::DbErr;
3use serde_json::Value;
4
5use crate::label;
6
7macro_rules! event_kinds {
8 ($($variant:ident: $kind:literal @ $topic:literal => $tag:literal;)*) => {
9 /// 业务事件的唯一真源:wire kind、topic area、tag、durable 与串行化粒度都从这里派生。
10 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
11 pub enum EventKind {
12 $($variant,)*
13 }
14
15 impl EventKind {
16 pub const ALL: &'static [EventKind] = &[$(EventKind::$variant,)*];
17
18 pub const fn as_str(self) -> &'static str {
19 match self {
20 $(Self::$variant => $kind,)*
21 }
22 }
23
24 pub fn parse(kind: &str) -> Option<Self> {
25 match kind {
26 $($kind => Some(Self::$variant),)*
27 _ => None,
28 }
29 }
30
31 /// RocketMQ topic 的 area 段:实际 topic 是 `<prefix>-<area>`。
32 pub const fn topic(self) -> &'static str {
33 match self {
34 $(Self::$variant => $topic,)*
35 }
36 }
37
38 fn only_tag(self) -> &'static [&'static str] {
39 match self {
40 $(Self::$variant => &[$tag],)*
41 }
42 }
43 }
44 };
45}
46
47event_kinds! {
48 AuthRegisterCreate: "auth.register.create" @ "auth" => "register";
49 AuthLoginCreate: "auth.login.create" @ "auth" => "login";
50 AuthLogoutDelete: "auth.logout.delete" @ "auth" => "logout";
51 AuthVerificationTokenCreate: "auth.verification_token.create" @ "auth" => "send-code";
52 AuthPasswordCreate: "auth.password.create" @ "auth" => "request-reset";
53 AuthPasswordUpdate: "auth.password.update" @ "auth" => "reset-password";
54 AuthSshKeyCreate: "auth.ssh_key.create" @ "auth" => "add-sshkey";
55 AuthSshKeyDelete: "auth.ssh_key.delete" @ "auth" => "delete-sshkey";
56 AuthAccessKeyCreate: "auth.access_key.create" @ "auth" => "create-accesskey";
57 AuthAccessKeyDelete: "auth.access_key.delete" @ "auth" => "delete-accesskey";
58 AuthAccessKeyUsed: "auth.access_key.used" @ "auth" => "use-accesskey";
59 AuthAppTokenCreate: "auth.app_token.create" @ "auth" => "issue-token";
60 AuthTokenCreate: "auth.token.create" @ "auth" => "issue-token";
61 AuthChallengeCreate: "auth.challenge.create" @ "auth" => "challenge";
62 AuthChallengeUsed: "auth.challenge.used" @ "auth" => "verify-challenge";
63
64 EmailDeliveryCreate: "email.delivery.create" @ "auth" => "delivery";
65 EmailFailureCreate: "email.failure.create" @ "auth" => "failure";
66
67 OrganizationLifeCycleCreate: "organization.life_cycle.create" @ "org" => "create";
68 OrganizationLifeCycleUpdate: "organization.life_cycle.update" @ "org" => "update";
69 OrganizationLifeCycleDelete: "organization.life_cycle.delete" @ "org" => "delete";
70 OrganizationInviteCreate: "organization.invite.create" @ "org" => "invite";
71 OrganizationMemberUpdate: "organization.member.update" @ "org" => "update-member";
72 OrganizationRoleUpdate: "organization.role.update" @ "org" => "grant";
73
74 ChatMessageCreate: "chat.message.create" @ "chat" => "message-create";
75 ChatMessageUpdate: "chat.message.update" @ "chat" => "message-update";
76 ChatMessageDelete: "chat.message.delete" @ "chat" => "message-delete";
77 ChatMessagesDelete: "chat.messages.delete" @ "chat" => "messages-delete";
78 ChatPinCreate: "chat.pin.create" @ "chat" => "pin-create";
79 ChatPinDelete: "chat.pin.delete" @ "chat" => "pin-delete";
80 ChatReactionUpdate: "chat.reaction.update" @ "chat" => "reaction-update";
81 ChatTypingCreate: "chat.typing.create" @ "chat" => "typing-create";
82 ChatRoomUpdate: "chat.room.update" @ "chat" => "room-update";
83 ChatMemberCreate: "chat.member.create" @ "chat" => "member-create";
84 ChatMemberDelete: "chat.member.delete" @ "chat" => "member-delete";
85 ChatThreadUpdate: "chat.thread.update" @ "chat" => "thread-update";
86 ChatVoicePresence: "chat.voice.presence" @ "chat" => "voice-presence";
87 ChatVoiceHand: "chat.voice.hand" @ "chat" => "voice-hand";
88 ChatVoiceLayout: "chat.voice.layout" @ "chat" => "voice-layout";
89 ChatVoiceModerate: "chat.voice.moderate" @ "chat" => "voice-moderate";
90 ChatMentionCreate: "chat.mention.create" @ "chat" => "mention-create";
91 ChatReminderCreate: "chat.reminder.create" @ "chat" => "reminder-create";
92 ChatMembershipCreate: "chat.membership.create" @ "chat" => "membership-create";
93 ChatReadStateUpdate: "chat.read_state.update" @ "chat" => "read_state-update";
94 ChatPresenceUpdate: "chat.presence.update" @ "chat" => "presence-update";
95
96 NotifyNotificationCreate: "notify.notification.create" @ "notify" => "notification-create";
97
98 IssueIssueCreate: "issue.issue.create" @ "issue" => "issue-create";
99 IssueTitleUpdate: "issue.title.update" @ "issue" => "title-update";
100 IssueDescriptionUpdate: "issue.description.update" @ "issue" => "description-update";
101 IssueContentUpdate: "issue.content.update" @ "issue" => "content-update";
102 IssueMilestoneUpdate: "issue.milestone.update" @ "issue" => "milestone-update";
103 IssuePriorityUpdate: "issue.priority.update" @ "issue" => "priority-update";
104 IssueDueUpdate: "issue.due.update" @ "issue" => "due-update";
105 IssueIssueClose: "issue.issue.close" @ "issue" => "issue-close";
106 IssueIssueReopen: "issue.issue.reopen" @ "issue" => "issue-reopen";
107 IssueCommentCreate: "issue.comment.create" @ "issue" => "comment-create";
108 IssueCommentUpdate: "issue.comment.update" @ "issue" => "comment-update";
109 IssueCommentDelete: "issue.comment.delete" @ "issue" => "comment-delete";
110 IssueMemberCreate: "issue.member.create" @ "issue" => "member-create";
111 IssueMemberDelete: "issue.member.delete" @ "issue" => "member-delete";
112 IssueLabelCreate: "issue.label.create" @ "issue" => "label-create";
113 IssueLabelDelete: "issue.label.delete" @ "issue" => "label-delete";
114 IssueDependencyCreate: "issue.dependency.create" @ "issue" => "dependency-create";
115 IssueDependencyDelete: "issue.dependency.delete" @ "issue" => "dependency-delete";
116 IssuePinCreate: "issue.pin.create" @ "issue" => "pin-create";
117 IssuePinDelete: "issue.pin.delete" @ "issue" => "pin-delete";
118 IssueRepoRelationCreate: "issue.repo-relation.create" @ "issue" => "repo-relation-create";
119 IssueRepoRelationDelete: "issue.repo-relation.delete" @ "issue" => "repo-relation-delete";
120 IssuePullLinkCreate: "issue.pull-link.create" @ "issue" => "pull-link-create";
121 IssuePullLinkDelete: "issue.pull-link.delete" @ "issue" => "pull-link-delete";
122
123 PullPullCreate: "pull.pull.create" @ "pull" => "pull-create";
124 PullPullUpdate: "pull.pull.update" @ "pull" => "pull-update";
125 PullPullClose: "pull.pull.close" @ "pull" => "pull-close";
126 PullPullReopen: "pull.pull.reopen" @ "pull" => "pull-reopen";
127 PullPullMerge: "pull.pull.merge" @ "pull" => "pull-merge";
128 PullCommentCreate: "pull.comment.create" @ "pull" => "comment-create";
129 PullReviewCreate: "pull.review.create" @ "pull" => "review-create";
130 PullReviewDelete: "pull.review.delete" @ "pull" => "review-delete";
131 PullReviewRequestCreate: "pull.review-request.create" @ "pull" => "review-request-create";
132 PullReviewRequestDelete: "pull.review-request.delete" @ "pull" => "review-request-delete";
133 PullHeadUpdate: "pull.head.update" @ "pull" => "head-update";
134 PullLabelCreate: "pull.label.create" @ "pull" => "label-create";
135 PullLabelDelete: "pull.label.delete" @ "pull" => "label-delete";
136 PullAssigneeCreate: "pull.assignee.create" @ "pull" => "assignee-create";
137 PullAssigneeDelete: "pull.assignee.delete" @ "pull" => "assignee-delete";
138 PullLockCreate: "pull.lock.create" @ "pull" => "lock-create";
139 PullLockDelete: "pull.lock.delete" @ "pull" => "lock-delete";
140 PullAutoMergeCreate: "pull.auto-merge.create" @ "pull" => "auto-merge-create";
141 PullAutoMergeDelete: "pull.auto-merge.delete" @ "pull" => "auto-merge-delete";
142
143 DirCreate: "dir.create" @ "dir" => "create";
144 DirUpdate: "dir.update" @ "dir" => "update";
145 DirArchive: "dir.archive" @ "dir" => "archive";
146}
147
148const ORGANIZATION_MEMBER_TAGS: &[&str] = &[
149 "update-member",
150 "join",
151 "leave",
152 "add-member",
153 "remove-member",
154 "set-role",
155 "ban",
156 "unban",
157 "update-ban",
158 "transfer-owner",
159];
160
161const ORGANIZATION_ROLE_TAGS: &[&str] = &[
162 "grant",
163 "create-role",
164 "set-permissions",
165 "update-role",
166 "delete-role",
167 "assign-role",
168 "unassign-role",
169];
170
171impl EventKind {
172 /// wire kind 的域名段,也是 envelope 的 `source`。
173 pub fn domain(self) -> &'static str {
174 self.as_str().split('.').next().unwrap_or_default()
175 }
176
177 /// 该 kind 可能出现的全部 tag,首个是默认值;订阅表达式由此生成。
178 pub fn tags(self) -> &'static [&'static str] {
179 match self {
180 Self::AuthRegisterCreate => &["register", "send-code"],
181 Self::OrganizationLifeCycleUpdate => &["update", "archive", "restore"],
182 Self::OrganizationInviteCreate => &["invite", "revoke-invite", "redeem-invite"],
183 Self::OrganizationMemberUpdate => ORGANIZATION_MEMBER_TAGS,
184 Self::OrganizationRoleUpdate => ORGANIZATION_ROLE_TAGS,
185 _ => self.only_tag(),
186 }
187 }
188
189 /// 实际投递用的 tag:多数 kind 与其他 tag 无关,少数按 payload 细分。
190 pub fn tag(self, payload: Option<&Value>) -> &'static str {
191 match self {
192 Self::AuthRegisterCreate if has_field(payload, "expires_in_secs") => "send-code",
193 Self::OrganizationLifeCycleUpdate => match action(payload) {
194 Some("archive") => "archive",
195 Some("restore") => "restore",
196 _ => "update",
197 },
198 Self::OrganizationInviteCreate => match action(payload) {
199 Some("revoke") => "revoke-invite",
200 Some("redeem") => "redeem-invite",
201 _ => "invite",
202 },
203 Self::OrganizationMemberUpdate => match action(payload) {
204 Some("join_role") => "join",
205 Some("leave_role") => "leave",
206 Some("add") => "add-member",
207 Some("remove") => "remove-member",
208 Some("set_role") => "set-role",
209 Some("ban") => "ban",
210 Some("unban") => "unban",
211 Some("update_ban") => "update-ban",
212 Some("transfer_ownership") => "transfer-owner",
213 _ => "update-member",
214 },
215 Self::OrganizationRoleUpdate => match action(payload) {
216 Some("create") => "create-role",
217 Some("set_permissions") => "set-permissions",
218 Some("update") => "update-role",
219 Some("delete") => "delete-role",
220 Some("assign") => "assign-role",
221 Some("unassign") => "unassign-role",
222 _ => "grant",
223 },
224 other => other.tags()[0],
225 }
226 }
227
228 /// 是否允许写入 outbox:只有持久业务域的事件才进 MQ。
229 pub fn durable(self) -> bool {
230 matches!(
231 self.topic(),
232 "auth" | "org" | "chat" | "notify" | "issue" | "pull" | "dir"
233 )
234 }
235
236 /// 身份域事件走身份 publisher(独立的连接与消费组)。
237 pub fn identity(self) -> bool {
238 matches!(self.domain(), "auth" | "email" | "organization")
239 }
240
241 /// 只投递给单个用户的个人事件族(不走资源广播)。
242 pub const fn personal(self) -> bool {
243 matches!(
244 self,
245 Self::NotifyNotificationCreate
246 | Self::ChatMentionCreate
247 | Self::ChatReminderCreate
248 | Self::ChatMembershipCreate
249 | Self::ChatReadStateUpdate
250 | Self::ChatPresenceUpdate
251 )
252 }
253
254 /// 串行化粒度:同一 `ordering_key` 的事件严格按写入顺序投递。
255 pub fn ordering_key(self, labels: &Labels) -> Result<String, DbErr> {
256 if let Some(explicit) = labels
257 .get(label::ORDERING_KEY)
258 .filter(|key| !key.is_empty())
259 {
260 return Ok(explicit.to_string());
261 }
262 let domain = self.domain();
263 let key = match domain {
264 "chat" => {
265 if let Some(uid) = labels.get(label::RECIPIENT_USER_ID) {
266 return Ok(format!("chat:user:{uid}"));
267 }
268 if let Some(room) = labels.get(label::ROOM_ID) {
269 return Ok(format!("chat:room:{room}"));
270 }
271 labels.get(label::USER_ID)
272 }
273 "notify" => {
274 if let Some(uid) = labels
275 .get(label::RECIPIENT_USER_ID)
276 .or_else(|| labels.get(label::USER_ID))
277 {
278 return Ok(format!("notify:user:{uid}"));
279 }
280 None
281 }
282 "pull" => labels
283 .get("pull_id")
284 .or_else(|| labels.get(label::PULL_REQUEST_ID))
285 .or_else(|| labels.get(label::REPOSITORY_ID)),
286 "issue" => labels
287 .get(label::ISSUE_ID)
288 .or_else(|| labels.get(label::BOARD_ID))
289 .or_else(|| labels.get(label::PULL_REQUEST_ID))
290 .or_else(|| labels.get(label::GUILD_ID)),
291 "organization" => labels.get(label::ORGANIZATION_ID),
292 "auth" => labels
293 .get(label::USER_ID)
294 .or_else(|| labels.get(label::REQUEST_ID)),
295 "email" => labels.get(label::REQUEST_ID),
296 "dir" => labels.get(label::DIR_ID),
297 _ => None,
298 };
299 key.filter(|key| !key.is_empty())
300 .map(|key| format!("{domain}:{key}"))
301 .ok_or_else(|| {
302 DbErr::Custom(format!(
303 "event `{}` requires a user, organization or request id label",
304 self.as_str()
305 ))
306 })
307 }
308}
309
310impl std::fmt::Display for EventKind {
311 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312 f.write_str(self.as_str())
313 }
314}
315
316fn action(payload: Option<&Value>) -> Option<&str> {
317 payload
318 .and_then(|value| value.get("action"))
319 .and_then(Value::as_str)
320}
321
322fn has_field(payload: Option<&Value>, key: &str) -> bool {
323 payload.is_some_and(|value| value.get(key).is_some_and(|value| !value.is_null()))
324}
Modified lib/share/events/src/label.rs lib/share/events/src/label.rs +18 −138 View lib/share/events/src/label.rs
@@ -1,5 +1,3 @@
1use serde_json::Value;
2
31pub const RECIPIENT_USER_ID: &str = "recipient_user_id";
42pub const USER_ID: &str = "user_id";
53pub const ACTOR_USER_ID: &str = "actor_user_id";
@@ -15,140 +13,22 @@
1513pub const DIR_ID: &str = "dir_id";
1614pub const ORDERING_KEY: &str = "ordering_key";
1715
18pub const AUTH_REGISTER_CREATE: &str = "auth.register.create";
19pub const AUTH_LOGIN_CREATE: &str = "auth.login.create";
20pub const AUTH_LOGOUT_DELETE: &str = "auth.logout.delete";
21pub const AUTH_VERIFICATION_TOKEN_CREATE: &str = "auth.verification_token.create";
22pub const AUTH_PASSWORD_CREATE: &str = "auth.password.create";
23pub const AUTH_PASSWORD_UPDATE: &str = "auth.password.update";
24pub const AUTH_SSH_KEY_CREATE: &str = "auth.ssh_key.create";
25pub const AUTH_SSH_KEY_DELETE: &str = "auth.ssh_key.delete";
26pub const AUTH_ACCESS_KEY_CREATE: &str = "auth.access_key.create";
27pub const AUTH_ACCESS_KEY_DELETE: &str = "auth.access_key.delete";
28pub const AUTH_ACCESS_KEY_USED: &str = "auth.access_key.used";
29pub const AUTH_APP_TOKEN_CREATE: &str = "auth.app_token.create";
30pub const AUTH_TOKEN_CREATE: &str = "auth.token.create";
31pub const AUTH_CHALLENGE_CREATE: &str = "auth.challenge.create";
32pub const AUTH_CHALLENGE_USED: &str = "auth.challenge.used";
33pub const ORGANIZATION_LIFE_CYCLE_CREATE: &str = "organization.life_cycle.create";
34pub const ORGANIZATION_LIFE_CYCLE_UPDATE: &str = "organization.life_cycle.update";
35pub const ORGANIZATION_LIFE_CYCLE_DELETE: &str = "organization.life_cycle.delete";
36pub const ORGANIZATION_INVITE_CREATE: &str = "organization.invite.create";
37pub const ORGANIZATION_MEMBER_UPDATE: &str = "organization.member.update";
38pub const ORGANIZATION_ROLE_UPDATE: &str = "organization.role.update";
39pub const EMAIL_DELIVERY_CREATE: &str = "email.delivery.create";
40pub const EMAIL_FAILURE_CREATE: &str = "email.failure.create";
16pub use crate::kind::EventKind;
4117
42pub const REGISTER: &str = "register";
43pub const SEND_CODE: &str = "send-code";
44pub const LOGIN: &str = "login";
45pub const LOGOUT: &str = "logout";
46pub const REQUEST_RESET: &str = "request-reset";
47pub const RESET_PASSWORD: &str = "reset-password";
48pub const ADD_SSH_KEY: &str = "add-sshkey";
49pub const DELETE_SSH_KEY: &str = "delete-sshkey";
50pub const CREATE_ACCESS_KEY: &str = "create-accesskey";
51pub const DELETE_ACCESS_KEY: &str = "delete-accesskey";
52pub const USE_ACCESS_KEY: &str = "use-accesskey";
53pub const CHALLENGE: &str = "challenge";
54pub const VERIFY_CHALLENGE: &str = "verify-challenge";
55pub const ISSUE_TOKEN: &str = "issue-token";
56pub const DELIVERY: &str = "delivery";
57pub const FAILURE: &str = "failure";
58pub const CREATE: &str = "create";
59pub const UPDATE: &str = "update";
60pub const DELETE: &str = "delete";
61pub const ARCHIVE: &str = "archive";
62pub const RESTORE: &str = "restore";
63pub const INVITE: &str = "invite";
64pub const REVOKE_INVITE: &str = "revoke-invite";
65pub const REDEEM_INVITE: &str = "redeem-invite";
66pub const JOIN: &str = "join";
67pub const LEAVE: &str = "leave";
68pub const ADD_MEMBER: &str = "add-member";
69pub const REMOVE_MEMBER: &str = "remove-member";
70pub const SET_ROLE: &str = "set-role";
71pub const BAN: &str = "ban";
72pub const UNBAN: &str = "unban";
73pub const UPDATE_BAN: &str = "update-ban";
74pub const TRANSFER_OWNER: &str = "transfer-owner";
75pub const UPDATE_MEMBER: &str = "update-member";
76pub const CREATE_ROLE: &str = "create-role";
77pub const SET_PERMISSIONS: &str = "set-permissions";
78pub const UPDATE_ROLE: &str = "update-role";
79pub const DELETE_ROLE: &str = "delete-role";
80pub const ASSIGN_ROLE: &str = "assign-role";
81pub const UNASSIGN_ROLE: &str = "unassign-role";
82pub const GRANT: &str = "grant";
83
84pub fn tag<'a>(kind: &'a str, payload: Option<&Value>) -> &'a str {
85 let mut parts = kind.split('.');
86 let domain = parts.next().unwrap_or_default();
87 let name = parts.next().unwrap_or_default();
88 let operate = parts.next().unwrap_or_default();
89 let action = payload
90 .and_then(|value| value.get("action"))
91 .and_then(Value::as_str);
92 match (domain, name, operate) {
93 ("auth", "register", "create") => {
94 if has_field(payload, "expires_in_secs") {
95 SEND_CODE
96 } else {
97 REGISTER
98 }
99 }
100 ("auth", "login", "create") => LOGIN,
101 ("auth", "logout", "delete") => LOGOUT,
102 ("auth", "verification_token", "create") => SEND_CODE,
103 ("auth", "password", "create") => REQUEST_RESET,
104 ("auth", "password", "update") => RESET_PASSWORD,
105 ("auth", "ssh_key", "create") => ADD_SSH_KEY,
106 ("auth", "ssh_key", "delete") => DELETE_SSH_KEY,
107 ("auth", "access_key", "create") => CREATE_ACCESS_KEY,
108 ("auth", "access_key", "delete") => DELETE_ACCESS_KEY,
109 ("auth", "access_key", "used") => USE_ACCESS_KEY,
110 ("auth", "challenge", "create") => CHALLENGE,
111 ("auth", "challenge", "used") => VERIFY_CHALLENGE,
112 ("auth", "app_token", "create") | ("auth", "token", "create") => ISSUE_TOKEN,
113 ("email", "delivery", _) => DELIVERY,
114 ("email", "failure", _) => FAILURE,
115 ("organization", "life_cycle", "create") => CREATE,
116 ("organization", "life_cycle", "update") => match action {
117 Some("archive") => ARCHIVE,
118 Some("restore") => RESTORE,
119 _ => UPDATE,
120 },
121 ("organization", "life_cycle", "delete") => DELETE,
122 ("organization", "invite", "create") => match action {
123 Some("revoke") => REVOKE_INVITE,
124 Some("redeem") => REDEEM_INVITE,
125 _ => INVITE,
126 },
127 ("organization", "member", "update") => match action {
128 Some("join_role") => JOIN,
129 Some("leave_role") => LEAVE,
130 Some("add") => ADD_MEMBER,
131 Some("remove") => REMOVE_MEMBER,
132 Some("set_role") => SET_ROLE,
133 Some("ban") => BAN,
134 Some("unban") => UNBAN,
135 Some("update_ban") => UPDATE_BAN,
136 Some("transfer_ownership") => TRANSFER_OWNER,
137 _ => UPDATE_MEMBER,
138 },
139 ("organization", "role", "update") => match action {
140 Some("create") => CREATE_ROLE,
141 Some("set_permissions") => SET_PERMISSIONS,
142 Some("update") => UPDATE_ROLE,
143 Some("delete") => DELETE_ROLE,
144 Some("assign") => ASSIGN_ROLE,
145 Some("unassign") => UNASSIGN_ROLE,
146 _ => GRANT,
147 },
148 (_, _, operate) => operate,
149 }
150}
151
152fn has_field(payload: Option<&Value>, key: &str) -> bool {
153 payload.is_some_and(|value| value.get(key).is_some_and(|value| !value.is_null()))
154}
18pub use EventKind::{
19 AuthAccessKeyCreate as AUTH_ACCESS_KEY_CREATE, AuthAccessKeyDelete as AUTH_ACCESS_KEY_DELETE,
20 AuthAccessKeyUsed as AUTH_ACCESS_KEY_USED, AuthAppTokenCreate as AUTH_APP_TOKEN_CREATE,
21 AuthChallengeCreate as AUTH_CHALLENGE_CREATE, AuthChallengeUsed as AUTH_CHALLENGE_USED,
22 AuthLoginCreate as AUTH_LOGIN_CREATE, AuthLogoutDelete as AUTH_LOGOUT_DELETE,
23 AuthPasswordCreate as AUTH_PASSWORD_CREATE, AuthPasswordUpdate as AUTH_PASSWORD_UPDATE,
24 AuthRegisterCreate as AUTH_REGISTER_CREATE, AuthSshKeyCreate as AUTH_SSH_KEY_CREATE,
25 AuthSshKeyDelete as AUTH_SSH_KEY_DELETE, AuthTokenCreate as AUTH_TOKEN_CREATE,
26 AuthVerificationTokenCreate as AUTH_VERIFICATION_TOKEN_CREATE,
27 EmailDeliveryCreate as EMAIL_DELIVERY_CREATE, EmailFailureCreate as EMAIL_FAILURE_CREATE,
28 OrganizationInviteCreate as ORGANIZATION_INVITE_CREATE,
29 OrganizationLifeCycleCreate as ORGANIZATION_LIFE_CYCLE_CREATE,
30 OrganizationLifeCycleDelete as ORGANIZATION_LIFE_CYCLE_DELETE,
31 OrganizationLifeCycleUpdate as ORGANIZATION_LIFE_CYCLE_UPDATE,
32 OrganizationMemberUpdate as ORGANIZATION_MEMBER_UPDATE,
33 OrganizationRoleUpdate as ORGANIZATION_ROLE_UPDATE,
34};
Modified lib/share/events/src/lib.rs lib/share/events/src/lib.rs +2 −1 View lib/share/events/src/lib.rs
@@ -1,11 +1,12 @@
11mod emit;
22pub use emit::{actor_snapshot, emit};
33pub mod handler;
4pub mod kind;
45pub mod label;
56pub mod outbox;
6pub mod topic;
77
88pub use handler::EventHandler;
9pub use kind::EventKind;
910pub use outbox::emit_retry;
1011pub use queue::{Envelope, Labels};
1112
Modified lib/share/events/src/outbox.rs lib/share/events/src/outbox.rs +39 −108 View lib/share/events/src/outbox.rs
@@ -16,8 +16,8 @@
1616use tracing::Instrument;
1717use uuid::Uuid;
1818
19use crate::handler::DURABLE_DOMAINS;
20use crate::{EventHandler, Labels, label, topic};
19use crate::handler::concrete_kind;
20use crate::{EventHandler, EventKind, Labels, label};
2121
2222const BATCH_SIZE: i64 = 16;
2323const PUBLISH_TIMEOUT: Duration = Duration::from_secs(10);
@@ -101,93 +101,26 @@
101101 .await
102102}
103103
104fn domain(kind: &str) -> &str {
105 kind.split('.').next().unwrap_or_default()
106}
107
108fn is_durable_kind(kind: &str) -> bool {
109 DURABLE_DOMAINS.contains(&domain(kind))
110}
111
112/// 串行化粒度:同一 `ordering_key` 的事件严格按写入顺序投递。
113///
114/// 组织事件按组织串行,认证事件按用户串行(没有用户主体时退回请求 id,
115/// 相当于不串行),邮件事件按发信请求串行。其余域沿用资源 id。
116fn ordering_key(kind: &str, labels: &Labels) -> Result<String, DbErr> {
117 if let Some(explicit) = labels
118 .get(label::ORDERING_KEY)
119 .filter(|key| !key.is_empty())
120 {
121 return Ok(explicit.to_string());
104fn require_durable(kind: EventKind) -> Result<(), DbErr> {
105 if kind.durable() {
106 return Ok(());
122107 }
123 let domain = domain(kind);
124 let key = match domain {
125 "chat" => {
126 if let Some(uid) = labels.get(label::RECIPIENT_USER_ID) {
127 return Ok(format!("chat:user:{uid}"));
128 }
129 if let Some(room) = labels.get(label::ROOM_ID) {
130 return Ok(format!("chat:room:{room}"));
131 }
132 labels.get(label::USER_ID)
133 }
134 "notify" => {
135 if let Some(uid) = labels
136 .get(label::RECIPIENT_USER_ID)
137 .or_else(|| labels.get(label::USER_ID))
138 {
139 return Ok(format!("notify:user:{uid}"));
140 }
141 None
142 }
143 "guild" => labels.get(label::GUILD_ID),
144 "git" => labels.get(label::REPOSITORY_ID),
145 "pull" => labels
146 .get("pull_id")
147 .or_else(|| labels.get(label::PULL_REQUEST_ID))
148 .or_else(|| labels.get(label::REPOSITORY_ID)),
149 "issue" => labels
150 .get(label::ISSUE_ID)
151 .or_else(|| labels.get(label::BOARD_ID))
152 .or_else(|| labels.get(label::PULL_REQUEST_ID))
153 .or_else(|| labels.get(label::GUILD_ID)),
154 "organization" => labels.get(label::ORGANIZATION_ID),
155 "auth" => labels
156 .get(label::USER_ID)
157 .or_else(|| labels.get(label::REQUEST_ID)),
158 "email" => labels.get(label::REQUEST_ID),
159 _ => labels
160 .get(label::CHANNEL_ID)
161 .or_else(|| labels.get(label::GUILD_ID))
162 .or_else(|| labels.get(label::ROOM_ID))
163 .or_else(|| labels.get(label::ORGANIZATION_ID))
164 .or_else(|| labels.get(label::USER_ID))
165 .or_else(|| labels.get(label::REQUEST_ID)),
166 };
167 key.filter(|key| !key.is_empty())
168 .map(|key| format!("{domain}:{key}"))
169 .ok_or_else(|| {
170 DbErr::Custom(format!(
171 "event `{kind}` requires a user, organization or request id label"
172 ))
173 })
108 Err(DbErr::Custom(format!(
109 "event `{kind}` requires an outbox-enabled business domain"
110 )))
174111}
175112
176#[tracing::instrument(skip_all, fields(kind, request_id = %envelope::context::resolve_request_id(_request_id)))]
113#[tracing::instrument(skip_all, fields(kind = %kind, request_id = %envelope::context::resolve_request_id(_request_id)))]
177114pub async fn enqueue_at<T: Serialize>(
178115 conn: &impl ConnectionTrait,
179 kind: &str,
116 kind: EventKind,
180117 labels: Labels,
181118 payload: T,
182119 trace_id: &str,
183120 available_at: DateTime<Utc>,
184121 _request_id: Option<&str>,
185122) -> Result<Uuid, DbErr> {
186 if !is_durable_kind(kind) {
187 return Err(DbErr::Custom(format!(
188 "event `{kind}` requires an outbox-enabled business domain"
189 )));
190 }
123 require_durable(kind)?;
191124 let (id, model) = build_model(kind, labels, payload, trace_id, available_at)?;
192125 event_outbox::Entity::insert(model)
193126 .exec_without_returning(conn)
@@ -197,7 +130,7 @@
197130
198131pub async fn enqueue_now<T: Serialize>(
199132 conn: &impl ConnectionTrait,
200 kind: &str,
133 kind: EventKind,
201134 labels: Labels,
202135 payload: T,
203136 trace_id: &str,
@@ -215,10 +148,10 @@
215148 .await
216149}
217150
218#[tracing::instrument(skip_all, fields(kind))]
151#[tracing::instrument(skip_all, fields(kind = %kind))]
219152pub async fn emit_retry<T: Serialize>(
220153 conn: &impl ConnectionTrait,
221 kind: &str,
154 kind: EventKind,
222155 labels: Labels,
223156 payload: &T,
224157) -> Result<Uuid, DbErr> {
@@ -227,11 +160,11 @@
227160 match crate::emit::emit(conn, kind, labels.clone(), payload).await {
228161 Ok(id) => return Ok(id),
229162 Err(error) if attempt >= ENQUEUE_ATTEMPTS => {
230 ENQUEUE_FAILED.add(1, &[KeyValue::new("domain", domain(kind).to_owned())]);
163 ENQUEUE_FAILED.add(1, &[KeyValue::new("domain", kind.domain())]);
231164 return Err(error);
232165 }
233166 Err(error) => {
234 tracing::warn!(kind, attempt, %error, "retrying the outbox enqueue");
167 tracing::warn!(kind = %kind, attempt, %error, "retrying the outbox enqueue");
235168 tokio::time::sleep(Duration::from_millis(50) * attempt as u32).await;
236169 attempt += 1;
237170 }
@@ -240,22 +173,18 @@
240173}
241174
242175fn build_model<T: Serialize>(
243 kind: &str,
176 kind: EventKind,
244177 labels: Labels,
245178 payload: T,
246179 trace_id: &str,
247180 available_at: DateTime<Utc>,
248181) -> Result<(Uuid, event_outbox::ActiveModel), DbErr> {
249 if !is_durable_kind(kind) {
250 return Err(DbErr::Custom(format!(
251 "event `{kind}` requires an outbox-enabled business domain"
252 )));
253 }
254 let ordering_key = ordering_key(kind, &labels)?;
182 require_durable(kind)?;
183 let ordering_key = kind.ordering_key(&labels)?;
255184 let labels = labels.without(label::ORDERING_KEY);
256 let mut envelope = Envelope::new(kind, payload)
185 let mut envelope = Envelope::new(kind.as_str(), payload)
257186 .with_delivery(DeliveryClass::BusinessEvent)
258 .with_source(domain(kind))
187 .with_source(kind.domain())
259188 .with_labels(labels);
260189 if !trace_id.is_empty() {
261190 envelope = envelope.with_trace_id(trace_id);
@@ -339,10 +268,11 @@
339268 }
340269 envelope.verify_event_subject(&row.subject)?;
341270 envelope.attempt = row.attempts.max(1) as u32;
342 let topic = publisher.topic(topic::of(&envelope.kind));
271 let kind = concrete_kind(&envelope.kind)?;
272 let topic = publisher.topic(kind.topic());
343273 let body = envelope.encode()?;
344274 let payload = serde_json::to_value(&envelope.payload).ok();
345 let tag = label::tag(&envelope.kind, payload.as_ref());
275 let tag = kind.tag(payload.as_ref());
346276 publisher
347277 .publish(
348278 &topic,
@@ -387,13 +317,9 @@
387317 request_id = %request_id,
388318 );
389319 let result = async {
390 if {
391 let kind = &row.kind;
392 matches!(domain(kind), "auth" | "organization" | "email")
393 } {
394 publish_identity(identity, &row).await
395 } else {
396 publish_app(app, &row).await
320 match concrete_kind(&row.kind) {
321 Ok(kind) if kind.identity() => publish_identity(identity, &row).await,
322 _ => publish_app(app, &row).await,
397323 }
398324 }
399325 .instrument(span);
@@ -453,7 +379,9 @@
453379 }
454380 }
455381 };
456 let event_domain = domain(&row.kind).to_owned();
382 let event_domain = concrete_kind(&row.kind)
383 .map(EventKind::domain)
384 .unwrap_or_default();
457385 let updated = db.execute_raw(statement).await?;
458386 if updated.rows_affected() == 0 {
459387 LEASE_LOST.add(1, &[KeyValue::new("domain", event_domain)]);
@@ -671,7 +599,6 @@
671599
672600#[cfg(test)]
673601mod tests {
674 use super::ordering_key;
675602 use crate::{Labels, label};
676603
677604 #[test]
@@ -680,13 +607,13 @@
680607 .with(label::USER_ID, 42)
681608 .with(label::REQUEST_ID, "req-1");
682609 assert_eq!(
683 ordering_key(label::AUTH_LOGIN_CREATE, &user).unwrap(),
610 label::AUTH_LOGIN_CREATE.ordering_key(&user).unwrap(),
684611 "auth:42"
685612 );
686613
687614 let anonymous = Labels::new().with(label::REQUEST_ID, "req-2");
688615 assert_eq!(
689 ordering_key(label::AUTH_LOGIN_CREATE, &anonymous).unwrap(),
616 label::AUTH_LOGIN_CREATE.ordering_key(&anonymous).unwrap(),
690617 "auth:req-2"
691618 );
692619 }
@@ -697,20 +624,24 @@
697624 .with(label::ORGANIZATION_ID, "org-1")
698625 .with(label::ACTOR_USER_ID, 7);
699626 assert_eq!(
700 ordering_key(label::ORGANIZATION_LIFE_CYCLE_CREATE, &org).unwrap(),
627 label::ORGANIZATION_LIFE_CYCLE_CREATE
628 .ordering_key(&org)
629 .unwrap(),
701630 "organization:org-1"
702631 );
703632
704633 let mail = Labels::new().with(label::REQUEST_ID, "mail-1");
705634 assert_eq!(
706 ordering_key(label::EMAIL_DELIVERY_CREATE, &mail).unwrap(),
635 label::EMAIL_DELIVERY_CREATE.ordering_key(&mail).unwrap(),
707636 "email:mail-1"
708637 );
709638 }
710639
711640 #[test]
712641 fn events_without_any_correlation_label_are_rejected() {
713 let error = ordering_key(label::AUTH_LOGIN_CREATE, &Labels::new()).unwrap_err();
642 let error = label::AUTH_LOGIN_CREATE
643 .ordering_key(&Labels::new())
644 .unwrap_err();
714645 assert!(
715646 error
716647 .to_string()
Deleted lib/share/events/src/topic.rs lib/share/events/src/topic.rs +0 −46
@@ -1,46 +0,0 @@
1pub const AUTH: &str = "auth";
2pub const SSH_KEY: &str = "sshkey";
3pub const ACCESS_KEY: &str = "accesskey";
4pub const TOKEN: &str = "token";
5pub const VERIFICATION: &str = "verification";
6pub const ORGANIZATION: &str = "org";
7pub const EMAIL: &str = "email";
8
9pub const AGENT: &str = "agent";
10pub const CHAT: &str = "chat";
11pub const GIT: &str = "git";
12pub const GUILD: &str = "guild";
13pub const ISSUE: &str = "issue";
14pub const NOTIFY: &str = "notify";
15pub const OBJECT: &str = "object";
16pub const PULL: &str = "pull";
17pub const REALTIME: &str = "realtime";
18pub const USER: &str = "user";
19pub const WEBHOOK: &str = "webhook";
20
21pub fn of(kind: &str) -> &str {
22 let mut parts = kind.split('.');
23 let domain = parts.next().unwrap_or_default();
24 let name = parts.next().unwrap_or_default();
25 match (domain, name) {
26 ("auth", "ssh_key") => SSH_KEY,
27 ("auth", "access_key") => ACCESS_KEY,
28 ("auth", "app_token" | "token") => TOKEN,
29 ("auth", "verification_token") => VERIFICATION,
30 ("auth", _) => AUTH,
31 ("organization", _) => ORGANIZATION,
32 ("email", _) => EMAIL,
33 ("agent", _) => AGENT,
34 ("chat", _) => CHAT,
35 ("git", _) => GIT,
36 ("guild", _) => GUILD,
37 ("issue", _) => ISSUE,
38 ("notify", _) => NOTIFY,
39 ("object", _) => OBJECT,
40 ("pull", _) => PULL,
41 ("realtime", _) => REALTIME,
42 ("user", _) => USER,
43 ("webhook", _) => WEBHOOK,
44 (other, _) => other,
45 }
46}
Modified Cargo.lock Cargo.lock +1 −0 View Cargo.lock
@@ -8500,6 +8500,7 @@
85008500 "chrono",
85018501 "config 1.0.0",
85028502 "db",
8503 "events",
85038504 "futures",
85048505 "i18n",
85058506 "imaging",