1use serde::{Deserialize, Serialize};
2
3#[derive(Serialize, Deserialize, Clone, Debug)]
8pub struct Tenant {
9 pub id: String,
10 pub email: String,
11 pub name: Option<String>,
12 #[serde(default)]
13 pub facebook_id: Option<String>,
14 pub plan: String,
15 pub created_at: String,
16 pub updated_at: String,
17}
18
19#[derive(Serialize, Deserialize, Clone, Debug, Default)]
20pub struct TenantCredentials {}
21
22#[derive(Serialize, Deserialize, Clone, Debug)]
27pub struct WhatsAppAccount {
28 pub id: String,
29 pub tenant_id: String,
30 pub name: String,
31 pub phone_number: String,
32 pub phone_number_id: String,
33 pub auto_reply: AutoReplyConfig,
34 pub created_at: String,
35 pub updated_at: String,
36}
37
38#[derive(Serialize, Deserialize, Clone, Debug, Default)]
39pub struct AutoReplyConfig {
40 pub enabled: bool,
41 pub mode: AutoReplyMode,
42 pub prompt: String,
43}
44
45#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
46#[serde(rename_all = "snake_case")]
47pub enum AutoReplyMode {
48 #[default]
49 Static,
50 Ai,
51}
52
53#[derive(Serialize, Deserialize, Clone, Debug)]
58pub struct InstagramAccount {
59 pub id: String,
60 pub tenant_id: String,
61 pub instagram_user_id: String,
62 pub instagram_username: String,
63 pub page_id: String,
64 pub auto_reply: AutoReplyConfig,
65 pub enabled: bool,
66 pub created_at: String,
67 #[serde(default)]
68 pub updated_at: String,
69}
70
71#[derive(Serialize, Deserialize, Clone, Debug)]
76pub struct LeadCaptureForm {
77 pub id: String,
78 pub tenant_id: String,
79 pub name: String,
80 pub slug: String,
81 pub whatsapp_account_id: String,
82 pub reply_mode: AutoReplyMode,
83 pub reply_prompt: String,
84 pub style: LeadFormStyle,
85 pub allowed_origins: Vec<String>,
86 pub enabled: bool,
87 pub created_at: String,
88 pub updated_at: String,
89}
90
91#[derive(Serialize, Deserialize, Clone, Debug)]
92pub struct LeadFormStyle {
93 pub primary_color: String,
94 pub text_color: String,
95 pub background_color: String,
96 pub border_radius: String,
97 pub button_text: String,
98 pub placeholder_text: String,
99 pub success_message: String,
100 #[serde(default)]
101 pub custom_css: String,
102}
103
104impl Default for LeadFormStyle {
105 fn default() -> Self {
106 Self {
107 primary_color: String::from("#F38020"),
108 text_color: String::from("#333333"),
109 background_color: String::from("#ffffff"),
110 border_radius: String::from("8px"),
111 button_text: String::from("Get in touch"),
112 placeholder_text: String::from("Your phone number"),
113 success_message: String::from("Thanks! We'll message you on WhatsApp shortly."),
114 custom_css: String::new(),
115 }
116 }
117}
118
119#[derive(Serialize, Deserialize, Clone, Debug)]
124pub struct InstagramToken {
125 pub access_token: String,
126 pub expires_at: String,
127 pub user_id: String,
128}
129
130#[derive(Debug, Deserialize)]
135pub struct WhatsAppWebhook {
136 pub object: String,
137 #[serde(default)]
138 pub entry: Vec<WebhookEntry>,
139}
140
141#[derive(Debug, Deserialize)]
142pub struct WebhookEntry {
143 pub id: String,
144 #[serde(default)]
145 pub changes: Vec<WebhookChange>,
146}
147
148#[derive(Debug, Deserialize)]
149pub struct WebhookChange {
150 pub field: String,
151 pub value: WebhookValue,
152}
153
154#[derive(Debug, Deserialize)]
155pub struct WebhookValue {
156 pub messaging_product: String,
157 pub metadata: WebhookMetadata,
158 #[serde(default)]
159 pub contacts: Vec<WebhookContact>,
160 #[serde(default)]
161 pub messages: Vec<WhatsAppMessage>,
162}
163
164#[derive(Debug, Deserialize)]
165pub struct WebhookMetadata {
166 pub display_phone_number: String,
167 pub phone_number_id: String,
168}
169
170#[derive(Debug, Deserialize)]
171pub struct WebhookContact {
172 pub wa_id: String,
173 pub profile: ContactProfile,
174}
175
176#[derive(Debug, Deserialize)]
177pub struct ContactProfile {
178 pub name: String,
179}
180
181#[derive(Debug, Deserialize)]
182pub struct WhatsAppMessage {
183 pub from: String,
184 pub id: String,
185 pub timestamp: String,
186 #[serde(rename = "type")]
187 pub message_type: String,
188 #[serde(default)]
189 pub text: Option<TextMessage>,
190}
191
192#[derive(Debug, Deserialize)]
193pub struct TextMessage {
194 pub body: String,
195}
196
197#[derive(Debug, Clone)]
198pub struct IncomingMessage {
199 pub from: String,
200 pub sender_name: String,
201 pub text: String,
202 pub message_id: String,
203 pub timestamp: String,
204}
205
206#[derive(Debug, Deserialize)]
211pub struct InstagramWebhookPayload {
212 pub object: String,
213 #[serde(default)]
214 pub entry: Vec<InstagramWebhookEntry>,
215}
216
217#[derive(Debug, Deserialize)]
218pub struct InstagramWebhookEntry {
219 pub id: String,
220 #[serde(default)]
221 pub time: i64,
222 #[serde(default)]
223 pub messaging: Vec<InstagramMessaging>,
224}
225
226#[derive(Debug, Deserialize)]
227pub struct InstagramMessaging {
228 pub sender: IdField,
229 pub recipient: IdField,
230 #[serde(default)]
231 pub timestamp: i64,
232 #[serde(default)]
233 pub message: Option<InstagramDm>,
234}
235
236#[derive(Debug, Deserialize)]
237pub struct IdField {
238 pub id: String,
239}
240
241#[derive(Debug, Deserialize)]
242pub struct InstagramDm {
243 pub mid: String,
244 #[serde(default)]
245 pub text: Option<String>,
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_auto_reply_mode_serialization() {
254 assert_eq!(
255 serde_json::to_string(&AutoReplyMode::Static).unwrap(),
256 "\"static\""
257 );
258 assert_eq!(serde_json::to_string(&AutoReplyMode::Ai).unwrap(), "\"ai\"");
259 let mode: AutoReplyMode = serde_json::from_str("\"ai\"").unwrap();
260 assert_eq!(mode, AutoReplyMode::Ai);
261 }
262
263 #[test]
264 fn test_lead_form_style_default() {
265 let style = LeadFormStyle::default();
266 assert_eq!(style.primary_color, "#F38020");
267 assert_eq!(style.button_text, "Get in touch");
268 assert!(style.custom_css.is_empty());
269 }
270
271 #[test]
272 fn test_whatsapp_webhook_deserialization() {
273 let json = r#"{
274 "object": "whatsapp_business_account",
275 "entry": [{
276 "id": "123456789",
277 "changes": [{
278 "field": "messages",
279 "value": {
280 "messaging_product": "whatsapp",
281 "metadata": {
282 "display_phone_number": "+1234567890",
283 "phone_number_id": "phone-123"
284 },
285 "contacts": [{
286 "wa_id": "user123",
287 "profile": {"name": "Test User"}
288 }],
289 "messages": [{
290 "from": "user123",
291 "id": "msg-123",
292 "timestamp": "1234567890",
293 "type": "text",
294 "text": {"body": "Hello!"}
295 }]
296 }
297 }]
298 }]
299 }"#;
300
301 let webhook: WhatsAppWebhook = serde_json::from_str(json).unwrap();
302 assert_eq!(webhook.object, "whatsapp_business_account");
303 assert_eq!(
304 webhook.entry[0].changes[0].value.messages[0].from,
305 "user123"
306 );
307 }
308
309 #[test]
310 fn test_instagram_webhook_deserialization() {
311 let json = r#"{
312 "object": "instagram",
313 "entry": [{
314 "id": "page-123",
315 "time": 1700000000,
316 "messaging": [{
317 "sender": {"id": "sender-456"},
318 "recipient": {"id": "page-123"},
319 "timestamp": 1700000000,
320 "message": {
321 "mid": "mid-789",
322 "text": "Hello!"
323 }
324 }]
325 }]
326 }"#;
327
328 let payload: InstagramWebhookPayload = serde_json::from_str(json).unwrap();
329 assert_eq!(payload.object, "instagram");
330 assert_eq!(payload.entry[0].messaging[0].sender.id, "sender-456");
331 assert_eq!(
332 payload.entry[0].messaging[0]
333 .message
334 .as_ref()
335 .unwrap()
336 .text
337 .as_deref(),
338 Some("Hello!")
339 );
340 }
341}