Skip to main content

vortex_sdk/
types.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4/// User type for JWT generation
5/// Optional fields: name (max 200 chars), avatar_url (HTTPS URL, max 2000 chars), admin_scopes
6#[derive(Debug, Clone, Serialize, Deserialize)]
7#[serde(rename_all = "camelCase")]
8pub struct User {
9    pub id: String,
10    pub email: String,
11    #[serde(skip_serializing_if = "Option::is_none")]
12    pub name: Option<String>,
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub avatar_url: Option<String>,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub admin_scopes: Option<Vec<String>>,
17}
18
19impl User {
20    pub fn new(id: &str, email: &str) -> Self {
21        Self {
22            id: id.to_string(),
23            email: email.to_string(),
24            name: None,
25            avatar_url: None,
26            admin_scopes: None,
27        }
28    }
29
30    pub fn with_name(mut self, name: &str) -> Self {
31        self.name = Some(name.to_string());
32        self
33    }
34
35    pub fn with_avatar_url(mut self, avatar_url: &str) -> Self {
36        self.avatar_url = Some(avatar_url.to_string());
37        self
38    }
39
40    pub fn with_admin_scopes(mut self, scopes: Vec<String>) -> Self {
41        self.admin_scopes = Some(scopes);
42        self
43    }
44}
45
46/// Identifier for a user (email, sms, etc.)
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct Identifier {
49    #[serde(rename = "type")]
50    pub identifier_type: String,
51    pub value: String,
52}
53
54impl Identifier {
55    pub fn new(identifier_type: &str, value: &str) -> Self {
56        Self {
57            identifier_type: identifier_type.to_string(),
58            value: value.to_string(),
59        }
60    }
61}
62
63/// Group information for JWT generation (input)
64/// Supports both 'id' (legacy) and 'groupId' (preferred) for backward compatibility
65#[derive(Debug, Clone, Serialize, Deserialize)]
66#[serde(rename_all = "camelCase")]
67pub struct Group {
68    #[serde(rename = "type")]
69    pub group_type: String,
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub id: Option<String>,
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub group_id: Option<String>,
74    pub name: String,
75}
76
77impl Group {
78    pub fn new(group_type: &str, name: &str) -> Self {
79        Self {
80            group_type: group_type.to_string(),
81            id: None,
82            group_id: None,
83            name: name.to_string(),
84        }
85    }
86
87    pub fn with_id(mut self, id: &str) -> Self {
88        self.id = Some(id.to_string());
89        self
90    }
91
92    pub fn with_group_id(mut self, group_id: &str) -> Self {
93        self.group_id = Some(group_id.to_string());
94        self
95    }
96}
97
98/// Invitation group from API responses
99/// This matches the MemberGroups table structure from the API
100#[derive(Debug, Clone, Serialize, Deserialize)]
101#[serde(rename_all = "camelCase")]
102pub struct InvitationGroup {
103    /// Vortex internal UUID
104    pub id: String,
105    /// Vortex account ID
106    pub account_id: String,
107    /// Customer's group ID (the ID they provided to Vortex)
108    pub group_id: String,
109    /// Group type (e.g., "workspace", "team")
110    #[serde(rename = "type")]
111    pub group_type: String,
112    /// Group name
113    pub name: String,
114    /// ISO 8601 timestamp when the group was created
115    pub created_at: String,
116}
117
118/// Invitation target (email or sms) - DEPRECATED
119/// Use AcceptUser instead for accepting invitations
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct InvitationTarget {
122    #[serde(rename = "type")]
123    pub target_type: String,
124    pub value: String,
125}
126
127impl InvitationTarget {
128    pub fn new(target_type: &str, value: &str) -> Self {
129        Self {
130            target_type: target_type.to_string(),
131            value: value.to_string(),
132        }
133    }
134}
135
136/// User data for accepting invitations (preferred format)
137///
138/// At least one of email or phone must be provided.
139///
140/// # Example
141///
142/// ```
143/// use vortex_sdk::AcceptUser;
144///
145/// // With email only
146/// let user = AcceptUser::new().with_email("user@example.com");
147///
148/// // With email and name
149/// let user = AcceptUser::new()
150///     .with_email("user@example.com")
151///     .with_name("John Doe");
152///
153/// // With all fields
154/// let user = AcceptUser::new()
155///     .with_email("user@example.com")
156///     .with_phone("+1234567890")
157///     .with_name("John Doe");
158/// ```
159#[derive(Debug, Clone, Serialize, Deserialize, Default)]
160pub struct AcceptUser {
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub email: Option<String>,
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub phone: Option<String>,
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub name: Option<String>,
167}
168
169impl AcceptUser {
170    pub fn new() -> Self {
171        Self::default()
172    }
173
174    pub fn with_email(mut self, email: &str) -> Self {
175        self.email = Some(email.to_string());
176        self
177    }
178
179    pub fn with_phone(mut self, phone: &str) -> Self {
180        self.phone = Some(phone.to_string());
181        self
182    }
183
184    pub fn with_name(mut self, name: &str) -> Self {
185        self.name = Some(name.to_string());
186        self
187    }
188}
189
190/// Invitation acceptance information
191#[derive(Debug, Clone, Serialize, Deserialize)]
192#[serde(rename_all = "camelCase")]
193pub struct InvitationAcceptance {
194    pub id: Option<String>,
195    pub account_id: Option<String>,
196    pub project_id: Option<String>,
197    pub accepted_at: Option<String>,
198    pub target: Option<InvitationTarget>,
199}
200
201/// Full invitation details
202#[derive(Debug, Clone, Serialize, Deserialize)]
203#[serde(rename_all = "camelCase")]
204pub struct Invitation {
205    #[serde(default)]
206    pub id: String,
207    #[serde(default)]
208    pub account_id: String,
209    #[serde(default)]
210    pub click_throughs: u32,
211    pub configuration_attributes: Option<HashMap<String, serde_json::Value>>,
212    pub attributes: Option<HashMap<String, serde_json::Value>>,
213    #[serde(default)]
214    pub created_at: String,
215    #[serde(default)]
216    pub deactivated: bool,
217    #[serde(default)]
218    pub delivery_count: u32,
219    /// Valid values: "email", "phone", "share", "internal"
220    #[serde(default)]
221    pub delivery_types: Vec<String>,
222    #[serde(default)]
223    pub foreign_creator_id: String,
224    #[serde(default)]
225    pub invitation_type: String,
226    pub modified_at: Option<String>,
227    #[serde(default)]
228    pub status: String,
229    #[serde(default)]
230    pub target: Vec<InvitationTarget>,
231    #[serde(default)]
232    pub views: u32,
233    #[serde(default)]
234    pub widget_configuration_id: String,
235    #[serde(default)]
236    pub project_id: String,
237    #[serde(default)]
238    pub groups: Vec<InvitationGroup>,
239    #[serde(default)]
240    pub accepts: Vec<InvitationAcceptance>,
241    pub expired: bool,
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub expires: Option<String>,
244    #[serde(skip_serializing_if = "Option::is_none")]
245    pub source: Option<String>,
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub creator_name: Option<String>,
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub creator_avatar_url: Option<String>,
250}
251
252/// Response containing multiple invitations
253#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct InvitationsResponse {
255    pub invitations: Option<Vec<Invitation>>,
256}
257
258/// Accept invitation parameter - supports both new User format and legacy Target format
259#[derive(Debug, Clone)]
260pub enum AcceptInvitationParam {
261    /// New User format (preferred)
262    User(AcceptUser),
263    /// Legacy target format (deprecated)
264    Target(InvitationTarget),
265    /// Legacy multiple targets format (deprecated)
266    Targets(Vec<InvitationTarget>),
267}
268
269impl From<AcceptUser> for AcceptInvitationParam {
270    fn from(user: AcceptUser) -> Self {
271        AcceptInvitationParam::User(user)
272    }
273}
274
275impl From<InvitationTarget> for AcceptInvitationParam {
276    fn from(target: InvitationTarget) -> Self {
277        AcceptInvitationParam::Target(target)
278    }
279}
280
281impl From<Vec<InvitationTarget>> for AcceptInvitationParam {
282    fn from(targets: Vec<InvitationTarget>) -> Self {
283        AcceptInvitationParam::Targets(targets)
284    }
285}
286
287// --- Types for creating invitations via backend API ---
288
289/// Target for creating an invitation
290#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct CreateInvitationTarget {
292    /// Target type: "email", "phone", or "internal"
293    #[serde(rename = "type")]
294    pub target_type: String,
295    /// Target value: email address, phone number, or internal user ID
296    pub value: String,
297}
298
299impl CreateInvitationTarget {
300    pub fn email(value: &str) -> Self {
301        Self {
302            target_type: "email".to_string(),
303            value: value.to_string(),
304        }
305    }
306
307    pub fn sms(value: &str) -> Self {
308        Self {
309            target_type: "phone".to_string(),
310            value: value.to_string(),
311        }
312    }
313
314    pub fn internal(value: &str) -> Self {
315        Self {
316            target_type: "internal".to_string(),
317            value: value.to_string(),
318        }
319    }
320}
321
322/// Information about the user creating the invitation (the inviter)
323#[derive(Debug, Clone, Serialize, Deserialize)]
324#[serde(rename_all = "camelCase")]
325pub struct Inviter {
326    /// Required: Your internal user ID for the inviter
327    pub user_id: String,
328    /// Optional: Email of the inviter
329    #[serde(skip_serializing_if = "Option::is_none")]
330    pub user_email: Option<String>,
331    /// Optional: Display name of the inviter
332    #[serde(skip_serializing_if = "Option::is_none")]
333    pub name: Option<String>,
334    /// Optional: Avatar URL of the inviter
335    #[serde(skip_serializing_if = "Option::is_none")]
336    pub avatar_url: Option<String>,
337}
338
339impl Inviter {
340    pub fn new(user_id: &str) -> Self {
341        Self {
342            user_id: user_id.to_string(),
343            user_email: None,
344            name: None,
345            avatar_url: None,
346        }
347    }
348
349    pub fn with_email(mut self, email: &str) -> Self {
350        self.user_email = Some(email.to_string());
351        self
352    }
353
354    pub fn with_name(mut self, name: &str) -> Self {
355        self.name = Some(name.to_string());
356        self
357    }
358
359    pub fn with_avatar_url(mut self, url: &str) -> Self {
360        self.avatar_url = Some(url.to_string());
361        self
362    }
363}
364
365/// Group information for creating invitations
366#[derive(Debug, Clone, Serialize, Deserialize)]
367#[serde(rename_all = "camelCase")]
368pub struct CreateInvitationGroup {
369    /// Group type (e.g., "team", "organization")
370    #[serde(rename = "type")]
371    pub group_type: String,
372    /// Your internal group ID
373    pub group_id: String,
374    /// Display name of the group
375    pub name: String,
376}
377
378impl CreateInvitationGroup {
379    pub fn new(group_type: &str, group_id: &str, name: &str) -> Self {
380        Self {
381            group_type: group_type.to_string(),
382            group_id: group_id.to_string(),
383            name: name.to_string(),
384        }
385    }
386}
387
388/// Request body for creating an invitation
389#[derive(Debug, Clone, Serialize, Deserialize)]
390#[serde(rename_all = "camelCase")]
391pub struct CreateInvitationRequest {
392    pub widget_configuration_id: String,
393    pub target: CreateInvitationTarget,
394    pub inviter: Inviter,
395    #[serde(skip_serializing_if = "Option::is_none")]
396    pub groups: Option<Vec<CreateInvitationGroup>>,
397    #[serde(skip_serializing_if = "Option::is_none")]
398    pub source: Option<String>,
399    #[serde(skip_serializing_if = "Option::is_none")]
400    pub template_variables: Option<HashMap<String, String>>,
401    #[serde(skip_serializing_if = "Option::is_none")]
402    pub metadata: Option<HashMap<String, serde_json::Value>>,
403}
404
405impl CreateInvitationRequest {
406    pub fn new(
407        widget_configuration_id: &str,
408        target: CreateInvitationTarget,
409        inviter: Inviter,
410    ) -> Self {
411        Self {
412            widget_configuration_id: widget_configuration_id.to_string(),
413            target,
414            inviter,
415            groups: None,
416            source: None,
417            template_variables: None,
418            metadata: None,
419        }
420    }
421
422    pub fn with_groups(mut self, groups: Vec<CreateInvitationGroup>) -> Self {
423        self.groups = Some(groups);
424        self
425    }
426
427    pub fn with_source(mut self, source: &str) -> Self {
428        self.source = Some(source.to_string());
429        self
430    }
431
432    pub fn with_template_variables(mut self, vars: HashMap<String, String>) -> Self {
433        self.template_variables = Some(vars);
434        self
435    }
436
437    pub fn with_metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
438        self.metadata = Some(metadata);
439        self
440    }
441}
442
443/// Response from creating an invitation
444#[derive(Debug, Clone, Serialize, Deserialize)]
445#[serde(rename_all = "camelCase")]
446pub struct CreateInvitationResponse {
447    /// The ID of the created invitation
448    pub id: String,
449    /// The short link for the invitation
450    pub short_link: String,
451    /// The status of the invitation
452    pub status: String,
453    /// When the invitation was created
454    pub created_at: String,
455}