1use guts_auth::WebhookEvent;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct RealtimeEvent {
9 #[serde(rename = "type")]
11 pub event_type: String,
12
13 pub channel: String,
15
16 pub event: EventKind,
18
19 pub data: serde_json::Value,
21
22 pub timestamp: u64,
24
25 pub event_id: String,
27}
28
29impl RealtimeEvent {
30 pub fn new(channel: String, event: EventKind, data: serde_json::Value) -> Self {
32 Self {
33 event_type: "event".to_string(),
34 channel,
35 event,
36 data,
37 timestamp: Self::now(),
38 event_id: uuid::Uuid::new_v4().to_string(),
39 }
40 }
41
42 fn now() -> u64 {
43 std::time::SystemTime::now()
44 .duration_since(std::time::UNIX_EPOCH)
45 .unwrap_or_default()
46 .as_secs()
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
52#[serde(rename_all = "snake_case")]
53pub enum EventKind {
54 Push,
57 BranchCreated,
59 BranchDeleted,
61 TagCreated,
63 TagDeleted,
65
66 PrOpened,
69 PrClosed,
71 PrMerged,
73 PrUpdated,
75 PrReopened,
77 PrReview,
79 PrComment,
81
82 IssueOpened,
85 IssueClosed,
87 IssueReopened,
89 IssueUpdated,
91 IssueComment,
93
94 LabelAdded,
97 LabelRemoved,
99
100 RepoCreated,
103 RepoUpdated,
105
106 CollaboratorAdded,
109 CollaboratorRemoved,
111}
112
113impl EventKind {
114 pub fn from_webhook(event: WebhookEvent) -> Self {
116 match event {
117 WebhookEvent::Push => EventKind::Push,
118 WebhookEvent::PullRequest => EventKind::PrOpened,
119 WebhookEvent::PullRequestReview => EventKind::PrReview,
120 WebhookEvent::PullRequestComment => EventKind::PrComment,
121 WebhookEvent::Issue => EventKind::IssueOpened,
122 WebhookEvent::IssueComment => EventKind::IssueComment,
123 WebhookEvent::Create => EventKind::BranchCreated,
124 WebhookEvent::Delete => EventKind::BranchDeleted,
125 WebhookEvent::Fork => EventKind::RepoCreated,
126 WebhookEvent::Star => EventKind::RepoUpdated,
127 }
128 }
129
130 pub fn all() -> Vec<EventKind> {
132 vec![
133 EventKind::Push,
134 EventKind::BranchCreated,
135 EventKind::BranchDeleted,
136 EventKind::TagCreated,
137 EventKind::TagDeleted,
138 EventKind::PrOpened,
139 EventKind::PrClosed,
140 EventKind::PrMerged,
141 EventKind::PrUpdated,
142 EventKind::PrReopened,
143 EventKind::PrReview,
144 EventKind::PrComment,
145 EventKind::IssueOpened,
146 EventKind::IssueClosed,
147 EventKind::IssueReopened,
148 EventKind::IssueUpdated,
149 EventKind::IssueComment,
150 EventKind::LabelAdded,
151 EventKind::LabelRemoved,
152 EventKind::RepoCreated,
153 EventKind::RepoUpdated,
154 EventKind::CollaboratorAdded,
155 EventKind::CollaboratorRemoved,
156 ]
157 }
158}
159
160impl std::fmt::Display for EventKind {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 match self {
163 EventKind::Push => write!(f, "push"),
164 EventKind::BranchCreated => write!(f, "branch.created"),
165 EventKind::BranchDeleted => write!(f, "branch.deleted"),
166 EventKind::TagCreated => write!(f, "tag.created"),
167 EventKind::TagDeleted => write!(f, "tag.deleted"),
168 EventKind::PrOpened => write!(f, "pr.opened"),
169 EventKind::PrClosed => write!(f, "pr.closed"),
170 EventKind::PrMerged => write!(f, "pr.merged"),
171 EventKind::PrUpdated => write!(f, "pr.updated"),
172 EventKind::PrReopened => write!(f, "pr.reopened"),
173 EventKind::PrReview => write!(f, "pr.review"),
174 EventKind::PrComment => write!(f, "pr.comment"),
175 EventKind::IssueOpened => write!(f, "issue.opened"),
176 EventKind::IssueClosed => write!(f, "issue.closed"),
177 EventKind::IssueReopened => write!(f, "issue.reopened"),
178 EventKind::IssueUpdated => write!(f, "issue.updated"),
179 EventKind::IssueComment => write!(f, "issue.comment"),
180 EventKind::LabelAdded => write!(f, "label.added"),
181 EventKind::LabelRemoved => write!(f, "label.removed"),
182 EventKind::RepoCreated => write!(f, "repo.created"),
183 EventKind::RepoUpdated => write!(f, "repo.updated"),
184 EventKind::CollaboratorAdded => write!(f, "collaborator.added"),
185 EventKind::CollaboratorRemoved => write!(f, "collaborator.removed"),
186 }
187 }
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct PushEventData {
193 #[serde(rename = "ref")]
195 pub ref_name: String,
196 pub before: String,
198 pub after: String,
200 pub pusher: String,
202 pub commit_count: usize,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct PullRequestEventData {
209 pub number: u64,
211 pub title: String,
213 pub author: String,
215 pub source_branch: String,
217 pub target_branch: String,
219 pub state: String,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct IssueEventData {
226 pub number: u64,
228 pub title: String,
230 pub author: String,
232 pub state: String,
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct CommentEventData {
239 pub id: u64,
241 pub parent_number: u64,
243 pub author: String,
245 pub body_preview: String,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct ReviewEventData {
252 pub id: u64,
254 pub pr_number: u64,
256 pub reviewer: String,
258 pub state: String,
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn test_event_creation() {
268 let data = serde_json::json!({
269 "ref": "refs/heads/main",
270 "before": "abc123",
271 "after": "def456"
272 });
273
274 let event = RealtimeEvent::new("repo:alice/myrepo".to_string(), EventKind::Push, data);
275
276 assert_eq!(event.event_type, "event");
277 assert_eq!(event.channel, "repo:alice/myrepo");
278 assert_eq!(event.event, EventKind::Push);
279 assert!(!event.event_id.is_empty());
280 }
281
282 #[test]
283 fn test_event_kind_display() {
284 assert_eq!(EventKind::Push.to_string(), "push");
285 assert_eq!(EventKind::PrOpened.to_string(), "pr.opened");
286 assert_eq!(EventKind::IssueComment.to_string(), "issue.comment");
287 }
288
289 #[test]
290 fn test_event_serialization() {
291 let data = serde_json::json!({"test": "value"});
292 let event = RealtimeEvent::new("repo:test/repo".to_string(), EventKind::Push, data);
293
294 let json = serde_json::to_string(&event).unwrap();
295 assert!(json.contains("\"type\":\"event\""));
296 assert!(json.contains("\"channel\":\"repo:test/repo\""));
297 }
298}