cal_core/
contact.rs

1// File: cal-core/src/contact.rs
2
3use crate::RecordReference;
4use serde::{Deserialize, Serialize};
5use std::collections::BTreeMap;
6#[cfg(feature = "openapi")]
7use utoipa::ToSchema;
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10#[cfg_attr(feature = "openapi", derive(ToSchema))]
11#[serde(rename_all = "camelCase")]
12pub struct Contact {
13    #[serde(deserialize_with = "crate::shared::object_id_as_string", rename = "_id")]
14    pub id: String,
15    pub account: RecordReference,
16    #[serde(default, skip_serializing_if = "Option::is_none")]
17    pub organisation: Option<RecordReference>,
18    pub primary: String,
19    #[serde(default, skip_serializing_if = "Option::is_none")]
20    pub secondary: Option<String>,
21    #[serde(default, skip_serializing_if = "Option::is_none")]
22    pub tertiary: Option<String>,
23    pub name: String,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub nickname: Option<String>,
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub email: Option<String>,
28    #[serde(default, skip_serializing_if = "Vec::is_empty")]
29    pub capabilities: Vec<String>,
30    #[serde(default, skip_serializing_if = "Vec::is_empty")]
31    pub groups: Vec<String>,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub device: Option<ContactDevice>,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub group: Option<String>,
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    pub opt_out: Option<bool>,
38    #[serde(rename = "_class", skip_serializing_if = "Option::is_none")]
39    pub class_name: Option<String>,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
43#[serde(rename_all = "camelCase")]
44pub struct ContactRef {
45    pub primary: String,
46    #[serde(default, skip_serializing_if = "Option::is_none")]
47    pub secondary: Option<String>,
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    pub tertiary: Option<String>,
50    pub name: String,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub nickname: Option<String>,
53    #[serde(default, skip_serializing_if = "Option::is_none")]
54    pub email: Option<String>,
55    #[serde(default, skip_serializing_if = "Vec::is_empty")]
56    pub capabilities: Vec<String>,
57    #[serde(default, skip_serializing_if = "Vec::is_empty")]
58    pub groups: Vec<String>,
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub device: Option<ContactDevice>,
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub group: Option<String>,
63    #[serde(default, skip_serializing_if = "Option::is_none")]
64    pub opt_out: Option<bool>,
65    #[serde(rename = "_class", skip_serializing_if = "Option::is_none")]
66    pub class_name: Option<String>,
67}
68
69impl From<Contact> for ContactRef {
70    fn from(contact: Contact) -> Self {
71        ContactRef {
72            primary: contact.primary,
73            secondary: contact.secondary,
74            tertiary: contact.tertiary,
75            name: contact.name,
76            nickname: contact.nickname,
77            email: contact.email,
78            capabilities: contact.capabilities,
79            groups: contact.groups,
80            device: contact.device,
81            group: contact.group,
82            opt_out: contact.opt_out,
83            class_name: contact.class_name,
84        }
85    }
86}
87
88// You can also implement it for references if needed
89impl From<&Contact> for ContactRef {
90    fn from(contact: &Contact) -> Self {
91        ContactRef {
92            primary: contact.primary.clone(),
93            secondary: contact.secondary.clone(),
94            tertiary: contact.tertiary.clone(),
95            name: contact.name.clone(),
96            nickname: contact.nickname.clone(),
97            email: contact.email.clone(),
98            capabilities: contact.capabilities.clone(),
99            groups: contact.groups.clone(),
100            device: contact.device.clone(),
101            group: contact.group.clone(),
102            opt_out: contact.opt_out,
103            class_name: contact.class_name.clone(),
104        }
105    }
106}
107
108#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
109#[serde(rename_all = "camelCase")]
110pub struct ContactDevice {
111    #[serde(rename = "_id")]
112    pub id: String,
113    pub name: String,
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub class_name: Option<String>,
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub meta: Option<BTreeMap<String, String>>,
118}
119
120impl Contact {
121    pub fn new(
122        id: String,
123        account: RecordReference,
124        primary: String,
125        name: String,
126    ) -> Self {
127        Self {
128            id,
129            account,
130            name,
131            primary,
132            organisation: None,           
133            secondary: None,
134            tertiary: None,
135            nickname: None,
136            email: None,
137            capabilities: Vec::new(),
138            groups: Vec::new(),
139            device: None,
140            group: None,
141            opt_out: None,
142            class_name: None,
143        }
144    }
145
146    pub fn with_email(mut self, email: String) -> Self {
147        self.email = Some(email);
148        self
149    }
150
151    pub fn with_nickname(mut self, nickname: String) -> Self {
152        self.nickname = Some(nickname);
153        self
154    }
155
156    pub fn with_capabilities(mut self, capabilities: Vec<String>) -> Self {
157        self.capabilities = capabilities;
158        self
159    }
160
161    pub fn with_groups(mut self, groups: Vec<String>) -> Self {
162        self.groups = groups;
163        self
164    }
165
166    pub fn with_device(mut self, device: ContactDevice) -> Self {
167        self.device = Some(device);
168        self
169    }
170
171    pub fn has_capability(&self, capability: &str) -> bool {
172        self.capabilities.iter().any(|c| c == capability)
173    }
174
175    pub fn is_in_group(&self, group: &str) -> bool {
176        self.groups.iter().any(|g| g == group)
177    }
178}
179
180impl ContactDevice {
181    pub fn new(id: String, name: String) -> Self {
182        Self {
183            id,
184            name,
185            class_name: None,
186            meta: None,
187        }
188    }
189
190    pub fn with_meta(mut self, meta: BTreeMap<String, String>) -> Self {
191        self.meta = Some(meta);
192        self
193    }
194
195    pub fn with_class_name(mut self, class_name: String) -> Self {
196        self.class_name = Some(class_name);
197        self
198    }
199}
200
201// Display implementations
202impl std::fmt::Display for Contact {
203    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204        write!(
205            f,
206            "Contact {{ id: {}, name: {}, primary: {}, organisation: {:?}, account: {:?} }}",
207            self.id, self.name, self.primary, self.organisation, self.account
208        )
209    }
210}
211
212impl std::fmt::Display for ContactDevice {
213    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214        write!(f, "ContactDevice {{ id: {}, name: {} }}", self.id, self.name)
215    }
216}