Skip to main content

vortex_sdk/
types.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4// ============================================================================
5// Enums for type-safe API values
6// ============================================================================
7
8/// Target type for invitation responses (who was invited)
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum InvitationTargetType {
12    Email,
13    Phone,
14    Share,
15    Internal,
16}
17
18/// Target type for creating invitations
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(rename_all = "snake_case")]
21pub enum CreateInvitationTargetType {
22    Email,
23    Phone,
24    Internal,
25}
26
27/// Type of invitation
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "snake_case")]
30pub enum InvitationType {
31    SingleUse,
32    MultiUse,
33    Autojoin,
34}
35
36/// Current status of an invitation
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "snake_case")]
39pub enum InvitationStatus {
40    Queued,
41    Sending,
42    Sent,
43    Delivered,
44    Accepted,
45    Shared,
46    Unfurled,
47    AcceptedElsewhere,
48}
49
50/// Delivery type for invitations
51#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
52#[serde(rename_all = "snake_case")]
53pub enum DeliveryType {
54    Email,
55    Phone,
56    Share,
57    Internal,
58}
59
60// ============================================================================
61// Core types
62// ============================================================================
63
64/// User type for JWT generation
65/// Optional fields: user_name (max 200 chars), user_avatar_url (HTTPS URL, max 2000 chars), admin_scopes
66#[derive(Debug, Clone, Serialize, Deserialize)]
67#[serde(rename_all = "camelCase")]
68pub struct User {
69    pub id: String,
70    pub email: String,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub user_name: Option<String>,
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub user_avatar_url: Option<String>,
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub admin_scopes: Option<Vec<String>>,
77}
78
79impl User {
80    pub fn new(id: &str, email: &str) -> Self {
81        Self {
82            id: id.to_string(),
83            email: email.to_string(),
84            user_name: None,
85            user_avatar_url: None,
86            admin_scopes: None,
87        }
88    }
89
90    pub fn with_user_name(mut self, name: &str) -> Self {
91        self.user_name = Some(name.to_string());
92        self
93    }
94
95    pub fn with_user_avatar_url(mut self, avatar_url: &str) -> Self {
96        self.user_avatar_url = Some(avatar_url.to_string());
97        self
98    }
99
100    pub fn with_admin_scopes(mut self, scopes: Vec<String>) -> Self {
101        self.admin_scopes = Some(scopes);
102        self
103    }
104}
105
106/// Identifier for a user (email, sms, etc.)
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct Identifier {
109    #[serde(rename = "type")]
110    pub identifier_type: String,
111    pub value: String,
112}
113
114impl Identifier {
115    pub fn new(identifier_type: &str, value: &str) -> Self {
116        Self {
117            identifier_type: identifier_type.to_string(),
118            value: value.to_string(),
119        }
120    }
121}
122
123/// Group information for JWT generation (input)
124/// Supports both 'id' (legacy) and 'groupId' (preferred) for backward compatibility
125#[derive(Debug, Clone, Serialize, Deserialize)]
126#[serde(rename_all = "camelCase")]
127pub struct Group {
128    #[serde(rename = "type")]
129    pub group_type: String,
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub id: Option<String>,
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub group_id: Option<String>,
134    pub name: String,
135}
136
137impl Group {
138    pub fn new(group_type: &str, name: &str) -> Self {
139        Self {
140            group_type: group_type.to_string(),
141            id: None,
142            group_id: None,
143            name: name.to_string(),
144        }
145    }
146
147    pub fn with_id(mut self, id: &str) -> Self {
148        self.id = Some(id.to_string());
149        self
150    }
151
152    pub fn with_group_id(mut self, group_id: &str) -> Self {
153        self.group_id = Some(group_id.to_string());
154        self
155    }
156}
157
158/// Invitation group from API responses
159/// This matches the MemberGroups table structure from the API
160#[derive(Debug, Clone, Serialize, Deserialize)]
161#[serde(rename_all = "camelCase")]
162pub struct InvitationGroup {
163    /// Vortex internal UUID
164    pub id: String,
165    /// Vortex account ID
166    pub account_id: String,
167    /// Customer's group ID (the ID they provided to Vortex)
168    pub group_id: String,
169    /// Group type (e.g., "workspace", "team")
170    #[serde(rename = "type")]
171    pub group_type: String,
172    /// Group name
173    pub name: String,
174    /// ISO 8601 timestamp when the group was created
175    pub created_at: String,
176}
177
178/// Invitation target from API responses
179#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct InvitationTarget {
181    #[serde(rename = "type")]
182    pub target_type: InvitationTargetType,
183    pub value: String,
184}
185
186impl InvitationTarget {
187    pub fn new(target_type: InvitationTargetType, value: &str) -> Self {
188        Self {
189            target_type,
190            value: value.to_string(),
191        }
192    }
193
194    pub fn email(value: &str) -> Self {
195        Self::new(InvitationTargetType::Email, value)
196    }
197
198    pub fn phone(value: &str) -> Self {
199        Self::new(InvitationTargetType::Phone, value)
200    }
201}
202
203/// User data for accepting invitations (preferred format)
204///
205/// At least one of email or phone must be provided.
206///
207/// # Example
208///
209/// ```
210/// use vortex_sdk::AcceptUser;
211///
212/// // With email only
213/// let user = AcceptUser::new().with_email("user@example.com");
214///
215/// // With email and name
216/// let user = AcceptUser::new()
217///     .with_email("user@example.com")
218///     .with_name("John Doe");
219///
220/// // With all fields
221/// let user = AcceptUser::new()
222///     .with_email("user@example.com")
223///     .with_phone("+1234567890")
224///     .with_name("John Doe");
225/// ```
226#[derive(Debug, Clone, Serialize, Deserialize, Default)]
227pub struct AcceptUser {
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub email: Option<String>,
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub phone: Option<String>,
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub name: Option<String>,
234}
235
236impl AcceptUser {
237    pub fn new() -> Self {
238        Self::default()
239    }
240
241    pub fn with_email(mut self, email: &str) -> Self {
242        self.email = Some(email.to_string());
243        self
244    }
245
246    pub fn with_phone(mut self, phone: &str) -> Self {
247        self.phone = Some(phone.to_string());
248        self
249    }
250
251    pub fn with_name(mut self, name: &str) -> Self {
252        self.name = Some(name.to_string());
253        self
254    }
255}
256
257/// Invitation acceptance information
258#[derive(Debug, Clone, Serialize, Deserialize)]
259#[serde(rename_all = "camelCase")]
260pub struct InvitationAcceptance {
261    pub id: Option<String>,
262    pub account_id: Option<String>,
263    pub project_id: Option<String>,
264    pub accepted_at: Option<String>,
265    pub target: Option<InvitationTarget>,
266}
267
268/// Full invitation details
269#[derive(Debug, Clone, Serialize, Deserialize)]
270#[serde(rename_all = "camelCase")]
271pub struct Invitation {
272    #[serde(default)]
273    pub id: String,
274    #[serde(default)]
275    pub account_id: String,
276    #[serde(default)]
277    pub click_throughs: u32,
278    pub configuration_attributes: Option<HashMap<String, serde_json::Value>>,
279    pub attributes: Option<HashMap<String, serde_json::Value>>,
280    #[serde(default)]
281    pub created_at: String,
282    #[serde(default)]
283    pub deactivated: bool,
284    #[serde(default)]
285    pub delivery_count: u32,
286    #[serde(default)]
287    pub delivery_types: Vec<DeliveryType>,
288    #[serde(default)]
289    pub foreign_creator_id: String,
290    pub invitation_type: InvitationType,
291    pub modified_at: Option<String>,
292    pub status: InvitationStatus,
293    #[serde(default)]
294    pub target: Vec<InvitationTarget>,
295    #[serde(default)]
296    pub views: u32,
297    #[serde(default)]
298    pub widget_configuration_id: String,
299    #[serde(default)]
300    pub project_id: String,
301    #[serde(default)]
302    pub groups: Vec<InvitationGroup>,
303    #[serde(default)]
304    pub accepts: Vec<InvitationAcceptance>,
305    pub expired: bool,
306    #[serde(skip_serializing_if = "Option::is_none")]
307    pub expires: Option<String>,
308    #[serde(skip_serializing_if = "Option::is_none")]
309    pub source: Option<String>,
310    #[serde(skip_serializing_if = "Option::is_none")]
311    pub creator_name: Option<String>,
312    #[serde(skip_serializing_if = "Option::is_none")]
313    pub creator_avatar_url: Option<String>,
314}
315
316/// Response containing multiple invitations
317#[derive(Debug, Clone, Serialize, Deserialize)]
318pub struct InvitationsResponse {
319    pub invitations: Option<Vec<Invitation>>,
320}
321
322/// Accept invitation parameter - supports both new User format and legacy Target format
323#[derive(Debug, Clone)]
324pub enum AcceptInvitationParam {
325    /// New User format (preferred)
326    User(AcceptUser),
327    /// Legacy target format (deprecated)
328    Target(InvitationTarget),
329    /// Legacy multiple targets format (deprecated)
330    Targets(Vec<InvitationTarget>),
331}
332
333impl From<AcceptUser> for AcceptInvitationParam {
334    fn from(user: AcceptUser) -> Self {
335        AcceptInvitationParam::User(user)
336    }
337}
338
339impl From<InvitationTarget> for AcceptInvitationParam {
340    fn from(target: InvitationTarget) -> Self {
341        AcceptInvitationParam::Target(target)
342    }
343}
344
345impl From<Vec<InvitationTarget>> for AcceptInvitationParam {
346    fn from(targets: Vec<InvitationTarget>) -> Self {
347        AcceptInvitationParam::Targets(targets)
348    }
349}
350
351// --- Types for creating invitations via backend API ---
352
353/// Target for creating an invitation
354#[derive(Debug, Clone, Serialize, Deserialize)]
355pub struct CreateInvitationTarget {
356    #[serde(rename = "type")]
357    pub target_type: CreateInvitationTargetType,
358    /// Target value: email address, phone number, or internal user ID
359    pub value: String,
360}
361
362impl CreateInvitationTarget {
363    pub fn new(target_type: CreateInvitationTargetType, value: &str) -> Self {
364        Self {
365            target_type,
366            value: value.to_string(),
367        }
368    }
369
370    pub fn email(value: &str) -> Self {
371        Self::new(CreateInvitationTargetType::Email, value)
372    }
373
374    pub fn phone(value: &str) -> Self {
375        Self::new(CreateInvitationTargetType::Phone, value)
376    }
377
378    /// Alias for phone (backward compatibility)
379    pub fn sms(value: &str) -> Self {
380        Self::phone(value)
381    }
382
383    pub fn internal(value: &str) -> Self {
384        Self::new(CreateInvitationTargetType::Internal, value)
385    }
386}
387
388/// Information about the user creating the invitation (the inviter)
389#[derive(Debug, Clone, Serialize, Deserialize)]
390#[serde(rename_all = "camelCase")]
391pub struct Inviter {
392    /// Required: Your internal user ID for the inviter
393    pub user_id: String,
394    /// Optional: Email of the inviter
395    #[serde(skip_serializing_if = "Option::is_none")]
396    pub user_email: Option<String>,
397    /// Optional: Display name of the inviter
398    #[serde(skip_serializing_if = "Option::is_none")]
399    pub user_name: Option<String>,
400    /// Optional: Avatar URL of the inviter
401    #[serde(skip_serializing_if = "Option::is_none")]
402    pub user_avatar_url: Option<String>,
403}
404
405impl Inviter {
406    pub fn new(user_id: &str) -> Self {
407        Self {
408            user_id: user_id.to_string(),
409            user_email: None,
410            user_name: None,
411            user_avatar_url: None,
412        }
413    }
414
415    pub fn with_email(mut self, email: &str) -> Self {
416        self.user_email = Some(email.to_string());
417        self
418    }
419
420    pub fn with_user_name(mut self, name: &str) -> Self {
421        self.user_name = Some(name.to_string());
422        self
423    }
424
425    pub fn with_user_avatar_url(mut self, url: &str) -> Self {
426        self.user_avatar_url = Some(url.to_string());
427        self
428    }
429}
430
431/// Group information for creating invitations
432#[derive(Debug, Clone, Serialize, Deserialize)]
433#[serde(rename_all = "camelCase")]
434pub struct CreateInvitationGroup {
435    /// Group type (e.g., "team", "organization")
436    #[serde(rename = "type")]
437    pub group_type: String,
438    /// Your internal group ID
439    pub group_id: String,
440    /// Display name of the group
441    pub name: String,
442}
443
444impl CreateInvitationGroup {
445    pub fn new(group_type: &str, group_id: &str, name: &str) -> Self {
446        Self {
447            group_type: group_type.to_string(),
448            group_id: group_id.to_string(),
449            name: name.to_string(),
450        }
451    }
452}
453
454/// Valid Open Graph types for unfurl configuration
455#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
456#[serde(rename_all = "snake_case")]
457pub enum UnfurlOgType {
458    Website,
459    Article,
460    Video,
461    Music,
462    Book,
463    Profile,
464    Product,
465}
466
467/// Configuration for link unfurl (Open Graph) metadata
468/// Controls how the invitation link appears when shared on social platforms or messaging apps
469#[derive(Debug, Clone, Serialize, Deserialize, Default)]
470#[serde(rename_all = "camelCase")]
471pub struct UnfurlConfig {
472    /// The title shown in link previews (og:title)
473    #[serde(skip_serializing_if = "Option::is_none")]
474    pub title: Option<String>,
475    /// The description shown in link previews (og:description)
476    #[serde(skip_serializing_if = "Option::is_none")]
477    pub description: Option<String>,
478    /// The image URL shown in link previews (og:image) - must be HTTPS
479    #[serde(skip_serializing_if = "Option::is_none")]
480    pub image: Option<String>,
481    /// The Open Graph type (og:type)
482    #[serde(skip_serializing_if = "Option::is_none", rename = "type")]
483    pub og_type: Option<UnfurlOgType>,
484    /// The site name shown in link previews (og:site_name)
485    #[serde(skip_serializing_if = "Option::is_none")]
486    pub site_name: Option<String>,
487}
488
489impl UnfurlConfig {
490    pub fn new() -> Self {
491        Self::default()
492    }
493
494    pub fn with_title(mut self, title: &str) -> Self {
495        self.title = Some(title.to_string());
496        self
497    }
498
499    pub fn with_description(mut self, description: &str) -> Self {
500        self.description = Some(description.to_string());
501        self
502    }
503
504    pub fn with_image(mut self, image: &str) -> Self {
505        self.image = Some(image.to_string());
506        self
507    }
508
509    pub fn with_type(mut self, og_type: UnfurlOgType) -> Self {
510        self.og_type = Some(og_type);
511        self
512    }
513
514    pub fn with_site_name(mut self, site_name: &str) -> Self {
515        self.site_name = Some(site_name.to_string());
516        self
517    }
518}
519
520/// Request body for creating an invitation
521#[derive(Debug, Clone, Serialize, Deserialize)]
522#[serde(rename_all = "camelCase")]
523pub struct CreateInvitationRequest {
524    pub widget_configuration_id: String,
525    pub target: CreateInvitationTarget,
526    pub inviter: Inviter,
527    #[serde(skip_serializing_if = "Option::is_none")]
528    pub groups: Option<Vec<CreateInvitationGroup>>,
529    #[serde(skip_serializing_if = "Option::is_none")]
530    pub source: Option<String>,
531    #[serde(skip_serializing_if = "Option::is_none")]
532    pub template_variables: Option<HashMap<String, String>>,
533    #[serde(skip_serializing_if = "Option::is_none")]
534    pub metadata: Option<HashMap<String, serde_json::Value>>,
535    #[serde(skip_serializing_if = "Option::is_none")]
536    pub unfurl_config: Option<UnfurlConfig>,
537}
538
539impl CreateInvitationRequest {
540    pub fn new(
541        widget_configuration_id: &str,
542        target: CreateInvitationTarget,
543        inviter: Inviter,
544    ) -> Self {
545        Self {
546            widget_configuration_id: widget_configuration_id.to_string(),
547            target,
548            inviter,
549            groups: None,
550            source: None,
551            template_variables: None,
552            metadata: None,
553            unfurl_config: None,
554        }
555    }
556
557    pub fn with_groups(mut self, groups: Vec<CreateInvitationGroup>) -> Self {
558        self.groups = Some(groups);
559        self
560    }
561
562    pub fn with_source(mut self, source: &str) -> Self {
563        self.source = Some(source.to_string());
564        self
565    }
566
567    pub fn with_template_variables(mut self, vars: HashMap<String, String>) -> Self {
568        self.template_variables = Some(vars);
569        self
570    }
571
572    pub fn with_metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
573        self.metadata = Some(metadata);
574        self
575    }
576
577    pub fn with_unfurl_config(mut self, unfurl_config: UnfurlConfig) -> Self {
578        self.unfurl_config = Some(unfurl_config);
579        self
580    }
581}
582
583/// Response from creating an invitation
584#[derive(Debug, Clone, Serialize, Deserialize)]
585#[serde(rename_all = "camelCase")]
586pub struct CreateInvitationResponse {
587    /// The ID of the created invitation
588    pub id: String,
589    /// The short link for the invitation
590    pub short_link: String,
591    /// The status of the invitation
592    pub status: String,
593    /// When the invitation was created
594    pub created_at: String,
595}