shuttle_common/models/
user.rs

1#[cfg(feature = "display")]
2use std::fmt::Write;
3
4use chrono::{DateTime, Utc};
5#[cfg(feature = "display")]
6use crossterm::style::Stylize;
7use serde::{Deserialize, Serialize};
8use strum::{EnumString, IntoStaticStr};
9
10#[derive(Debug, Deserialize, Serialize)]
11#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
12#[typeshare::typeshare]
13pub struct UserResponse {
14    pub id: String,
15    /// Auth0 id (deprecated)
16    pub name: Option<String>,
17    /// Auth0 id
18    pub auth0_id: Option<String>,
19    // deprecated
20    pub key: Option<String>,
21    pub account_tier: AccountTier,
22    pub subscriptions: Vec<Subscription>,
23    pub flags: Option<Vec<String>>,
24}
25
26impl UserResponse {
27    #[cfg(feature = "display")]
28    pub fn to_string_colored(&self) -> String {
29        let mut s = String::new();
30        writeln!(&mut s, "{}", "Account info:".bold()).unwrap();
31        writeln!(&mut s, "  User ID: {}", self.id).unwrap();
32        writeln!(
33            &mut s,
34            "  Account tier: {}",
35            self.account_tier.to_string_fancy()
36        )
37        .unwrap();
38        if !self.subscriptions.is_empty() {
39            writeln!(&mut s, "  Subscriptions:").unwrap();
40            for sub in &self.subscriptions {
41                writeln!(
42                    &mut s,
43                    "    - {}: Type: {}, Quantity: {}, Created: {}, Updated: {}",
44                    sub.id, sub.r#type, sub.quantity, sub.created_at, sub.updated_at,
45                )
46                .unwrap();
47            }
48        }
49        if let Some(flags) = self.flags.as_ref() {
50            if !flags.is_empty() {
51                writeln!(&mut s, "  Feature flags:").unwrap();
52                for flag in flags {
53                    writeln!(&mut s, "    - {}", flag).unwrap();
54                }
55            }
56        }
57
58        s
59    }
60}
61
62#[derive(
63    // std
64    Clone,
65    Debug,
66    Default,
67    Eq,
68    PartialEq,
69    Ord,
70    PartialOrd,
71    // serde
72    Deserialize,
73    Serialize,
74    // strum
75    EnumString,
76    IntoStaticStr,
77    strum::Display,
78)]
79#[serde(rename_all = "lowercase")]
80#[strum(serialize_all = "lowercase")]
81#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
82#[typeshare::typeshare]
83pub enum AccountTier {
84    #[default]
85    Basic,
86    /// Partial access to Pro features and higher limits than Basic
87    ProTrial,
88    /// A Basic user that is pending a payment to go back to Pro
89    // soft-deprecated
90    PendingPaymentPro,
91    /// Pro user with an expiring subscription
92    // soft-deprecated
93    CancelledPro,
94    Pro,
95    Growth,
96    /// Growth tier but even higher limits
97    Employee,
98    /// No limits, full API access, admin endpoint access
99    Admin,
100
101    /// Forward compatibility
102    #[cfg(feature = "unknown-variants")]
103    #[doc(hidden)]
104    #[typeshare(skip)]
105    #[serde(untagged, skip_serializing)]
106    #[strum(default, to_string = "Unknown: {0}")]
107    Unknown(String),
108}
109impl AccountTier {
110    pub fn to_string_fancy(&self) -> String {
111        match self {
112            Self::Basic => "Community".to_owned(),
113            Self::ProTrial => "Pro Trial".to_owned(),
114            Self::PendingPaymentPro => "Community (pending payment for Pro)".to_owned(),
115            Self::CancelledPro => "Pro (subscription cancelled)".to_owned(),
116            Self::Pro => "Pro".to_owned(),
117            Self::Growth => "Growth".to_owned(),
118            Self::Employee => "Employee".to_owned(),
119            Self::Admin => "Admin".to_owned(),
120            #[cfg(feature = "unknown-variants")]
121            Self::Unknown(_) => self.to_string(),
122        }
123    }
124}
125
126#[derive(Debug, Deserialize, Serialize)]
127#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
128#[typeshare::typeshare]
129pub struct Subscription {
130    pub id: String,
131    pub r#type: SubscriptionType,
132    pub quantity: i32,
133    pub created_at: DateTime<Utc>,
134    pub updated_at: DateTime<Utc>,
135}
136
137#[derive(Debug, Deserialize)]
138#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
139#[typeshare::typeshare]
140pub struct SubscriptionRequest {
141    pub id: String,
142    pub r#type: SubscriptionType,
143    pub quantity: i32,
144}
145
146#[derive(
147    // std
148    Clone,
149    Debug,
150    Eq,
151    PartialEq,
152    // serde
153    Deserialize,
154    Serialize,
155    // strum
156    EnumString,
157    strum::Display,
158    IntoStaticStr,
159)]
160#[serde(rename_all = "lowercase")]
161#[strum(serialize_all = "lowercase")]
162#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
163#[typeshare::typeshare]
164pub enum SubscriptionType {
165    Pro,
166    Rds,
167
168    /// Forward compatibility
169    #[cfg(feature = "unknown-variants")]
170    #[doc(hidden)]
171    #[typeshare(skip)]
172    #[serde(untagged, skip_serializing)]
173    #[strum(default, to_string = "Unknown: {0}")]
174    Unknown(String),
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180    #[test]
181    fn deser() {
182        assert_eq!(
183            serde_json::from_str::<AccountTier>("\"basic\"").unwrap(),
184            AccountTier::Basic
185        );
186    }
187    #[cfg(feature = "unknown-variants")]
188    #[test]
189    fn unknown_deser() {
190        assert_eq!(
191            serde_json::from_str::<AccountTier>("\"\"").unwrap(),
192            AccountTier::Unknown("".to_string())
193        );
194        assert_eq!(
195            serde_json::from_str::<AccountTier>("\"hisshiss\"").unwrap(),
196            AccountTier::Unknown("hisshiss".to_string())
197        );
198        assert!(serde_json::to_string(&AccountTier::Unknown("asdf".to_string())).is_err());
199    }
200    #[cfg(not(feature = "unknown-variants"))]
201    #[test]
202    fn not_unknown_deser() {
203        assert!(serde_json::from_str::<AccountTier>("\"\"").is_err());
204        assert!(serde_json::from_str::<AccountTier>("\"hisshiss\"").is_err());
205    }
206}
207
208#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
209#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
210#[typeshare::typeshare]
211pub struct CreateAccountRequest {
212    pub auth0_id: String,
213    pub account_tier: AccountTier,
214}
215
216#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
217#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
218#[typeshare::typeshare]
219pub struct UpdateAccountTierRequest {
220    pub account_tier: AccountTier,
221}