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: name (max 200 chars), avatar_url (HTTPS URL, max 2000 chars), admin_scopes, allowed_email_domains
66#[derive(Debug, Clone, Serialize, Deserialize)]
67#[serde(rename_all = "camelCase")]
68pub struct User {
69    pub id: String,
70    pub email: String,
71    /// User's display name (preferred)
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub name: Option<String>,
74    /// User's avatar URL (preferred)
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub avatar_url: Option<String>,
77    /// Deprecated: use name instead
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub user_name: Option<String>,
80    /// Deprecated: use avatar_url instead
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub user_avatar_url: Option<String>,
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub admin_scopes: Option<Vec<String>>,
85    /// Optional list of allowed email domains for invitation restrictions (e.g., ["acme.com", "acme.org"])
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub allowed_email_domains: Option<Vec<String>>,
88    /// Additional fields included in signing
89    #[serde(flatten)]
90    pub extra: Option<HashMap<String, serde_json::Value>>,
91}
92
93impl User {
94    pub fn new(id: &str, email: &str) -> Self {
95        Self {
96            id: id.to_string(),
97            email: email.to_string(),
98            name: None,
99            avatar_url: None,
100            user_name: None,
101            user_avatar_url: None,
102            admin_scopes: None,
103            allowed_email_domains: None,
104            extra: None,
105        }
106    }
107
108    pub fn with_name(mut self, name: &str) -> Self {
109        self.name = Some(name.to_string());
110        self
111    }
112
113    pub fn with_avatar_url(mut self, avatar_url: &str) -> Self {
114        self.avatar_url = Some(avatar_url.to_string());
115        self
116    }
117
118    /// Deprecated: use with_name instead
119    #[deprecated(note = "Use with_name instead")]
120    pub fn with_user_name(mut self, name: &str) -> Self {
121        self.user_name = Some(name.to_string());
122        self
123    }
124
125    /// Deprecated: use with_avatar_url instead
126    #[deprecated(note = "Use with_avatar_url instead")]
127    pub fn with_user_avatar_url(mut self, avatar_url: &str) -> Self {
128        self.user_avatar_url = Some(avatar_url.to_string());
129        self
130    }
131
132    pub fn with_admin_scopes(mut self, scopes: Vec<String>) -> Self {
133        self.admin_scopes = Some(scopes);
134        self
135    }
136
137    pub fn with_allowed_email_domains(mut self, domains: Vec<String>) -> Self {
138        self.allowed_email_domains = Some(domains);
139        self
140    }
141}
142
143/// Identifier for a user (email, sms, etc.)
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct Identifier {
146    #[serde(rename = "type")]
147    pub identifier_type: String,
148    pub value: String,
149}
150
151impl Identifier {
152    pub fn new(identifier_type: &str, value: &str) -> Self {
153        Self {
154            identifier_type: identifier_type.to_string(),
155            value: value.to_string(),
156        }
157    }
158}
159
160/// Group information for JWT generation (input)
161/// Supports both 'id' (legacy) and 'groupId' (preferred) for backward compatibility
162#[derive(Debug, Clone, Serialize, Deserialize)]
163#[serde(rename_all = "camelCase")]
164pub struct Group {
165    #[serde(rename = "type")]
166    pub scope_type: String,
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub id: Option<String>,
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub scope: Option<String>,
171    #[serde(rename = "groupId")]
172    pub name: String,
173}
174
175impl Group {
176    pub fn new(scope_type: &str, name: &str) -> Self {
177        Self {
178            scope_type: scope_type.to_string(),
179            id: None,
180            scope: None,
181            name: name.to_string(),
182        }
183    }
184
185    pub fn with_id(mut self, id: &str) -> Self {
186        self.id = Some(id.to_string());
187        self
188    }
189
190    pub fn with_scope(mut self, scope: &str) -> Self {
191        self.scope = Some(scope.to_string());
192        self
193    }
194}
195
196/// Invitation group from API responses
197/// This matches the MemberGroups table structure from the API
198#[derive(Debug, Clone, Serialize, Deserialize)]
199#[serde(rename_all = "camelCase")]
200pub struct InvitationScope {
201    /// Vortex internal UUID
202    pub id: String,
203    /// Vortex account ID
204    pub account_id: String,
205    /// Customer's group ID (the ID they provided to Vortex)
206    #[serde(rename = "groupId")]
207    pub scope: String,
208    /// Preferred alias for scope (= groupId value). Populated by SDK.
209    #[serde(skip)]
210    pub scope_id: String,
211    /// Group type (e.g., "workspace", "team")
212    #[serde(rename = "type")]
213    pub scope_type: String,
214    /// Group name
215    pub name: String,
216    /// ISO 8601 timestamp when the group was created
217    pub created_at: String,
218}
219
220/// Invitation target from API responses
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct InvitationTarget {
223    #[serde(rename = "type")]
224    pub target_type: InvitationTargetType,
225    pub value: String,
226    /// Display name of the person being invited
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub name: Option<String>,
229    /// Avatar URL for the person being invited (for display in invitation lists)
230    #[serde(skip_serializing_if = "Option::is_none")]
231    #[serde(rename = "avatarUrl")]
232    pub avatar_url: Option<String>,
233}
234
235impl InvitationTarget {
236    pub fn new(target_type: InvitationTargetType, value: &str) -> Self {
237        Self {
238            target_type,
239            value: value.to_string(),
240            name: None,
241            avatar_url: None,
242        }
243    }
244
245    pub fn email(value: &str) -> Self {
246        Self::new(InvitationTargetType::Email, value)
247    }
248
249    pub fn phone(value: &str) -> Self {
250        Self::new(InvitationTargetType::Phone, value)
251    }
252
253    pub fn with_name(mut self, name: &str) -> Self {
254        self.name = Some(name.to_string());
255        self
256    }
257
258    pub fn with_avatar_url(mut self, avatar_url: &str) -> Self {
259        self.avatar_url = Some(avatar_url.to_string());
260        self
261    }
262}
263
264/// User data for accepting invitations (preferred format)
265///
266/// At least one of email or phone must be provided.
267///
268/// # Example
269///
270/// ```
271/// use vortex_sdk::AcceptUser;
272///
273/// // With email only
274/// let user = AcceptUser::new().with_email("user@example.com");
275///
276/// // With email and name
277/// let user = AcceptUser::new()
278///     .with_email("user@example.com")
279///     .with_name("John Doe");
280///
281/// // With all fields
282/// let user = AcceptUser::new()
283///     .with_email("user@example.com")
284///     .with_phone("+1234567890")
285///     .with_name("John Doe")
286///     .with_is_existing(false);
287/// ```
288#[derive(Debug, Clone, Serialize, Deserialize, Default)]
289pub struct AcceptUser {
290    #[serde(skip_serializing_if = "Option::is_none")]
291    pub email: Option<String>,
292    #[serde(skip_serializing_if = "Option::is_none")]
293    pub phone: Option<String>,
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub name: Option<String>,
296    /// Whether the accepting user is an existing user in your system.
297    /// Set to true if the user was already registered before accepting the invitation.
298    /// Set to false if this is a new user signup.
299    /// Leave as None if unknown.
300    /// Used for analytics to track new vs existing user conversions.
301    #[serde(rename = "isExisting", skip_serializing_if = "Option::is_none")]
302    pub is_existing: Option<bool>,
303}
304
305impl AcceptUser {
306    pub fn new() -> Self {
307        Self::default()
308    }
309
310    pub fn with_email(mut self, email: &str) -> Self {
311        self.email = Some(email.to_string());
312        self
313    }
314
315    pub fn with_phone(mut self, phone: &str) -> Self {
316        self.phone = Some(phone.to_string());
317        self
318    }
319
320    pub fn with_name(mut self, name: &str) -> Self {
321        self.name = Some(name.to_string());
322        self
323    }
324
325    pub fn with_is_existing(mut self, is_existing: bool) -> Self {
326        self.is_existing = Some(is_existing);
327        self
328    }
329}
330
331/// Invitation acceptance information
332#[derive(Debug, Clone, Serialize, Deserialize)]
333#[serde(rename_all = "camelCase")]
334pub struct InvitationAcceptance {
335    pub id: Option<String>,
336    pub account_id: Option<String>,
337    pub project_id: Option<String>,
338    pub accepted_at: Option<String>,
339    pub target: Option<InvitationTarget>,
340}
341
342/// Full invitation details
343#[derive(Debug, Clone, Serialize, Deserialize)]
344#[serde(rename_all = "camelCase")]
345pub struct Invitation {
346    #[serde(default)]
347    pub id: String,
348    #[serde(default)]
349    pub account_id: String,
350    #[serde(default)]
351    pub click_throughs: u32,
352    pub configuration_attributes: Option<HashMap<String, serde_json::Value>>,
353    pub attributes: Option<HashMap<String, serde_json::Value>>,
354    #[serde(default)]
355    pub created_at: String,
356    #[serde(default)]
357    pub deactivated: bool,
358    #[serde(default)]
359    pub delivery_count: u32,
360    #[serde(default)]
361    pub delivery_types: Vec<DeliveryType>,
362    #[serde(default)]
363    pub foreign_creator_id: String,
364    pub invitation_type: InvitationType,
365    pub modified_at: Option<String>,
366    pub status: InvitationStatus,
367    #[serde(default)]
368    pub target: Vec<InvitationTarget>,
369    #[serde(default)]
370    pub views: u32,
371    #[serde(default)]
372    pub widget_configuration_id: String,
373    #[serde(default)]
374    pub project_id: String,
375    #[serde(default)]
376    pub groups: Vec<InvitationScope>,
377    /// Preferred alias for groups. Each element also has scope_id. Populated by SDK.
378    #[serde(skip)]
379    pub scopes: Vec<InvitationScope>,
380    #[serde(default)]
381    pub accepts: Vec<InvitationAcceptance>,
382    pub expired: bool,
383    #[serde(skip_serializing_if = "Option::is_none")]
384    pub expires: Option<String>,
385    #[serde(skip_serializing_if = "Option::is_none")]
386    pub source: Option<String>,
387    /// Customer-defined subtype for analytics segmentation (e.g., "pymk", "find-friends")
388    #[serde(skip_serializing_if = "Option::is_none")]
389    pub subtype: Option<String>,
390    #[serde(skip_serializing_if = "Option::is_none")]
391    pub creator_name: Option<String>,
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub creator_avatar_url: Option<String>,
394}
395
396/// Response containing multiple invitations
397#[derive(Debug, Clone, Serialize, Deserialize)]
398pub struct InvitationsResponse {
399    pub invitations: Option<Vec<Invitation>>,
400}
401
402/// Accept invitation parameter - supports both new User format and legacy Target format
403#[derive(Debug, Clone)]
404pub enum AcceptInvitationParam {
405    /// New User format (preferred)
406    User(AcceptUser),
407    /// Legacy target format (deprecated)
408    Target(InvitationTarget),
409    /// Legacy multiple targets format (deprecated)
410    Targets(Vec<InvitationTarget>),
411}
412
413impl From<AcceptUser> for AcceptInvitationParam {
414    fn from(user: AcceptUser) -> Self {
415        AcceptInvitationParam::User(user)
416    }
417}
418
419impl From<InvitationTarget> for AcceptInvitationParam {
420    fn from(target: InvitationTarget) -> Self {
421        AcceptInvitationParam::Target(target)
422    }
423}
424
425impl From<Vec<InvitationTarget>> for AcceptInvitationParam {
426    fn from(targets: Vec<InvitationTarget>) -> Self {
427        AcceptInvitationParam::Targets(targets)
428    }
429}
430
431// --- Types for creating invitations via backend API ---
432
433/// Target for creating an invitation
434#[derive(Debug, Clone, Serialize, Deserialize)]
435pub struct CreateInvitationTarget {
436    #[serde(rename = "type")]
437    pub target_type: CreateInvitationTargetType,
438    /// Target value: email address, phone number, or internal user ID
439    pub value: String,
440    /// Display name of the person being invited
441    #[serde(skip_serializing_if = "Option::is_none")]
442    pub name: Option<String>,
443    /// Avatar URL for the person being invited (for display in invitation lists)
444    #[serde(skip_serializing_if = "Option::is_none")]
445    #[serde(rename = "avatarUrl")]
446    pub avatar_url: Option<String>,
447}
448
449impl CreateInvitationTarget {
450    pub fn new(target_type: CreateInvitationTargetType, value: &str) -> Self {
451        Self {
452            target_type,
453            value: value.to_string(),
454            name: None,
455            avatar_url: None,
456        }
457    }
458
459    pub fn email(value: &str) -> Self {
460        Self::new(CreateInvitationTargetType::Email, value)
461    }
462
463    pub fn phone(value: &str) -> Self {
464        Self::new(CreateInvitationTargetType::Phone, value)
465    }
466
467    /// Alias for phone (backward compatibility)
468    pub fn sms(value: &str) -> Self {
469        Self::phone(value)
470    }
471
472    pub fn internal(value: &str) -> Self {
473        Self::new(CreateInvitationTargetType::Internal, value)
474    }
475
476    pub fn with_name(mut self, name: &str) -> Self {
477        self.name = Some(name.to_string());
478        self
479    }
480
481    pub fn with_avatar_url(mut self, avatar_url: &str) -> Self {
482        self.avatar_url = Some(avatar_url.to_string());
483        self
484    }
485}
486
487/// Information about the user creating the invitation (the inviter)
488#[derive(Debug, Clone, Serialize, Deserialize)]
489#[serde(rename_all = "camelCase")]
490pub struct Inviter {
491    /// Required: Your internal user ID for the inviter
492    pub user_id: String,
493    /// Optional: Email of the inviter
494    #[serde(skip_serializing_if = "Option::is_none")]
495    pub user_email: Option<String>,
496    /// Optional: Display name of the inviter (preferred)
497    #[serde(skip_serializing_if = "Option::is_none")]
498    pub name: Option<String>,
499    /// Optional: Avatar URL of the inviter (preferred)
500    #[serde(skip_serializing_if = "Option::is_none")]
501    pub avatar_url: Option<String>,
502    /// Deprecated: use name instead
503    #[serde(skip_serializing_if = "Option::is_none")]
504    pub user_name: Option<String>,
505    /// Deprecated: use avatar_url instead
506    #[serde(skip_serializing_if = "Option::is_none")]
507    pub user_avatar_url: Option<String>,
508}
509
510impl Inviter {
511    pub fn new(user_id: &str) -> Self {
512        Self {
513            user_id: user_id.to_string(),
514            user_email: None,
515            name: None,
516            avatar_url: None,
517            user_name: None,
518            user_avatar_url: None,
519        }
520    }
521
522    pub fn with_email(mut self, email: &str) -> Self {
523        self.user_email = Some(email.to_string());
524        self
525    }
526
527    pub fn with_name(mut self, name: &str) -> Self {
528        self.name = Some(name.to_string());
529        self
530    }
531
532    pub fn with_avatar_url(mut self, url: &str) -> Self {
533        self.avatar_url = Some(url.to_string());
534        self
535    }
536
537    /// Deprecated: use with_name instead
538    #[deprecated(note = "Use with_name instead")]
539    pub fn with_user_name(mut self, name: &str) -> Self {
540        self.user_name = Some(name.to_string());
541        self
542    }
543
544    /// Deprecated: use with_avatar_url instead
545    #[deprecated(note = "Use with_avatar_url instead")]
546    pub fn with_user_avatar_url(mut self, url: &str) -> Self {
547        self.user_avatar_url = Some(url.to_string());
548        self
549    }
550}
551
552/// Group information for creating invitations
553#[derive(Debug, Clone, Serialize, Deserialize)]
554#[serde(rename_all = "camelCase")]
555pub struct CreateInvitationScope {
556    /// Group type (e.g., "team", "organization")
557    #[serde(rename = "type")]
558    pub scope_type: String,
559    /// Your internal group ID
560    #[serde(rename = "groupId")]
561    pub scope: String,
562    /// Display name of the group
563    pub name: String,
564}
565
566impl CreateInvitationScope {
567    pub fn new(scope_type: &str, scope: &str, name: &str) -> Self {
568        Self {
569            scope_type: scope_type.to_string(),
570            scope: scope.to_string(),
571            name: name.to_string(),
572        }
573    }
574}
575
576/// Valid Open Graph types for unfurl configuration
577#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
578#[serde(rename_all = "snake_case")]
579pub enum UnfurlOgType {
580    Website,
581    Article,
582    Video,
583    Music,
584    Book,
585    Profile,
586    Product,
587}
588
589/// Configuration for link unfurl (Open Graph) metadata
590/// Controls how the invitation link appears when shared on social platforms or messaging apps
591#[derive(Debug, Clone, Serialize, Deserialize, Default)]
592#[serde(rename_all = "camelCase")]
593pub struct UnfurlConfig {
594    /// The title shown in link previews (og:title)
595    #[serde(skip_serializing_if = "Option::is_none")]
596    pub title: Option<String>,
597    /// The description shown in link previews (og:description)
598    #[serde(skip_serializing_if = "Option::is_none")]
599    pub description: Option<String>,
600    /// The image URL shown in link previews (og:image) - must be HTTPS
601    #[serde(skip_serializing_if = "Option::is_none")]
602    pub image: Option<String>,
603    /// The Open Graph type (og:type)
604    #[serde(skip_serializing_if = "Option::is_none", rename = "type")]
605    pub og_type: Option<UnfurlOgType>,
606    /// The site name shown in link previews (og:site_name)
607    #[serde(skip_serializing_if = "Option::is_none")]
608    pub site_name: Option<String>,
609}
610
611impl UnfurlConfig {
612    pub fn new() -> Self {
613        Self::default()
614    }
615
616    pub fn with_title(mut self, title: &str) -> Self {
617        self.title = Some(title.to_string());
618        self
619    }
620
621    pub fn with_description(mut self, description: &str) -> Self {
622        self.description = Some(description.to_string());
623        self
624    }
625
626    pub fn with_image(mut self, image: &str) -> Self {
627        self.image = Some(image.to_string());
628        self
629    }
630
631    pub fn with_type(mut self, og_type: UnfurlOgType) -> Self {
632        self.og_type = Some(og_type);
633        self
634    }
635
636    pub fn with_site_name(mut self, site_name: &str) -> Self {
637        self.site_name = Some(site_name.to_string());
638        self
639    }
640}
641
642/// Request body for creating an invitation
643#[derive(Debug, Clone, Serialize, Deserialize)]
644#[serde(rename_all = "camelCase")]
645pub struct CreateInvitationRequest {
646    pub widget_configuration_id: String,
647    pub target: CreateInvitationTarget,
648    pub inviter: Inviter,
649    #[serde(skip_serializing_if = "Option::is_none")]
650    pub groups: Option<Vec<CreateInvitationScope>>,
651    /// Preferred: flat scope ID for single scope (takes priority over groups/scopes)
652    #[serde(skip)]
653    pub scope_id: Option<String>,
654    /// Scope type when using flat scope_id param
655    #[serde(skip)]
656    pub scope_type_flat: Option<String>,
657    /// Scope name when using flat scope_id param
658    #[serde(skip)]
659    pub scope_name: Option<String>,
660    /// Deprecated: use scope_id/scope_type_flat/scope_name or groups
661    #[serde(skip)]
662    pub scopes: Option<Vec<CreateInvitationScope>>,
663    #[serde(skip_serializing_if = "Option::is_none")]
664    pub source: Option<String>,
665    /// Customer-defined subtype for analytics segmentation (e.g., "pymk", "find-friends")
666    #[serde(skip_serializing_if = "Option::is_none")]
667    pub subtype: Option<String>,
668    #[serde(skip_serializing_if = "Option::is_none")]
669    pub template_variables: Option<HashMap<String, String>>,
670    #[serde(skip_serializing_if = "Option::is_none")]
671    pub metadata: Option<HashMap<String, serde_json::Value>>,
672    #[serde(skip_serializing_if = "Option::is_none")]
673    pub unfurl_config: Option<UnfurlConfig>,
674}
675
676impl CreateInvitationRequest {
677    pub fn new(
678        widget_configuration_id: &str,
679        target: CreateInvitationTarget,
680        inviter: Inviter,
681    ) -> Self {
682        Self {
683            widget_configuration_id: widget_configuration_id.to_string(),
684            target,
685            inviter,
686            groups: None,
687            scope_id: None,
688            scope_type_flat: None,
689            scope_name: None,
690            scopes: None,
691            source: None,
692            subtype: None,
693            template_variables: None,
694            metadata: None,
695            unfurl_config: None,
696        }
697    }
698
699    pub fn with_groups(mut self, groups: Vec<CreateInvitationScope>) -> Self {
700        self.groups = Some(groups);
701        self
702    }
703
704    /// Set a single scope using flat params (preferred over with_groups)
705    pub fn with_scope(mut self, scope_id: &str, scope_type: &str, scope_name: &str) -> Self {
706        self.scope_id = Some(scope_id.to_string());
707        self.scope_type_flat = Some(scope_type.to_string());
708        self.scope_name = Some(scope_name.to_string());
709        self
710    }
711
712    pub fn with_source(mut self, source: &str) -> Self {
713        self.source = Some(source.to_string());
714        self
715    }
716
717    pub fn with_subtype(mut self, subtype: &str) -> Self {
718        self.subtype = Some(subtype.to_string());
719        self
720    }
721
722    pub fn with_template_variables(mut self, vars: HashMap<String, String>) -> Self {
723        self.template_variables = Some(vars);
724        self
725    }
726
727    pub fn with_metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
728        self.metadata = Some(metadata);
729        self
730    }
731
732    pub fn with_unfurl_config(mut self, unfurl_config: UnfurlConfig) -> Self {
733        self.unfurl_config = Some(unfurl_config);
734        self
735    }
736}
737
738/// Response from creating an invitation
739#[derive(Debug, Clone, Serialize, Deserialize)]
740#[serde(rename_all = "camelCase")]
741pub struct CreateInvitationResponse {
742    /// The ID of the created invitation
743    pub id: String,
744    /// The short link for the invitation
745    pub short_link: String,
746    /// The status of the invitation
747    pub status: String,
748    /// When the invitation was created
749    pub created_at: String,
750}
751
752// --- Types for syncing internal invitation actions ---
753
754/// Request body for syncing an internal invitation action
755#[derive(Debug, Clone, Serialize, Deserialize)]
756#[serde(rename_all = "camelCase")]
757pub struct SyncInternalInvitationRequest {
758    /// The inviter's user ID
759    pub creator_id: String,
760    /// The invitee's user ID
761    pub target_value: String,
762    /// The action taken: "accepted" or "declined"
763    pub action: String,
764    /// The widget component UUID
765    pub component_id: String,
766}
767
768impl SyncInternalInvitationRequest {
769    pub fn new(creator_id: &str, target_value: &str, action: &str, component_id: &str) -> Self {
770        Self {
771            creator_id: creator_id.to_string(),
772            target_value: target_value.to_string(),
773            action: action.to_string(),
774            component_id: component_id.to_string(),
775        }
776    }
777}
778
779/// Response from syncing an internal invitation action
780#[derive(Debug, Clone, Serialize, Deserialize)]
781#[serde(rename_all = "camelCase")]
782pub struct SyncInternalInvitationResponse {
783    /// Number of invitations processed
784    pub processed: u32,
785    /// IDs of the invitations that were processed
786    pub invitation_ids: Vec<String>,
787}
788
789// --- Types for autojoin domain management ---
790
791/// Represents an autojoin domain configuration
792#[derive(Debug, Clone, Serialize, Deserialize)]
793pub struct AutojoinDomain {
794    pub id: String,
795    pub domain: String,
796}
797
798/// Response from autojoin API endpoints
799#[derive(Debug, Clone, Serialize, Deserialize)]
800#[serde(rename_all = "camelCase")]
801pub struct AutojoinDomainsResponse {
802    pub autojoin_domains: Vec<AutojoinDomain>,
803    pub invitation: Option<Invitation>,
804}
805
806/// Request body for configuring autojoin domains
807#[derive(Debug, Clone, Serialize, Deserialize)]
808#[serde(rename_all = "camelCase")]
809pub struct ConfigureAutojoinRequest {
810    pub scope: String,
811    pub scope_type: String,
812    #[serde(skip_serializing_if = "Option::is_none")]
813    pub scope_name: Option<String>,
814    pub domains: Vec<String>,
815    pub widget_id: String,
816    #[serde(skip_serializing_if = "Option::is_none")]
817    pub metadata: Option<HashMap<String, serde_json::Value>>,
818}
819
820impl ConfigureAutojoinRequest {
821    pub fn new(scope: &str, scope_type: &str, domains: Vec<String>, widget_id: &str) -> Self {
822        Self {
823            scope: scope.to_string(),
824            scope_type: scope_type.to_string(),
825            scope_name: None,
826            domains,
827            widget_id: widget_id.to_string(),
828            metadata: None,
829        }
830    }
831
832    pub fn with_scope_name(mut self, scope_name: &str) -> Self {
833        self.scope_name = Some(scope_name.to_string());
834        self
835    }
836
837    pub fn with_metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
838        self.metadata = Some(metadata);
839        self
840    }
841}
842
843// Deprecated type aliases for backward compatibility
844#[deprecated(since = "0.1.0", note = "Use InvitationScope instead")]
845pub type InvitationGroup = InvitationScope;
846
847#[deprecated(since = "0.1.0", note = "Use CreateInvitationScope instead")]
848pub type CreateInvitationGroup = CreateInvitationScope;
849
850// ─── generate_token types ────────────────────────────────────
851
852/// Helper function to check if a string is empty (for serde skip_serializing_if)
853fn is_empty_string(s: &String) -> bool {
854    s.is_empty()
855}
856
857/// User data for generate_token
858#[derive(Debug, Clone, Serialize, Deserialize, Default)]
859#[serde(rename_all = "camelCase")]
860pub struct TokenUser {
861    #[serde(skip_serializing_if = "is_empty_string")]
862    pub id: String,
863    #[serde(skip_serializing_if = "Option::is_none")]
864    pub name: Option<String>,
865    #[serde(skip_serializing_if = "Option::is_none")]
866    pub email: Option<String>,
867    #[serde(skip_serializing_if = "Option::is_none")]
868    pub avatar_url: Option<String>,
869    #[serde(skip_serializing_if = "Option::is_none")]
870    pub admin_scopes: Option<Vec<String>>,
871    #[serde(skip_serializing_if = "Option::is_none")]
872    pub allowed_email_domains: Option<Vec<String>>,
873    #[serde(flatten)]
874    pub extra: Option<HashMap<String, serde_json::Value>>,
875}
876
877impl TokenUser {
878    pub fn new(id: &str) -> Self {
879        Self {
880            id: id.to_string(),
881            ..Default::default()
882        }
883    }
884
885    pub fn with_name(mut self, name: &str) -> Self {
886        self.name = Some(name.to_string());
887        self
888    }
889
890    pub fn with_email(mut self, email: &str) -> Self {
891        self.email = Some(email.to_string());
892        self
893    }
894}
895
896/// Payload for generate_token
897#[derive(Debug, Clone, Serialize, Deserialize, Default)]
898#[serde(rename_all = "camelCase")]
899pub struct GenerateTokenPayload {
900    #[serde(skip_serializing_if = "Option::is_none")]
901    pub user: Option<TokenUser>,
902    #[serde(skip_serializing_if = "Option::is_none")]
903    pub component: Option<String>,
904    #[serde(skip_serializing_if = "Option::is_none")]
905    pub scope: Option<String>,
906    #[serde(skip_serializing_if = "Option::is_none")]
907    pub vars: Option<HashMap<String, serde_json::Value>>,
908    #[serde(flatten)]
909    pub extra: Option<HashMap<String, serde_json::Value>>,
910}
911
912impl GenerateTokenPayload {
913    pub fn new() -> Self {
914        Self::default()
915    }
916
917    pub fn with_user(mut self, user: TokenUser) -> Self {
918        self.user = Some(user);
919        self
920    }
921
922    pub fn with_component(mut self, component: &str) -> Self {
923        self.component = Some(component.to_string());
924        self
925    }
926
927    pub fn with_scope(mut self, scope: &str) -> Self {
928        self.scope = Some(scope.to_string());
929        self
930    }
931}
932
933/// Expiration time for generate_token
934#[derive(Debug, Clone)]
935pub enum ExpiresIn {
936    /// Duration string like "5m", "1h", "24h", "7d"
937    Duration(String),
938    /// Seconds as integer
939    Seconds(u64),
940}
941
942impl From<&str> for ExpiresIn {
943    fn from(s: &str) -> Self {
944        ExpiresIn::Duration(s.to_string())
945    }
946}
947
948impl From<String> for ExpiresIn {
949    fn from(s: String) -> Self {
950        ExpiresIn::Duration(s)
951    }
952}
953
954impl From<u64> for ExpiresIn {
955    fn from(s: u64) -> Self {
956        ExpiresIn::Seconds(s)
957    }
958}
959
960impl From<i64> for ExpiresIn {
961    fn from(s: i64) -> Self {
962        ExpiresIn::Seconds(s as u64)
963    }
964}
965
966impl From<u32> for ExpiresIn {
967    fn from(s: u32) -> Self {
968        ExpiresIn::Seconds(s as u64)
969    }
970}
971
972impl From<i32> for ExpiresIn {
973    fn from(s: i32) -> Self {
974        ExpiresIn::Seconds(s as u64)
975    }
976}
977
978/// Options for generate_token
979#[derive(Debug, Clone, Default)]
980pub struct GenerateTokenOptions {
981    pub expires_in: Option<ExpiresIn>,
982}
983
984impl GenerateTokenOptions {
985    pub fn new() -> Self {
986        Self::default()
987    }
988
989    pub fn with_expires_in<T: Into<ExpiresIn>>(mut self, expires_in: T) -> Self {
990        self.expires_in = Some(expires_in.into());
991        self
992    }
993}