discourse_webhooks/
events.rs

1//! Event types and parsing for Discourse webhooks
2//!
3//! This module contains all the data structures that represent different
4//! webhook events from Discourse, along with functions to parse them.
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9/// Represents a user in webhook payloads
10#[derive(Debug, Serialize, Deserialize, Clone)]
11pub struct WebhookUser {
12    /// Template URL for the user's avatar
13    pub avatar_template: String,
14    /// Unique user ID
15    pub id: i32,
16    /// Optional display name
17    pub name: Option<String>,
18    /// Username (unique identifier)
19    pub username: String,
20}
21
22#[derive(Debug, Serialize, Deserialize, Clone)]
23pub struct TopicWebhookEvent {
24    pub topic: WebhookTopic,
25}
26
27#[derive(Debug, Serialize, Deserialize, Clone)]
28pub struct WebhookTopic {
29    pub archetype: String,
30    pub archived: bool,
31    pub bookmarked: bool,
32    pub category_id: i32,
33    pub closed: bool,
34    pub created_at: DateTime<Utc>,
35    pub created_by: WebhookUser,
36    pub deleted_at: Option<DateTime<Utc>>,
37    pub deleted_by: Option<WebhookUser>,
38    pub fancy_title: String,
39    pub featured_link: Option<String>,
40    pub has_deleted: bool,
41    pub highest_post_number: i32,
42    pub id: i32,
43    pub last_posted_at: DateTime<Utc>,
44    pub last_poster: WebhookUser,
45    pub like_count: i32,
46    pub participant_count: i32,
47    pub pinned: bool,
48    pub pinned_at: Option<DateTime<Utc>>,
49    pub pinned_globally: bool,
50    pub pinned_until: Option<DateTime<Utc>>,
51    pub posts_count: i32,
52    pub reply_count: i32,
53    pub slug: String,
54    pub tags: Vec<String>,
55    pub tags_descriptions: serde_json::Value,
56    pub thumbnails: Option<serde_json::Value>,
57    pub title: String,
58    pub unpinned: Option<DateTime<Utc>>,
59    pub user_id: i32,
60    pub views: i32,
61    pub visible: bool,
62    pub word_count: i32,
63}
64
65#[derive(Debug, Serialize, Deserialize, Clone)]
66pub struct PostWebhookEvent {
67    pub post: WebhookPost,
68}
69
70#[derive(Debug, Serialize, Deserialize, Clone)]
71pub struct WebhookPost {
72    pub admin: bool,
73    pub avatar_template: String,
74    pub bookmarked: bool,
75    pub category_id: i32,
76    pub category_slug: String,
77    pub cooked: String,
78    pub created_at: DateTime<Utc>,
79    pub deleted_at: Option<DateTime<Utc>>,
80    pub deleted_by: Option<WebhookUser>,
81    pub display_username: Option<String>,
82    pub edit_reason: Option<String>,
83    pub flair_group_id: Option<i32>,
84    pub flair_name: Option<String>,
85    pub hidden: bool,
86    pub id: i32,
87    pub incoming_link_count: i32,
88    pub moderator: bool,
89    pub name: Option<String>,
90    pub post_number: i32,
91    pub post_type: i32,
92    pub post_url: String,
93    pub posts_count: i32,
94    pub primary_group_name: Option<String>,
95    pub quote_count: i32,
96    pub raw: String,
97    pub reads: i32,
98    pub reply_count: i32,
99    pub reply_to_post_number: Option<i32>,
100    pub reviewable_id: Option<i32>,
101    pub reviewable_score_count: i32,
102    pub reviewable_score_pending_count: i32,
103    pub score: f64,
104    pub staff: bool,
105    pub topic_archetype: String,
106    pub topic_filtered_posts_count: i32,
107    pub topic_id: i32,
108    pub topic_posts_count: i32,
109    pub topic_slug: String,
110    pub topic_title: String,
111    pub trust_level: i32,
112    pub updated_at: DateTime<Utc>,
113    pub user_deleted: bool,
114    pub user_id: i32,
115    pub user_title: Option<String>,
116    pub username: String,
117    pub version: i32,
118    pub wiki: bool,
119}
120
121#[derive(Debug, Serialize, Deserialize)]
122#[serde(untagged)]
123pub enum WebhookEventPayload {
124    TopicEvent(TopicWebhookEvent),
125    PostEvent(PostWebhookEvent),
126    // For events we don't have specific types for yet
127    Generic(serde_json::Value),
128}
129
130pub fn parse_webhook_payload(
131    event_type: &str,
132    payload: serde_json::Value,
133) -> Result<WebhookEventPayload, serde_json::Error> {
134    match event_type {
135        "topic_created" | "topic_edited" | "topic_destroyed" | "topic_recovered" => {
136            let topic_event: TopicWebhookEvent = serde_json::from_value(payload)?;
137            Ok(WebhookEventPayload::TopicEvent(topic_event))
138        }
139        "post_created" | "post_edited" | "post_destroyed" | "post_recovered" => {
140            let post_event: PostWebhookEvent = serde_json::from_value(payload)?;
141            Ok(WebhookEventPayload::PostEvent(post_event))
142        }
143        _ => Ok(WebhookEventPayload::Generic(payload)),
144    }
145}