Skip to main content

autter_core/
model.rs

1use serde::{Deserialize, Serialize};
2use serde_valid::Validate;
3pub use tetratto_core2::model::{ApiReturn, Error, Result, id::Id};
4use totp_rs::TOTP;
5use tritools::{
6    encoding::{hash_salted, salt},
7    time::unix_epoch_timestamp,
8};
9
10#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
11pub enum ThemePreference {
12    #[default]
13    Auto,
14    Light,
15    Dark,
16}
17
18#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
19pub enum UserPermission {
20    /// Manage other users.
21    ManageUsers,
22    /// Verify and unverify users.
23    ManageVerified,
24    /// Manage audit log entries.
25    ManageAuditLog,
26    /// Manage reports.
27    ManageReports,
28    /// Manage miscellaneous app data (e.g. Askall asks).
29    ManageMiscAppData,
30    /// Manage user notifications.
31    ManageNotifications,
32    /// User is banned.
33    Banned,
34    /// Ban and unban users.
35    ManageBans,
36    /// Delete other users.
37    DeleteUsers,
38    /// Manage user warnings.
39    ManageWarnings,
40    /// User is a premium user.
41    Seedling,
42    /// User is a premium plus user.
43    SeedlingPlus,
44    /// Manage user properties.
45    ManageProperties,
46    /// Manage user organizations.
47    ManageOrganizations,
48    /// Manage organization permission lists.
49    ManagePermissionsLists,
50    /// Manage the permissions of other users.
51    Administrator,
52}
53
54#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
55pub enum UserBadge {
56    /// User is a site moderator.
57    Staff,
58}
59
60#[derive(Clone, Debug, Serialize, Deserialize, Validate, Default)]
61pub struct UserSettings {
62    #[serde(default)]
63    #[validate(max_length = 32)]
64    pub display_name: String,
65    #[serde(default)]
66    #[validate(max_length = 4096)]
67    pub biography: String,
68    /// External links for the user's other profiles on other websites.
69    #[serde(default)]
70    #[validate(max_items = 15)]
71    #[validate(unique_items)]
72    pub links: Vec<(String, String)>,
73    /// The user's contacts.
74    #[serde(default)]
75    #[validate(max_items = 5)]
76    #[validate(unique_items)]
77    pub contacts: Vec<(String, String)>,
78    /// The user's theme used on their profile.
79    #[serde(default)]
80    pub profile_theme: ThemePreference,
81}
82
83impl UserSettings {
84    pub fn verify_values(&self) -> Result<()> {
85        if let Err(e) = self.validate() {
86            return Err(Error::MiscError(e.to_string()));
87        }
88
89        Ok(())
90    }
91}
92
93#[derive(Clone, Debug, Serialize, Deserialize)]
94pub struct TetrattoLinkedAccount {
95    pub id: Id,
96    pub username: String,
97    pub added: u128,
98}
99
100#[derive(Clone, Debug, Serialize, Deserialize, Default)]
101pub struct UserLinkedAccounts {
102    #[serde(default)]
103    pub tetratto_multiple: Vec<TetrattoLinkedAccount>,
104}
105
106/// `(ip, token, creation timestamp)`
107pub type Token = (String, String, u128);
108
109#[derive(Clone, Debug, Serialize, Deserialize)]
110pub struct User {
111    pub id: Id,
112    pub created: u128,
113    pub username: String,
114    pub password: String,
115    pub salt: String,
116    pub settings: UserSettings,
117    pub tokens: Vec<Token>,
118    pub permissions: Vec<UserPermission>,
119    pub is_verified: bool,
120    pub notification_count: usize,
121    /// The TOTP secret for this profile. An empty value means the user has TOTP disabled.
122    #[serde(default)]
123    pub totp: String,
124    /// The TOTP recovery codes for this profile.
125    #[serde(default)]
126    pub recovery_codes: Vec<String>,
127    /// The user's Stripe customer ID.
128    #[serde(default)]
129    pub stripe_id: String,
130    /// The reason the user was banned.
131    #[serde(default)]
132    pub ban_reason: String,
133    /// The time at which the user's ban will automatically expire.
134    #[serde(default)]
135    pub ban_expire: usize,
136    /// If the user is deactivated. Deactivated users act almost like deleted
137    /// users, but their data is not wiped.
138    #[serde(default)]
139    pub is_deactivated: bool,
140    /// The IDs of Stripe checkout sessions that this user has successfully completed.
141    ///
142    /// This should be checked BEFORE applying purchases to ensure that the user hasn't
143    /// already applied this purchase.
144    #[serde(default)]
145    pub checkouts: Vec<String>,
146    /// The time in which the user last consented to the site's policies.
147    #[serde(default)]
148    pub last_policy_consent: u128,
149    /// The other accounts in the database that are linked to this account.
150    #[serde(default)]
151    pub linked_accounts: UserLinkedAccounts,
152    /// A list of the user's profile badges.
153    #[serde(default)]
154    pub badges: Vec<UserBadge>,
155    /// The ID of the user's principal organization (i.e. the organization shown on their profile).
156    #[serde(default)]
157    pub principal_org: Id,
158    /// If the user is locked into their principal organization. Their organization's
159    /// owner can manage their account if this is true.
160    #[serde(default)]
161    pub org_as_tenant: bool,
162    /// The number of organizations the user can create.
163    #[serde(default)]
164    pub org_creation_credits: i32,
165    /// The number of users the user can register to an organization.
166    #[serde(default)]
167    pub org_user_register_credits: i32,
168    /// Users can pay to have their account marked as fully verified. This verification
169    /// confirms they are a real human (to a degree). Promoting users who are super
170    /// verified is a good way to keep AI and disgraceful content down.
171    #[serde(default)]
172    pub is_super_verified: bool,
173}
174
175impl Default for User {
176    fn default() -> Self {
177        Self::new("<unknown>".to_string(), String::new())
178    }
179}
180
181impl User {
182    /// Create a new [`User`].
183    pub fn new(username: String, password: String) -> Self {
184        let salt = salt();
185        let password = hash_salted(password, salt.clone());
186        let created = unix_epoch_timestamp();
187
188        Self {
189            id: Id::Legacy(Id::new().as_usize()),
190            created,
191            username,
192            password,
193            salt,
194            settings: UserSettings::default(),
195            tokens: Vec::new(),
196            permissions: Vec::new(),
197            is_verified: false,
198            notification_count: 0,
199            totp: String::new(),
200            recovery_codes: Vec::new(),
201            stripe_id: String::new(),
202            ban_reason: String::new(),
203            is_deactivated: false,
204            ban_expire: 0,
205            checkouts: Vec::new(),
206            last_policy_consent: created,
207            linked_accounts: UserLinkedAccounts::default(),
208            badges: Vec::new(),
209            principal_org: Id::default(),
210            org_as_tenant: false,
211            org_creation_credits: 0,
212            org_user_register_credits: 0,
213            is_super_verified: false,
214        }
215    }
216
217    /// Deleted user profile.
218    pub fn deleted() -> Self {
219        Self {
220            username: "<deleted>".to_string(),
221            id: Id::default(),
222            ..Default::default()
223        }
224    }
225
226    /// Banned user profile.
227    pub fn banned() -> Self {
228        Self {
229            username: "<banned>".to_string(),
230            id: Id::default(),
231            permissions: vec![UserPermission::Banned],
232            ..Default::default()
233        }
234    }
235
236    /// Create a new token
237    ///
238    /// # Returns
239    /// `(unhashed id, token)`
240    pub fn create_token(ip: &str) -> (String, Token) {
241        let unhashed = Id::new().to_string();
242        (
243            unhashed.clone(),
244            (
245                ip.to_string(),
246                tritools::encoding::hash(unhashed),
247                tritools::time::unix_epoch_timestamp(),
248            ),
249        )
250    }
251
252    /// Check if the given password is correct for the user.
253    pub fn check_password(&self, against: String) -> bool {
254        self.password == hash_salted(against, self.salt.clone())
255    }
256
257    /// Get a [`TOTP`] from the profile's `totp` secret value.
258    pub fn totp(&self, issuer: Option<String>) -> Option<TOTP> {
259        if self.totp.is_empty() {
260            return None;
261        }
262
263        TOTP::new(
264            totp_rs::Algorithm::SHA1,
265            6,
266            1,
267            30,
268            self.totp.as_bytes().to_owned(),
269            Some(issuer.unwrap_or("tetratto!".to_string())),
270            self.username.clone(),
271        )
272        .ok()
273    }
274
275    /// Clean the struct for public viewing.
276    pub fn clean(&mut self) {
277        self.password = String::new();
278        self.salt = String::new();
279
280        self.tokens = Vec::new();
281
282        self.recovery_codes = Vec::new();
283        self.totp = String::new();
284
285        self.settings = UserSettings::default();
286        self.stripe_id = String::new();
287    }
288}
289
290#[derive(Debug, Serialize, Deserialize)]
291pub struct Notification {
292    pub id: Id,
293    pub created: u128,
294    pub title: String,
295    pub content: String,
296    pub owner: Id,
297    pub read: bool,
298    pub tag: String,
299}
300
301impl Notification {
302    /// Create a new [`Notification`].
303    pub fn new(title: String, content: String, owner: Id) -> Self {
304        Self {
305            id: Id::new(),
306            created: unix_epoch_timestamp(),
307            title,
308            content,
309            owner,
310            read: false,
311            tag: String::new(),
312        }
313    }
314}
315
316#[derive(Serialize, Deserialize)]
317pub struct AuditLogEntry {
318    pub id: Id,
319    pub created: u128,
320    pub moderator: Id,
321    pub content: String,
322}
323
324impl AuditLogEntry {
325    /// Create a new [`AuditLogEntry`].
326    pub fn new(moderator: Id, content: String) -> Self {
327        Self {
328            id: Id::new(),
329            created: unix_epoch_timestamp(),
330            moderator,
331            content,
332        }
333    }
334}
335
336#[derive(Serialize, Deserialize)]
337pub struct IpBan {
338    pub ip: String,
339    pub created: u128,
340    pub reason: String,
341    pub moderator: Id,
342}
343
344impl IpBan {
345    /// Create a new [`IpBan`].
346    pub fn new(ip: String, moderator: Id, reason: String) -> Self {
347        Self {
348            ip,
349            created: unix_epoch_timestamp(),
350            reason,
351            moderator,
352        }
353    }
354}
355
356#[derive(Serialize, Deserialize)]
357pub struct UserWarning {
358    pub id: Id,
359    pub created: u128,
360    pub receiver: Id,
361    pub moderator: Id,
362    pub content: String,
363}
364
365impl UserWarning {
366    /// Create a new [`UserWarning`].
367    pub fn new(user: Id, moderator: Id, content: String) -> Self {
368        Self {
369            id: Id::new(),
370            created: unix_epoch_timestamp(),
371            receiver: user,
372            moderator,
373            content,
374        }
375    }
376}
377
378// socket
379pub mod socket {
380    use serde::{Deserialize, Serialize, de::DeserializeOwned};
381
382    #[derive(Serialize, Deserialize, PartialEq, Eq)]
383    pub enum CrudMessageType {
384        Create,
385        Delete,
386    }
387
388    #[derive(Serialize, Deserialize, PartialEq, Eq)]
389    pub enum PacketType {
390        /// A regular check to ensure the connection is still alive.
391        Ping,
392        /// General text which can be ignored.
393        Text,
394        /// A CRUD operation.
395        Crud(CrudMessageType),
396        /// A text key which identifies the socket.
397        Key,
398    }
399
400    #[derive(Serialize, Deserialize, PartialEq, Eq)]
401    pub enum SocketMethod {
402        /// Authentication and channel identification.
403        Headers,
404        /// A message was sent in the channel.
405        Message,
406        /// A message was deleted in the channel.
407        Delete,
408        /// Forward message from server to client. (Redis pubsub to ws)
409        Forward(PacketType),
410        /// A general packet from client to server. (ws to Redis pubsub)
411        Misc(PacketType),
412        /// A general packet from client to server. (ws to Redis pubsub)
413        Packet(PacketType),
414    }
415
416    #[derive(Serialize, Deserialize)]
417    pub struct SocketMessage {
418        pub method: SocketMethod,
419        pub data: String,
420    }
421
422    impl SocketMessage {
423        pub fn data<T: DeserializeOwned>(&self) -> T {
424            serde_json::from_str(&self.data).unwrap()
425        }
426    }
427
428    /// [`PacketType::Text`]
429    #[derive(Serialize, Deserialize, PartialEq, Eq)]
430    pub struct TextMessage {
431        pub text: String,
432    }
433}
434
435// properties
436pub mod properties {
437    use serde::{Deserialize, Serialize};
438    use tetratto_core2::model::id::Id;
439    use tritools::time::unix_epoch_timestamp;
440
441    #[derive(Serialize, Deserialize, PartialEq, Eq)]
442    pub enum Product {
443        /// A single Tetratto account.
444        Tetratto,
445        /// Ability to create a Shrimpcamp organization.
446        ///
447        /// $200 once.
448        Organization,
449        /// Ability to register a user to an organization.
450        ///
451        /// $4 once.
452        UserReg,
453        /// A seedling membership.
454        ///
455        /// $4 monthly.
456        Seedling,
457        /// A user verification payment (corresponds to [`User::is_super_verified`]).
458        ///
459        /// $2 once.
460        UserVerification,
461    }
462
463    #[derive(Serialize, Deserialize)]
464    pub struct Property {
465        pub id: Id,
466        pub created: u128,
467        pub owner: Id,
468        /// A unique name for the instance. Used in the subdomain for the CNAME
469        /// record's host (aka the URL for when the instance doesn't have a custom domain).
470        pub name: String,
471        pub product: Product,
472        pub custom_domain: String,
473    }
474
475    impl Property {
476        /// Create a new [`Property`].
477        pub fn new(owner: Id, name: String, product: Product) -> Self {
478            Self {
479                id: Id::new(),
480                created: unix_epoch_timestamp(),
481                owner,
482                name,
483                product,
484                custom_domain: String::new(),
485            }
486        }
487    }
488}
489
490// organizations
491pub mod organizations {
492    use serde::{Deserialize, Serialize};
493    use std::collections::HashMap;
494    use tetratto_core2::model::id::Id;
495    use tritools::time::unix_epoch_timestamp;
496
497    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
498    pub enum OrganizationRole {
499        Owner,
500        Custom(usize),
501        #[default]
502        Member,
503    }
504
505    #[derive(Debug, Clone, Serialize, Deserialize)]
506    pub struct Organization {
507        pub id: Id,
508        pub created: u128,
509        pub owner: Id,
510        pub name: String,
511        pub members: i32,
512        pub roles: HashMap<usize, String>,
513    }
514
515    impl Organization {
516        /// Create a new [`Organization`].
517        pub fn new(name: String, owner: Id) -> Self {
518            Self {
519                id: Id::new(),
520                created: unix_epoch_timestamp(),
521                owner,
522                name,
523                members: 0,
524                roles: HashMap::new(),
525            }
526        }
527    }
528
529    #[derive(Debug, Clone, Serialize, Deserialize)]
530    pub struct OrganizationMembership {
531        pub id: Id,
532        pub created: u128,
533        pub organization: Id,
534        pub owner: Id,
535        pub role: OrganizationRole,
536    }
537
538    impl OrganizationMembership {
539        /// Create a new [`OrganizationMembership`].
540        pub fn new(organization: Id, owner: Id) -> Self {
541            Self {
542                id: Id::new(),
543                created: unix_epoch_timestamp(),
544                organization,
545                owner,
546                role: OrganizationRole::Member,
547            }
548        }
549    }
550
551    #[derive(Debug, Clone, Serialize, Deserialize)]
552    pub struct PermissionsList {
553        pub id: Id,
554        pub created: u128,
555        pub owner_org: Id,
556        pub name: String,
557        pub roles: Vec<usize>,
558    }
559
560    impl PermissionsList {
561        /// Create a new [`PermissionsList`].
562        pub fn new(owner_org: Id, name: String) -> Self {
563            Self {
564                id: Id::new(),
565                created: unix_epoch_timestamp(),
566                owner_org,
567                name,
568                roles: Vec::new(),
569            }
570        }
571    }
572}