1use crate::contact::{Contact, ContactDevice};
4use crate::rest::common::{
5 ApiError, ApiResponse, BatchResult, Id, ListRequest, PaginatedResponse, PaginationParams,
6 SearchParams, SortOrder, SortParams,
7};
8use crate::RecordReference;
9use serde::{Deserialize, Serialize};
10#[cfg(feature = "openapi")]
11use utoipa::ToSchema;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase")]
16#[cfg_attr(feature = "openapi", derive(ToSchema))]
17pub struct CreateContactRequest {
18 pub organisation: RecordReference,
19 pub account: RecordReference,
20 pub primary: String,
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub secondary: Option<String>,
23 #[serde(skip_serializing_if = "Option::is_none")]
24 pub tertiary: Option<String>,
25 pub name: String,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub nickname: Option<String>,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub email: Option<String>,
30 #[serde(default, skip_serializing_if = "Vec::is_empty")]
31 pub capabilities: Vec<String>,
32 #[serde(default, skip_serializing_if = "Vec::is_empty")]
33 pub groups: Vec<String>,
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub device: Option<ContactDevice>,
36 #[serde(skip_serializing_if = "Option::is_none")]
37 pub group: Option<String>,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub opt_out: Option<bool>,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44#[cfg_attr(feature = "openapi", derive(ToSchema))]
45#[serde(rename_all = "camelCase")]
46pub struct UpdateContactRequest {
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub organisation: Option<RecordReference>,
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub account: Option<RecordReference>,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub primary: Option<String>,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub secondary: Option<String>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub tertiary: Option<String>,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub name: Option<String>,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub nickname: Option<String>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub email: Option<String>,
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub capabilities: Option<Vec<String>>,
65 #[serde(skip_serializing_if = "Option::is_none")]
66 pub groups: Option<Vec<String>>,
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub device: Option<ContactDevice>,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub group: Option<String>,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub opt_out: Option<bool>,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77#[cfg_attr(feature = "openapi", derive(ToSchema))]
78#[serde(rename_all = "camelCase")]
79pub struct ContactListRequest {
80 #[serde(flatten)]
81 pub common: ListRequest,
82 #[serde(skip_serializing_if = "Option::is_none")]
83 pub account_id: Option<String>,
84 #[serde(skip_serializing_if = "Option::is_none")]
85 pub organisation_id: Option<String>,
86 #[serde(skip_serializing_if = "Option::is_none")]
87 pub name: Option<String>,
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub primary: Option<String>,
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub email: Option<String>,
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub group: Option<String>,
94 #[serde(skip_serializing_if = "Option::is_none")]
95 pub capability: Option<String>,
96 #[serde(skip_serializing_if = "Option::is_none")]
97 pub device_id: Option<String>,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102#[cfg_attr(feature = "openapi", derive(ToSchema))]
103#[serde(rename_all = "camelCase")]
104pub struct BatchCreateContactsRequest {
105 pub contacts: Vec<CreateContactRequest>,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
109#[cfg_attr(feature = "openapi", derive(ToSchema))]
110#[serde(rename_all = "camelCase")]
111pub struct BatchUpdateContactsRequest {
112 pub updates: Vec<ContactUpdate>,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
116#[cfg_attr(feature = "openapi", derive(ToSchema))]
117#[serde(rename_all = "camelCase")]
118pub struct ContactUpdate {
119 pub id: String,
120 #[serde(flatten)]
121 pub update: UpdateContactRequest,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125#[cfg_attr(feature = "openapi", derive(ToSchema))]
126#[serde(rename_all = "camelCase")]
127pub struct BatchDeleteContactsRequest {
128 pub ids: Vec<String>,
129}
130
131pub type ContactResponse = ApiResponse<Contact>;
133pub type ContactListResponse = ApiResponse<PaginatedResponse<Contact>>;
134pub type BatchCreateContactsResponse = ApiResponse<BatchResult<Contact, ApiError>>;
135pub type BatchUpdateContactsResponse = ApiResponse<BatchResult<Contact, ApiError>>;
136pub type BatchDeleteContactsResponse = ApiResponse<BatchResult<Id, ApiError>>;
137
138#[derive(Debug, Clone)]
140pub enum ContactErrorCode {
141 NotFound,
142 DuplicatePrimary,
143 InvalidEmail,
144 InvalidPhone,
145 MissingRequiredField,
146 InvalidOrganisation,
147 InvalidAccount,
148}
149
150impl ContactErrorCode {
151 pub fn as_str(&self) -> &'static str {
152 match self {
153 Self::NotFound => "CONTACT_NOT_FOUND",
154 Self::DuplicatePrimary => "DUPLICATE_PRIMARY_NUMBER",
155 Self::InvalidEmail => "INVALID_EMAIL_FORMAT",
156 Self::InvalidPhone => "INVALID_PHONE_FORMAT",
157 Self::MissingRequiredField => "MISSING_REQUIRED_FIELD",
158 Self::InvalidOrganisation => "INVALID_ORGANISATION",
159 Self::InvalidAccount => "INVALID_ACCOUNT",
160 }
161 }
162}
163
164impl CreateContactRequest {
166 pub fn validate(&self) -> Result<(), ApiError> {
167 if self.name.trim().is_empty() {
168 return Err(ApiError::new(
169 ContactErrorCode::MissingRequiredField.as_str(),
170 "Contact name is required",
171 )
172 .with_field("name"));
173 }
174
175 if self.primary.trim().is_empty() {
176 return Err(ApiError::new(
177 ContactErrorCode::MissingRequiredField.as_str(),
178 "Primary contact number is required",
179 )
180 .with_field("primary"));
181 }
182
183 if let Some(email) = &self.email {
184 if !email.is_empty() && !email.contains('@') {
185 return Err(ApiError::new(
186 ContactErrorCode::InvalidEmail.as_str(),
187 "Invalid email format",
188 )
189 .with_field("email"));
190 }
191 }
192
193 Ok(())
194 }
195}
196
197impl UpdateContactRequest {
198 pub fn apply_to(self, contact: &mut Contact) {
199 contact.organisation = self.organisation;
200
201 if let Some(account) = self.account {
202 contact.account = account;
203 }
204 if let Some(primary) = self.primary {
205 contact.primary = primary;
206 }
207 if let Some(secondary) = self.secondary {
208 contact.secondary = Some(secondary);
209 }
210 if let Some(tertiary) = self.tertiary {
211 contact.tertiary = Some(tertiary);
212 }
213 if let Some(name) = self.name {
214 contact.name = name;
215 }
216 if let Some(nickname) = self.nickname {
217 contact.nickname = Some(nickname);
218 }
219 if let Some(email) = self.email {
220 contact.email = Some(email);
221 }
222 if let Some(capabilities) = self.capabilities {
223 contact.capabilities = capabilities;
224 }
225 if let Some(groups) = self.groups {
226 contact.groups = groups;
227 }
228 if let Some(device) = self.device {
229 contact.device = Some(device);
230 }
231 if let Some(opt_out) = self.opt_out {
232 contact.opt_out = Some(opt_out);
233 }
234 if let Some(group) = self.group {
235 contact.group = Some(group);
236 }
237 }
238
239 pub fn is_empty(&self) -> bool {
240 self.organisation.is_none()
241 && self.account.is_none()
242 && self.primary.is_none()
243 && self.secondary.is_none()
244 && self.tertiary.is_none()
245 && self.name.is_none()
246 && self.nickname.is_none()
247 && self.email.is_none()
248 && self.capabilities.is_none()
249 && self.groups.is_none()
250 && self.device.is_none()
251 && self.group.is_none()
252 && self.opt_out.is_none()
253 }
254}
255
256impl Default for ContactListRequest {
257 fn default() -> Self {
258 Self {
259 common: ListRequest {
260 pagination: PaginationParams::default(),
261 sort: SortParams::default(),
262 search: None,
263 time_range: None,
264 filters: None,
265 },
266 account_id: None,
267 organisation_id: None,
268 name: None,
269 primary: None,
270 email: None,
271 group: None,
272 capability: None,
273 device_id: None,
274 }
275 }
276}
277
278impl ContactListRequest {
280 pub fn new() -> Self {
281 Self::default()
282 }
283
284 pub fn with_pagination(mut self, page: u32, page_size: u32) -> Self {
285 self.common.pagination = PaginationParams::new(page, page_size);
286 self
287 }
288
289 pub fn with_sorting(mut self, sort_by: String, sort_order: SortOrder) -> Self {
290 self.common.sort = SortParams {
291 sort_by: Some(sort_by),
292 sort_order,
293 };
294 self
295 }
296
297 pub fn with_search(mut self, query: String) -> Self {
298 self.common.search = Some(SearchParams {
299 query,
300 fields: None,
301 fuzzy: false,
302 highlight: None,
303 });
304 self
305 }
306
307 pub fn with_account_id(mut self, account_id: String) -> Self {
308 self.account_id = Some(account_id);
309 self
310 }
311
312 pub fn with_organisation_id(mut self, organisation_id: String) -> Self {
313 self.organisation_id = Some(organisation_id);
314 self
315 }
316}